2 September 2026

An Angular auth interceptor, the functional way

An Angular auth interceptor, the functional way

Most results for this still show a class implementing HttpInterceptor with an @Injectable decorator and an entry in HTTP_INTERCEPTORS. That works, and it is not how you would write it now. HttpInterceptorFn is a plain function, uses inject(), and is registered in one line.

import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { inject } from '@angular/core';
import { catchError, throwError } from 'rxjs';
import { AuthService } from './auth.service';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const auth = inject(AuthService);
  const token = auth.token();

  // Never attach the token to a third-party URL. An interceptor sees
  // EVERY outgoing request, and this is how credentials end up in
  // somebody else's logs.
  const sameOrigin = req.url.startsWith('/') || req.url.startsWith(location.origin);

  const request = token && sameOrigin
    ? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })
    : req;

  return next(request).pipe(
    catchError((error: HttpErrorResponse) => {
      if (error.status === 401) {
        auth.signOut();
      }
      return throwError(() => error);
    }),
  );
};

Registering it:

import { provideHttpClient, withInterceptors } from '@angular/common/http';

bootstrapApplication(AppComponent, {
  providers: [
    provideHttpClient(withInterceptors([authInterceptor])),
  ],
});

Three things worth knowing

HttpRequest is immutable. req.headers.set(...) returns a new headers object and changes nothing about the request. req.clone() is the only way to alter one, and forgetting it produces an interceptor that runs correctly and does nothing.

Check the origin before attaching a token. An interceptor sees every request the app makes, including ones to third-party APIs. Without the guard above, your bearer token goes to whoever you call.

Order matters and it is the array order. Interceptors run in the order listed in withInterceptors, outermost first, and unwind in reverse on the way back. Put logging first if you want it to see what the others did.