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
User Login
User submits credentials through the login form
JWT Token Received
Backend validates credentials and returns JWT token with user data
Session Storage
Token and user data are stored in localStorage
Automatic Token Injection
HTTP interceptor adds token to all outgoing requests
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
login()
register()
saveSession()
logout()
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
Creates a new user account. register ( usuario : AuthRequest ): Observable < any >
Parameters:
usuario - Object containing username and password
Returns:
Observable with text response from server
Stores JWT token and user metadata in localStorage. saveSession ( response : AuthResponse ): void
Stores:
JWT token (via TokenStorageService)
Username
User role (ROLE_ADMIN or ROLE_USER)
Clears all authentication data. Removes:
JWT token
Username
User role
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
Method Description Returns saveToken(token)Stores JWT token in localStorage voidgetToken()Retrieves current token string | nullsignOut()Removes token from storage voidisLoggedIn()Checks if token exists boolean
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 );
};
Interceptor Invoked
Every HTTP request triggers the interceptor
Token Retrieved
Gets JWT token from TokenStorageService
Header Added
If token exists, clones request and adds Authorization: Bearer <token> header
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:
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:
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 () {
this . authService . logout ();
this . router . navigate ([ '/auth/login' ]);
}
Session Persistence
Authentication data is stored in localStorage, making sessions persist across browser refreshes:
Stored Data
Key Value Purpose auth-tokenJWT string API authentication usernameUser’s username Display 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:
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:
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