Introduction
Angular HTTP interceptors are used to attach authentication tokens to outgoing requests. When an interceptor fails to add the Authorization header, API requests return 401 Unauthorized errors. The issue is often subtle - the interceptor is registered but the header is not being added correctly:
// Interceptor code looks correct, but requests go out without Authorization header
req.headers.get('Authorization') // Returns nullSymptoms
- API requests return 401 Unauthorized
- Browser Network tab shows requests without
Authorizationheader - Interceptor's
intercept()method is called but header is not added - Some requests have the header while others do not
- Works in development but fails in production builds
Common Causes
- Interceptor not provided in the root injector or app configuration
- Token retrieved from storage synchronously when it has not been set yet
- Request not cloned correctly (HttpRequest is immutable)
- Multiple interceptors overwriting each other's headers
- Interceptor registered but
HTTP_INTERCEPTORSnot included in providers
Step-by-Step Fix
- 1.Register the interceptor correctly. For standalone components (Angular 14.1+):
- 2.```typescript
- 3.// app.config.ts
- 4.import { ApplicationConfig } from '@angular/core';
- 5.import { provideHttpClient, withInterceptors } from '@angular/common/http';
- 6.import { authInterceptor } from './auth.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(withInterceptors([authInterceptor])),
],
};
``
For NgModule-based apps:
typescript
@NgModule({
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true,
},
],
})
export class AppModule {}
- 1.Clone the request correctly when adding headers:
- 2.```typescript
- 3.import { Injectable } from '@angular/core';
- 4.import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
- 5.import { Observable } from 'rxjs';
@Injectable() export class AuthInterceptor implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { const token = localStorage.getItem('authToken');
if (token) {
// Clone with new header - HttpRequest is immutable
const cloned = req.clone({
headers: req.headers.set('Authorization', Bearer ${token}),
});
return next.handle(cloned);
}
return next.handle(req); } } ```
- 1.Handle async token retrieval with a custom HttpClient backend:
- 2.```typescript
- 3.// For async token sources (e.g., secure storage)
- 4.intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
- 5.return from(this.authService.getToken()).pipe(
- 6.switchMap((token) => {
- 7.if (token) {
- 8.const cloned = req.clone({
- 9.headers: req.headers.set('Authorization',
Bearer ${token}), - 10.});
- 11.return next.handle(cloned);
- 12.}
- 13.return next.handle(req);
- 14.}),
- 15.);
- 16.}
- 17.
` - 18.Debug interceptor execution by adding logging:
- 19.```typescript
- 20.intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
- 21.console.log('Interceptor called for:', req.url);
- 22.console.log('Token available:', !!localStorage.getItem('authToken'));
const cloned = req.clone({
setHeaders: { Authorization: Bearer ${localStorage.getItem('authToken')} },
});
console.log('Cloned request headers:', cloned.headers.keys()); return next.handle(cloned); } ```
Prevention
- Always use
req.clone()to modify requests - HttpRequest is immutable - Register interceptors with
multi: truein NgModule orwithInterceptors()for standalone - Test interceptor behavior in unit tests with
HttpClientTestingModule - Avoid
localStoragefor tokens in production - use HttpOnly cookies or secure storage - Handle token refresh with a retry mechanism in the interceptor
- Monitor outgoing requests in the browser Network tab during development
- Add an integration test that verifies the Authorization header is present on API calls