Skip to main content

Overview

Biblioteca Virtual implements role-based access control (RBAC) using Angular’s functional route guards. Three guards work together to control access to different parts of the application based on authentication status and user roles.

Guard Types

authGuard

Ensures user is authenticated

adminGuard

Verifies user has admin role

publicGuard

Restricts access to unauthenticated users

authGuard - Authentication Check

The authGuard verifies that a user has a valid JWT token before accessing protected routes.
src/app/core/guards/auth-guard.ts
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { TokenStorageService } from '../services/token-storage.service';

export const authGuard: CanActivateFn = (route, state) => {
  // Inject services
  const tokenStorage = inject(TokenStorageService);
  const router = inject(Router);

  // Get token
  const token = tokenStorage.getToken();

  // Verify token
  if (token) {
    // Has token → Allow access
    return true;
  } else {
    // No token → Redirect to login
    router.navigate(['/auth/login']);
    return false;
  }
};

How It Works

1

Token Retrieval

Gets JWT token from TokenStorageService
2

Token Validation

Checks if token exists (non-null)
3

Decision

  • Token exists: Returns true, allows navigation
  • No token: Redirects to /auth/login, returns false

Usage Example

app.routes.ts
{
  path: 'catalogo',
  component: CatalogoComponent,
  canActivate: [authGuard]  // User must be logged in
}
authGuard does NOT check user roles. It only verifies authentication. Use adminGuard for role-based restrictions.

adminGuard - Role Verification

The adminGuard checks if the authenticated user has the ROLE_ADMIN role.
src/app/core/guards/admin-guard.ts
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';

export const adminGuard: CanActivateFn = (route, state) => {
  const router = inject(Router);

  // Get role directly from localStorage
  const role = localStorage.getItem('role');

  if (role === 'ROLE_ADMIN') {
    // Is Admin: Allow access
    return true;
  } else {
    // Not Admin: Redirect to catalog
    router.navigate(['/catalogo']);
    return false;
  }
};

How It Works

1

Role Retrieval

Reads user role from localStorage
2

Role Comparison

Checks if role equals 'ROLE_ADMIN'
3

Decision

  • Is Admin: Returns true, allows navigation
  • Not Admin: Redirects to /catalogo, returns false

Usage Example

app.routes.ts
{
  path: 'libros',
  component: LibroListComponent,
  canActivate: [authGuard, adminGuard]  // Must be authenticated AND admin
}
Always use adminGuard together with authGuard. Place authGuard first to ensure the user is authenticated before checking their role.

publicGuard - Unauthenticated Access

The publicGuard restricts access to routes that should only be available to unauthenticated users (like login and registration pages).
src/app/core/guards/public-guard.ts
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { TokenStorageService } from '../services/token-storage.service';

export const publicGuard: CanActivateFn = (route, state) => {
  const tokenStorage = inject(TokenStorageService);
  const router = inject(Router);

  // Get token
  const token = tokenStorage.getToken();

  // If user is logged in
  if (token) {
    // Get role from localStorage
    const role = localStorage.getItem('role');

    // Redirect based on role
    if (role === 'ROLE_ADMIN') {
      router.navigate(['/libros']);
    } else {
      router.navigate(['/catalogo']);
    }

    return false; // Block access to login/register
  }

  // Allow access to login/register
  return true;
};

How It Works

1

Token Check

Checks if user has a JWT token
2

Role-Based Redirect

If logged in, redirects based on role:
  • Admin: /libros (Book management)
  • User: /catalogo (Book catalog)
3

Access Control

  • Logged in: Returns false, blocks access to public routes
  • Not logged in: Returns true, allows access

Usage Example

app.routes.ts
{
  path: 'auth/login',
  component: LoginComponent,
  canActivate: [publicGuard]  // Only accessible when NOT logged in
}
This prevents logged-in users from accessing the login page, improving UX by automatically redirecting them to their appropriate dashboard.

Guard Combination Patterns

Public Routes

Routes accessible only to unauthenticated users:
// Login and Registration
{ path: 'auth/login', component: LoginComponent, canActivate: [publicGuard] }
{ path: 'auth/registro', component: RegistroComponent, canActivate: [publicGuard] }

Authenticated User Routes

Routes accessible to any logged-in user (admin or regular user):
// User catalog
{ path: 'catalogo', component: CatalogoComponent, canActivate: [authGuard] }

Admin-Only Routes

Routes accessible only to authenticated admins:
// Book management
{ 
  path: 'libros', 
  component: LibroListComponent, 
  canActivate: [authGuard, adminGuard]  // Both guards required
}

Guard Execution Flow

When multiple guards are specified, they execute sequentially:

Role-Based Navigation in Components

Show/hide UI elements based on user role:
Conditional Navigation
import { Component, OnInit } from '@angular/core';
import { AuthService } from './auth/auth.service';

export class NavbarComponent implements OnInit {
  isAdmin = false;
  isLoggedIn = false;

  constructor(private authService: AuthService) {}

  ngOnInit() {
    this.isAdmin = this.authService.isAdmin();
    this.isLoggedIn = this.tokenStorage.isLoggedIn();
  }
}
Template with Role Checks
<!-- Show only to admins -->
<nav *ngIf="isAdmin">
  <a routerLink="/libros">Libros</a>
  <a routerLink="/autores">Autores</a>
  <a routerLink="/generos">Géneros</a>
  <a routerLink="/prestamos">Préstamos</a>
</nav>

<!-- Show to all logged-in users -->
<nav *ngIf="isLoggedIn">
  <a routerLink="/catalogo">Catálogo</a>
</nav>

<!-- Show only to logged-out users -->
<nav *ngIf="!isLoggedIn">
  <a routerLink="/auth/login">Iniciar Sesión</a>
  <a routerLink="/auth/registro">Registrarse</a>
</nav>

User Roles

The application supports two user roles:
Permissions:
  • Access all authenticated routes
  • Manage books (CRUD operations)
  • Manage authors (CRUD operations)
  • Manage genres (CRUD operations)
  • View and manage loans
  • Access user catalog
Default Redirect: /libros

Programmatic Role Checks

Use AuthService methods to check roles in component logic:
Component Logic
export class SomeComponent {
  private authService = inject(AuthService);

  canEdit() {
    return this.authService.isAdmin();
  }

  showAdminFeatures() {
    if (this.authService.isAdmin()) {
      // Show admin UI
    } else if (this.authService.isUser()) {
      // Show user UI
    }
  }
}

Guard Testing

Example unit tests for guards:
auth-guard.spec.ts
describe('authGuard', () => {
  it('should allow access when token exists', () => {
    spyOn(tokenStorage, 'getToken').and.returnValue('mock-token');
    
    const result = authGuard(null, null);
    
    expect(result).toBe(true);
  });

  it('should redirect to login when no token', () => {
    spyOn(tokenStorage, 'getToken').and.returnValue(null);
    spyOn(router, 'navigate');
    
    const result = authGuard(null, null);
    
    expect(result).toBe(false);
    expect(router.navigate).toHaveBeenCalledWith(['/auth/login']);
  });
});
admin-guard.spec.ts
describe('adminGuard', () => {
  it('should allow access for admin role', () => {
    spyOn(localStorage, 'getItem').and.returnValue('ROLE_ADMIN');
    
    const result = adminGuard(null, null);
    
    expect(result).toBe(true);
  });

  it('should redirect to catalog for non-admin', () => {
    spyOn(localStorage, 'getItem').and.returnValue('ROLE_USER');
    spyOn(router, 'navigate');
    
    const result = adminGuard(null, null);
    
    expect(result).toBe(false);
    expect(router.navigate).toHaveBeenCalledWith(['/catalogo']);
  });
});

Security Best Practices

Server-Side Validation

Always validate permissions on the backend. Guards only control UI access, not API security.

Guard Order

Place authGuard before adminGuard to avoid checking roles for unauthenticated users.

Token Validation

Implement token expiration handling and refresh mechanisms.

Hide UI Elements

Use role checks to hide admin features from regular users in the UI.

Common Scenarios

Scenario 1: Unauthenticated User

User visits: /libros

authGuard checks tokenNo token

Redirect to: /auth/login

Scenario 2: Regular User Accessing Admin Route

User visits: /libros

authGuard checks tokenToken exists

adminGuard checks roleROLE_USER

Redirect to: /catalogo

Scenario 3: Admin User

Admin visits: /libros

authGuard checks tokenToken exists

adminGuard checks roleROLE_ADMIN

Access granted: Load LibroListComponent

Scenario 4: Logged-In User Visits Login

User visits: /auth/login

publicGuard checks tokenToken exists

publicGuard checks roleROLE_USER

Redirect to: /catalogo

Advanced: Custom Guards

Create custom guards for specific business logic:
Custom Permission Guard
export const permissionGuard = (requiredPermission: string): CanActivateFn => {
  return (route, state) => {
    const authService = inject(AuthService);
    const router = inject(Router);
    
    const userPermissions = getUserPermissions(); // Custom logic
    
    if (userPermissions.includes(requiredPermission)) {
      return true;
    }
    
    router.navigate(['/unauthorized']);
    return false;
  };
};

// Usage
{
  path: 'prestamos/create',
  component: PrestamoFormComponent,
  canActivate: [authGuard, permissionGuard('CREATE_LOANS')]
}

Next Steps

Routing

Review complete route configuration

Authentication

Learn about JWT authentication flow

User Management

Manage user accounts and roles

Error Handling

Handle authorization errors gracefully

Build docs developers (and LLMs) love