Skip to main content

Overview

Biblioteca Virtual implements JWT (JSON Web Token) authentication to secure API communications. The authentication system handles user login, registration, session management, and automatic token injection into HTTP requests.

Authentication Architecture

1

User Login

User submits credentials through the login form
2

JWT Token Received

Backend validates credentials and returns JWT token with user data
3

Session Storage

Token and user data are stored in localStorage
4

Automatic Token Injection

HTTP interceptor adds token to all outgoing requests
5

Protected Route Access

Guards verify token presence before allowing route navigation

AuthService

The AuthService manages all authentication operations:
src/app/auth/auth.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { TokenStorageService } from '../core/services/token-storage.service';
import { AuthResponse, AuthRequest } from '../core/models/auth.interface';
import { environment } from '../../environments/environment';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  private API_URL = `${environment.apiUrl}/auth`;

  constructor(
    private http: HttpClient,
    private tokenStorage: TokenStorageService
  ) {}

  // Login
  login(usuario: AuthRequest): Observable<AuthResponse> {
    return this.http.post<AuthResponse>(`${this.API_URL}/login`, usuario);
  }

  // Register
  register(usuario: AuthRequest): Observable<any> {
    return this.http.post(`${this.API_URL}/register`, usuario, {
      responseType: 'text',
    });
  }

  // Save token and user data
  saveSession(response: AuthResponse): void {
    this.tokenStorage.saveToken(response.token);
    localStorage.setItem('username', response.username);
    localStorage.setItem('role', response.role);
  }

  // Logout
  logout(): void {
    this.tokenStorage.signOut();
    localStorage.removeItem('username');
    localStorage.removeItem('role');
  }

  // Role verification
  isAdmin(): boolean {
    return localStorage.getItem('role') === 'ROLE_ADMIN';
  }

  isUser(): boolean {
    return localStorage.getItem('role') === 'ROLE_USER';
  }
}

Key Methods

Sends username and password to the backend /auth/login endpoint.
login(usuario: AuthRequest): Observable<AuthResponse>
Parameters:
  • usuario - Object containing username and password
Returns:
  • Observable of AuthResponse with token and user data

Authentication Interfaces

Type-safe authentication data structures:
src/app/core/models/auth.interface.ts
export interface AuthRequest {
  username: string;
  password: string;
}

export interface AuthResponse {
  token: string;
  tipoToken: string;
  username: string;
  role: string;
}
The role field contains either ROLE_ADMIN or ROLE_USER, which is used throughout the application for authorization decisions.

TokenStorageService

Manages JWT token persistence in localStorage:
src/app/core/services/token-storage.service.ts
import { Injectable } from '@angular/core';

const TOKEN_KEY = 'auth-token';

@Injectable({
  providedIn: 'root',
})
export class TokenStorageService {
  // Save token
  saveToken(token: string): void {
    window.localStorage.removeItem(TOKEN_KEY);
    window.localStorage.setItem(TOKEN_KEY, token);
  }

  // Get token
  getToken(): string | null {
    return window.localStorage.getItem(TOKEN_KEY);
  }

  // Remove token
  signOut(): void {
    window.localStorage.removeItem(TOKEN_KEY);
  }

  // Check if user is logged in
  isLoggedIn(): boolean {
    return this.getToken() !== null;
  }
}

Token Storage Methods

MethodDescriptionReturns
saveToken(token)Stores JWT token in localStoragevoid
getToken()Retrieves current tokenstring | null
signOut()Removes token from storagevoid
isLoggedIn()Checks if token existsboolean

HTTP Interceptor

The authInterceptor automatically attaches JWT tokens to all outgoing HTTP requests:
src/app/core/interceptors/auth-interceptor.ts
import { inject } from '@angular/core';
import { HttpInterceptorFn } from '@angular/common/http';
import { TokenStorageService } from '../services/token-storage.service';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const tokenStorage = inject(TokenStorageService);
  const token = tokenStorage.getToken();

  if (!token) {
    return next(req);
  }

  // Add token to request header
  const clonedRequest = req.clone({
    setHeaders: {
      Authorization: `Bearer ${token}`,
    },
  });

  return next(clonedRequest);
};
1

Interceptor Invoked

Every HTTP request triggers the interceptor
2

Token Retrieved

Gets JWT token from TokenStorageService
3

Header Added

If token exists, clones request and adds Authorization: Bearer <token> header
4

Request Sent

Modified request is sent to the backend
The interceptor is registered globally in app.config.ts:
provideHttpClient(withFetch(), withInterceptors([authInterceptor]))

Login Flow Example

Complete implementation of the login process:
Login Component Example
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from '../auth.service';
import { AuthRequest } from '../../core/models/auth.interface';

export class LoginComponent {
  private authService = inject(AuthService);
  private router = inject(Router);
  
  username = '';
  password = '';
  errorMessage = '';

  onSubmit() {
    const credentials: AuthRequest = {
      username: this.username,
      password: this.password
    };

    this.authService.login(credentials).subscribe({
      next: (response) => {
        // Save session data
        this.authService.saveSession(response);
        
        // Redirect based on role
        if (response.role === 'ROLE_ADMIN') {
          this.router.navigate(['/libros']);
        } else {
          this.router.navigate(['/catalogo']);
        }
      },
      error: (err) => {
        this.errorMessage = 'Credenciales inválidas';
        console.error('Login error:', err);
      }
    });
  }
}

Login Sequence Diagram

Registration Flow

User registration follows a similar pattern:
Registration Example
onRegister() {
  const newUser: AuthRequest = {
    username: this.username,
    password: this.password
  };

  this.authService.register(newUser).subscribe({
    next: (response) => {
      console.log('Registration successful:', response);
      // Redirect to login
      this.router.navigate(['/auth/login']);
    },
    error: (err) => {
      this.errorMessage = 'Error en el registro';
      console.error('Registration error:', err);
    }
  });
}
Registration does NOT automatically log the user in. After successful registration, redirect to the login page.

Logout Implementation

Logout clears all session data and redirects to login:
Logout Example
logout() {
  this.authService.logout();
  this.router.navigate(['/auth/login']);
}

Session Persistence

Authentication data is stored in localStorage, making sessions persist across browser refreshes:

Stored Data

KeyValuePurpose
auth-tokenJWT stringAPI authentication
usernameUser’s usernameDisplay in UI
roleROLE_ADMIN or ROLE_USERAuthorization decisions

Checking Authentication Status

// Check if user is logged in
const isLoggedIn = this.tokenStorageService.isLoggedIn();

// Check user role
const isAdmin = this.authService.isAdmin();
const isUser = this.authService.isUser();

Error Handling

Handle authentication errors gracefully:
Error Handling Pattern
this.authService.login(credentials).subscribe({
  next: (response) => {
    // Success handling
    this.authService.saveSession(response);
    this.router.navigate(['/catalogo']);
  },
  error: (err) => {
    if (err.status === 401) {
      this.errorMessage = 'Credenciales inválidas';
    } else if (err.status === 0) {
      this.errorMessage = 'No se puede conectar al servidor';
    } else {
      this.errorMessage = 'Error desconocido';
    }
  }
});

Security Considerations

Token Expiration

JWT tokens have an expiration time set by the backend. Implement token refresh or force re-login on expiry.

HTTPS Only

Always use HTTPS in production to prevent token interception.

XSS Protection

Sanitize all user inputs to prevent token theft via XSS attacks.

Logout on Error

Clear session if backend returns 401/403 errors on protected endpoints.

Testing Authentication

Example unit tests for AuthService:
auth.service.spec.ts
describe('AuthService', () => {
  it('should login and return auth response', () => {
    const mockResponse: AuthResponse = {
      token: 'mock-jwt-token',
      tipoToken: 'Bearer',
      username: 'testuser',
      role: 'ROLE_USER'
    };

    service.login({ username: 'test', password: 'pass' }).subscribe(response => {
      expect(response).toEqual(mockResponse);
    });
  });

  it('should save session data', () => {
    const response: AuthResponse = {
      token: 'test-token',
      tipoToken: 'Bearer',
      username: 'testuser',
      role: 'ROLE_ADMIN'
    };

    service.saveSession(response);

    expect(localStorage.getItem('username')).toBe('testuser');
    expect(localStorage.getItem('role')).toBe('ROLE_ADMIN');
  });
});

Next Steps

Authorization Guards

Learn how guards use authentication to protect routes

Auth Service

Explore backend API endpoints

User Roles

Understand role-based features

Error Handling

Implement robust error handling

Build docs developers (and LLMs) love