Interceptor de autenticación con JWT
Los interceptores de autenticación representan uno de los patrones más fundamentales en aplicaciones empresariales modernas. Su función principal consiste en agregar automáticamente tokens de autorización a las peticiones HTTP salientes, eliminando la necesidad de hacerlo manualmente en cada servicio.
Creación del interceptor de autenticación
Un interceptor funcional para autenticación JWT sigue un patrón específico que aprovecha la inyección de dependencias moderna de Angular. El interceptor debe acceder al token almacenado y agregarlo como header Authorization:
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const token = inject(AuthService).getToken();
// Si no hay token, continuar sin modificar la request
if (!token) {
return next(req);
}
// Clonar la request y agregar el header Authorization
const authReq = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
return next(authReq);
};
La función inject() nos permite acceder al AuthService directamente dentro del interceptor funcional, siguiendo el patrón de inyección moderna de Angular 20+.
Servicio de autenticación básico
Para que el interceptor funcione correctamente, necesitamos un AuthService que gestione el almacenamiento y recuperación del token JWT:
import { Injectable, signal } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class AuthService {
private readonly tokenKey = 'auth_token';
private readonly tokenSignal = signal<string | null>(null);
constructor() {
// Cargar token desde localStorage al inicializar
const storedToken = localStorage.getItem(this.tokenKey);
if (storedToken) {
this.tokenSignal.set(storedToken);
}
}
getToken(): string | null {
return this.tokenSignal();
}
setToken(token: string): void {
localStorage.setItem(this.tokenKey, token);
this.tokenSignal.set(token);
}
clearToken(): void {
localStorage.removeItem(this.tokenKey);
this.tokenSignal.set(null);
}
isAuthenticated(): boolean {
return !!this.getToken();
}
}
Este servicio utiliza signals para mantener el estado reactivo del token y localStorage para persistencia entre sesiones del navegador.
Configuración del interceptor
El interceptor debe registrarse en la configuración de la aplicación usando withInterceptors:
import { bootstrapApplication } from '@angular/platform-browser';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { AppComponent } from './app/app.component';
import { authInterceptor } from './app/interceptors/auth.interceptor';
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(
withInterceptors([authInterceptor])
)
]
});
Exclusión de endpoints públicos
No todas las peticiones requieren autenticación. Los endpoints públicos como login, registro o recursos estáticos deben excluirse del interceptor:
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const token = inject(AuthService).getToken();
// Lista de endpoints que no requieren autenticación
const publicEndpoints = ['/auth/login', '/auth/register', '/public'];
const isPublicEndpoint = publicEndpoints.some(endpoint =>
req.url.includes(endpoint)
);
// Si es endpoint público o no hay token, continuar sin modificar
if (isPublicEndpoint || !token) {
return next(req);
}
const authReq = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
return next(authReq);
};
Integración con el flujo de login
El AuthService se integra naturalmente con el proceso de autenticación. Cuando el usuario se autentica exitosamente, el token se almacena y estará disponible automáticamente para el interceptor:
@Injectable({
providedIn: 'root'
})
export class LoginService {
private http = inject(HttpClient);
private authService = inject(AuthService);
login(credentials: LoginCredentials) {
return this.http.post<AuthResponse>('/auth/login', credentials)
.pipe(
tap(response => {
// El interceptor NO afectará esta petición por ser /auth/login
this.authService.setToken(response.accessToken);
})
);
}
}
Una vez almacenado el token, todas las peticiones posteriores incluirán automáticamente el header Authorization sin requerir modificaciones en los servicios existentes.
Validación de formato del token
Para mayor robustez, el interceptor puede incluir validación básica del formato JWT antes de agregarlo a las peticiones:
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const token = inject(AuthService).getToken();
if (!token || !isValidJWTFormat(token)) {
return next(req);
}
const authReq = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
return next(authReq);
};
function isValidJWTFormat(token: string): boolean {
// JWT tiene 3 partes separadas por puntos
const parts = token.split('.');
return parts.length === 3 && parts.every(part => part.length > 0);
}
Esta aproximación funcional moderna elimina la complejidad de los interceptores basados en clases, proporcionando una solución limpia y mantenible para la autenticación automática en aplicaciones Angular 20+.
Manejo de refresh tokens y errores 401
El manejo de refresh tokens representa el siguiente nivel de sofisticación en interceptores de autenticación. Cuando un token JWT expira, el servidor responde con error 401, y necesitamos renovar automáticamente el token sin interrumpir la experiencia del usuario.
Interceptor de respuesta para errores 401
Los interceptores funcionales pueden manejar tanto requests como responses. Para gestionar errores 401, necesitamos interceptar las respuestas y detectar cuándo el token ha expirado:
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { inject } from '@angular/core';
import { catchError, switchMap, throwError } from 'rxjs';
export const refreshTokenInterceptor: HttpInterceptorFn = (req, next) => {
const authService = inject(AuthService);
return next(req).pipe(
catchError((error: HttpErrorResponse) => {
// Solo manejar errores 401 Unauthorized
if (error.status === 401 && authService.getRefreshToken()) {
return handleTokenRefresh(req, next, authService);
}
// Para otros errores, propagarlos normalmente
return throwError(() => error);
})
);
};
Servicio de autenticación extendido
El AuthService debe ampliarse para manejar refresh tokens y proporcionar métodos de renovación:
@Injectable({
providedIn: 'root'
})
export class AuthService {
private readonly tokenKey = 'auth_token';
private readonly refreshTokenKey = 'refresh_token';
private readonly tokenSignal = signal<string | null>(null);
private readonly refreshTokenSignal = signal<string | null>(null);
private isRefreshing = false;
private refreshTokenSubject = new BehaviorSubject<string | null>(null);
getRefreshToken(): string | null {
return this.refreshTokenSignal() || localStorage.getItem(this.refreshTokenKey);
}
setTokens(accessToken: string, refreshToken: string): void {
localStorage.setItem(this.tokenKey, accessToken);
localStorage.setItem(this.refreshTokenKey, refreshToken);
this.tokenSignal.set(accessToken);
this.refreshTokenSignal.set(refreshToken);
}
refreshAccessToken(): Observable<AuthResponse> {
const refreshToken = this.getRefreshToken();
if (!refreshToken) {
return throwError(() => new Error('No refresh token available'));
}
return this.http.post<AuthResponse>('/auth/refresh', { refreshToken })
.pipe(
tap(response => {
this.setTokens(response.accessToken, response.refreshToken);
this.refreshTokenSubject.next(response.accessToken);
}),
catchError(error => {
// Si el refresh token también expiró, limpiar todo
this.logout();
return throwError(() => error);
})
);
}
}
Lógica de renovación automática
La función handleTokenRefresh gestiona el proceso complejo de renovar tokens y reintentar la petición original:
function handleTokenRefresh(
req: HttpRequest<any>,
next: HttpHandlerFn,
authService: AuthService
): Observable<HttpEvent<any>> {
// Si ya estamos renovando, esperar al resultado
if (authService.isRefreshing) {
return authService.refreshTokenSubject.pipe(
filter(token => token !== null),
take(1),
switchMap(token => {
// Reintentar la petición original con el nuevo token
const newReq = addTokenToRequest(req, token);
return next(newReq);
})
);
}
// Iniciar proceso de renovación
authService.isRefreshing = true;
authService.refreshTokenSubject.next(null);
return authService.refreshAccessToken().pipe(
switchMap(response => {
authService.isRefreshing = false;
authService.refreshTokenSubject.next(response.accessToken);
// Reintentar la petición original
const newReq = addTokenToRequest(req, response.accessToken);
return next(newReq);
}),
catchError(error => {
authService.isRefreshing = false;
authService.refreshTokenSubject.next(null);
// Redireccionar al login si la renovación falla
inject(Router).navigate(['/login']);
return throwError(() => error);
})
);
}
Función auxiliar para agregar tokens
Una función helper simplifica la adición del token a las peticiones HTTP:
function addTokenToRequest(req: HttpRequest<any>, token: string): HttpRequest<any> {
return req.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
}
Configuración de múltiples interceptores
Los interceptores deben ordenarse correctamente para que funcionen en secuencia: primero agregar el token, luego manejar errores de renovación:
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(
withInterceptors([
authInterceptor, // Agrega token a requests
refreshTokenInterceptor // Maneja renovación en responses
])
)
]
});
Manejo de múltiples peticiones simultáneas
Cuando múltiples peticiones fallan simultáneamente por token expirado, solo debe ejecutarse una renovación. El patrón con BehaviorSubject evita renovaciones duplicadas:
// En el AuthService
private isRefreshing = false;
private refreshTokenSubject = new BehaviorSubject<string | null>(null);
// Las peticiones en espera se suscriben al subject
// Solo la primera ejecuta refreshAccessToken()
// Las demás esperan el resultado compartido
Exclusión de endpoints de renovación
El interceptor de refresh debe excluir los endpoints de renovación para evitar bucles infinitos:
export const refreshTokenInterceptor: HttpInterceptorFn = (req, next) => {
const authService = inject(AuthService);
return next(req).pipe(
catchError((error: HttpErrorResponse) => {
// No manejar errores en endpoints de auth
const isAuthEndpoint = req.url.includes('/auth/');
if (error.status === 401 && !isAuthEndpoint && authService.getRefreshToken()) {
return handleTokenRefresh(req, next, authService);
}
return throwError(() => error);
})
);
};
Limpieza al cerrar sesión
Cuando el usuario cierra sesión o los tokens no pueden renovarse, el sistema debe limpiar completamente el estado:
logout(): void {
localStorage.removeItem(this.tokenKey);
localStorage.removeItem(this.refreshTokenKey);
this.tokenSignal.set(null);
this.refreshTokenSignal.set(null);
this.isRefreshing = false;
this.refreshTokenSubject.next(null);
inject(Router).navigate(['/login']);
}
Esta implementación proporciona una experiencia de usuario fluida donde los tokens se renuevan automáticamente sin interrumpir el flujo de trabajo, manteniendo la seguridad mediante la rotación regular de credenciales de acceso.

Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Angular es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Angular
Explora más contenido relacionado con Angular y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
- Comprender el uso de interceptores funcionales para añadir tokens JWT a peticiones HTTP.
- Implementar un servicio de autenticación que gestione tokens y su persistencia.
- Configurar interceptores para excluir endpoints públicos y manejar errores 401.
- Desarrollar lógica para la renovación automática de tokens mediante refresh tokens.
- Gestionar múltiples peticiones simultáneas y limpiar el estado al cerrar sesión.