In modern Angular applications, authentication tokens such as JWTs (JSON Web Tokens) are essential for securing API calls. HTTP interceptors provide a centralized way to attach these tokens to all outgoing requests automatically, ensuring consistent authentication and reducing repetitive code in services and components.
This post provides a comprehensive guide to setting up JWT HTTP interceptors, covering everything from basic usage to advanced features like error handling, token refresh, and modular implementation.
1. Understanding HTTP Interceptors in Angular
Angular HTTP interceptors are a part of the HttpClient
module. They act as middleware for outgoing HTTP requests and incoming responses. Interceptors can:
- Modify request headers (e.g., attach JWT tokens).
- Handle errors globally.
- Log requests and responses for debugging.
- Transform request or response data.
By using interceptors, you avoid adding authentication headers manually in each service method.
2. Basic JWT Interceptor Setup
To create an interceptor, implement the HttpInterceptor
interface:
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http';
@Injectable()
export class JwtInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler) {
const token = localStorage.getItem('token');
if (token) {
req = req.clone({
setHeaders: { Authorization: Bearer ${token}
}
});
}
return next.handle(req);
}
}
req.clone()
creates a new request with the Authorization header.next.handle(req)
forwards the request to the server.
3. Registering the Interceptor
Add the interceptor in app.module.ts
using the HTTP_INTERCEPTORS
token:
import { HTTP_INTERCEPTORS } from '@angular/common/http';
@NgModule({
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true }
]
})
export class AppModule {}
multi: true
allows multiple interceptors to coexist.- The interceptor now runs automatically for all HTTP requests.
4. Using the AuthService with Interceptors
When combined with a login service, the interceptor ensures all requests carry the JWT token:
@Injectable({ providedIn: 'root' })
export class AuthService {
login(username: string, password: string) {
return this.http.post<{ token: string }>('/api/login', { username, password })
.pipe(tap(res => localStorage.setItem('token', res.token)));
}
logout() {
localStorage.removeItem('token');
}
}
No additional headers are needed in service methods:
getUserData() {
return this.http.get('/api/user');
}
The interceptor automatically attaches the JWT from localStorage
.
5. Handling Token Expiration
Tokens often have a limited lifetime. To handle expired tokens:
- Detect
401 Unauthorized
responses. - Refresh the token if possible.
- Retry the failed request.
Example:
import { catchError, switchMap } from 'rxjs/operators';
import { throwError, of } from 'rxjs';
intercept(req: HttpRequest<any>, next: HttpHandler) {
const token = localStorage.getItem('token');
if (token) {
req = req.clone({ setHeaders: { Authorization: Bearer ${token}
} });
}
return next.handle(req).pipe(
catchError(err => {
if (err.status === 401) {
return this.authService.refreshToken().pipe(
switchMap(newToken => {
req = req.clone({ setHeaders: { Authorization: Bearer ${newToken}
} });
return next.handle(req);
})
);
}
return throwError(() => err);
})
);
}
- Uses
catchError
to detect401
responses. switchMap
retries the request with the new token.
6. Logging and Debugging Requests
Interceptors can also log requests for debugging purposes:
intercept(req: HttpRequest<any>, next: HttpHandler) {
console.log('Outgoing request:', req.url, req.headers);
return next.handle(req).pipe(
tap(event => console.log('Response received:', event))
);
}
This helps identify issues like missing headers or server errors.
7. Multiple Interceptors
You can chain multiple interceptors for different tasks:
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true }
]
- JWT interceptor attaches tokens.
- Logging interceptor logs requests/responses.
- Execution order is the order of registration.
8. Conditionally Attaching Tokens
Sometimes, you may not want to attach a token for public endpoints:
intercept(req: HttpRequest<any>, next: HttpHandler) {
if (req.url.includes('/public')) {
return next.handle(req);
}
const token = localStorage.getItem('token');
if (token) {
req = req.clone({ setHeaders: { Authorization: Bearer ${token}
} });
}
return next.handle(req);
}
This avoids sending unnecessary tokens to endpoints that don’t require authentication.
9. Using Interceptors with Angular Services
Example service using HTTP methods without manually adding headers:
@Injectable({ providedIn: 'root' })
export class UserService {
constructor(private http: HttpClient) {}
getProfile() {
return this.http.get('/api/profile');
}
updateProfile(data: any) {
return this.http.put('/api/profile', data);
}
}
All requests automatically carry the JWT via the interceptor.
10. Testing JWT Interceptors
Use Angular’s testing utilities to verify headers:
it('should attach JWT token to requests', () => {
const interceptor: JwtInterceptor = TestBed.inject(JwtInterceptor);
localStorage.setItem('token', '12345');
const httpRequest = new HttpRequest('GET', '/api/test');
interceptor.intercept(httpRequest, {
handle: req => {
expect(req.headers.get('Authorization')).toBe('Bearer 12345');
return of({});
}
}).subscribe();
});
- Ensures the interceptor attaches tokens correctly.
11. Best Practices for JWT Interceptors
- Centralize token management in a service.
- Use interceptors to attach tokens automatically.
- Handle token expiration with refresh logic.
- Avoid sending tokens to public endpoints.
- Chain interceptors for logging, error handling, and token attachment.
- Secure token storage (localStorage, sessionStorage, or cookies with HTTP-only flags).
- Test interceptors to ensure reliability.
12. Advanced Features
- Token Refresh Queue: Ensure multiple requests waiting for a refreshed token don’t trigger multiple refresh calls.
- Global Error Handling: Interceptor can catch errors like
401
and403
to redirect users to login. - Custom Headers: Combine JWT with other headers like
Content-Type
orAccept
.
13. Example: Complete JWT Interceptor with Refresh
@Injectable()
export class JwtInterceptor implements HttpInterceptor {
private isRefreshing = false;
constructor(private authService: AuthService) {}
intercept(req: HttpRequest<any>, next: HttpHandler) {
const token = this.authService.getToken();
if (token) req = req.clone({ setHeaders: { Authorization: Bearer ${token}
} });
return next.handle(req).pipe(
catchError(err => {
if (err.status === 401 && !this.isRefreshing) {
this.isRefreshing = true;
return this.authService.refreshToken().pipe(
switchMap(newToken => {
this.isRefreshing = false;
req = req.clone({ setHeaders: { Authorization: Bearer ${newToken}
} });
return next.handle(req);
})
);
}
return throwError(() => err);
})
);
}
}
- Ensures only one refresh request is sent at a time.
- All failed requests are retried with the new token.
Leave a Reply