← Back to Articles         
What is an HTTP interceptor?

What is an HTTP interceptor?

Published on 01 Oct 2026     6 min read Angular
HTTP Interceptors

What is an HTTP Interceptor in Angular?

An HTTP Interceptor in Angular is a mechanism that lets you intercept and modify HTTP requests before they are sent to the server and/or HTTP responses before they reach your application code.

Think of an interceptor as middleware between Angular's HttpClient and the Web API.

Angular Component
       ↓
Angular Service
       ↓
HttpClient
       ↓
HTTP Interceptor
       ↓
ASP.NET Core Web API
       ↓
HTTP Interceptor
       ↓
Angular Service
       ↓
Component

Why do we need an HTTP Interceptor?

Suppose your Angular application makes 50 API calls and every request requires a JWT token:

Authorization: Bearer eyJhbGciOi...

Without an interceptor, you might repeatedly write:

this.http.get('/api/products', {
  headers: {
    Authorization: `Bearer ${token}`
  }
});

With an interceptor, the token can be added automatically to every applicable request.

Interceptors are commonly used for:

  • Adding JWT / Authorization headers
  • Adding custom headers such as Correlation ID
  • Centralized HTTP error handling
  • Request/response logging
  • Loading indicators
  • Token refresh logic
  • Modifying request URLs or headers
  • Measuring API response time

Simple Full Example

Modern standalone Angular supports functional interceptors, which are generally the preferred approach for new applications.

auth.interceptor.ts

import { HttpInterceptorFn } from '@angular/common/http';

export const authInterceptor: HttpInterceptorFn = (req, next) => {

  const token = 'my-jwt-token';

  const modifiedRequest = req.clone({
    setHeaders: {
      Authorization: `Bearer ${token}`
    }
  });

  return next(modifiedRequest);
};

app.config.ts

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

import { authInterceptor } from './auth.interceptor';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(
      withInterceptors([authInterceptor])
    )
  ]
};

product.service.ts

import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

export interface Product {
  id: number;
  name: string;
  price: number;
}

@Injectable({
  providedIn: 'root'
})
export class ProductService {

  private http = inject(HttpClient);

  getProducts(): Observable<Product[]> {
    return this.http.get<Product[]>(
      'https://localhost:7001/api/products'
    );
  }
}

product.ts

import { Component, inject } from '@angular/core';
import { ProductService } from './product.service';

@Component({
  selector: 'app-product',
  standalone: true,
  template: `
    <button (click)="loadProducts()">
      Load Products
    </button>
  `
})
export class Product {

  private productService = inject(ProductService);

  loadProducts() {
    this.productService.getProducts()
      .subscribe(products => {
        console.log(products);
      });
  }
}

How does it work?

When this executes:

this.http.get<Product[]>(
  'https://localhost:7001/api/products'
);

Angular first creates an HTTP request.

The interceptor receives it:

(req, next) => {

req represents the outgoing HTTP request.

The interceptor then adds the authorization header:

const modifiedRequest = req.clone({
  setHeaders: {
    Authorization: `Bearer ${token}`
  }
});

The actual request becomes roughly:

GET /api/products HTTP/1.1

Authorization: Bearer my-jwt-token

Finally:

return next(modifiedRequest);

means:

"I have finished processing this request. Pass it to the next interceptor or send it to the server."


Why do we use req.clone()?

Angular HTTP request objects are immutable.

Therefore, you should not directly modify the existing request.

You create a modified copy:

const modifiedRequest = req.clone({
  setHeaders: {
    Authorization: `Bearer ${token}`
  }
});

Then forward the new request:

return next(modifiedRequest);

This is an important interview point.


Request and Response Interception

An interceptor isn't limited to outgoing requests.

                    REQUEST
Angular  ----------------------------> Web API
             HTTP Interceptor

                    RESPONSE
Angular  <---------------------------- Web API
             HTTP Interceptor

For example:

import { HttpInterceptorFn } from '@angular/common/http';
import { tap } from 'rxjs';

export const loggingInterceptor: HttpInterceptorFn =
  (req, next) => {

    console.log('Request:', req.url);

    return next(req).pipe(
      tap(response => {
        console.log('Response:', response);
      })
    );
  };

So the same interceptor can perform logic on both sides.


Multiple Interceptors

A real application commonly has several interceptors:

provideHttpClient(
  withInterceptors([
    authInterceptor,
    loggingInterceptor,
    errorInterceptor
  ])
)

For example:

HttpClient
    ↓
Auth Interceptor
    ↓
Logging Interceptor
    ↓
Error Interceptor
    ↓
ASP.NET Core API

This keeps each cross-cutting concern separate.


Before Standalone Angular

Traditionally, interceptors were class-based and registered through HTTP_INTERCEPTORS.

@Injectable()
export class AuthInterceptor
  implements HttpInterceptor {

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {

    const modifiedRequest = req.clone({
      setHeaders: {
        Authorization: 'Bearer my-token'
      }
    });

    return next.handle(modifiedRequest);
  }
}

Registration:

providers: [
  {
    provide: HTTP_INTERCEPTORS,
    useClass: AuthInterceptor,
    multi: true
  }
]

Modern Angular supports a simpler functional style:

export const authInterceptor: HttpInterceptorFn =
  (req, next) => {

    return next(req);
  };

and registration:

provideHttpClient(
  withInterceptors([authInterceptor])
)

Advantages

  • Centralized logic — authentication, logging, and error handling can be implemented in one place.
  • Avoids duplicate code — components/services don't repeatedly add the same headers.
  • Separation of concerns — API services concentrate on API operations rather than infrastructure concerns.
  • Reusable — one interceptor can affect many HTTP calls.
  • Maintainable — changing authentication header logic can be done centrally.

Disadvantages

If too much logic is placed inside interceptors, request processing can become difficult to understand and debug.

For example, avoid creating one enormous interceptor responsible for authentication, refresh tokens, logging, caching, loading indicators, and error handling. Separate concerns when practical.


Key Points

  • HTTP interceptors sit between HttpClient and the server.
  • They can intercept requests and responses.
  • Common uses include JWT tokens, logging, error handling, correlation IDs, and token refresh.
  • HTTP requests are immutable, so use:
req.clone()
  • Continue the HTTP pipeline using:
next(req)
  • Modern standalone Angular can register functional interceptors using:
provideHttpClient(
  withInterceptors([...])
)
  • Interceptors do not replace backend security. Your ASP.NET Core API must still validate JWTs and enforce authorization.

Interview Questions and Answers

1. What is an HTTP interceptor?

An HTTP interceptor is an Angular mechanism that intercepts HTTP requests and responses globally, allowing common processing such as adding authentication headers, logging, and error handling.

2. Why use an interceptor for JWT?

It prevents us from manually adding the JWT authorization header to every API request.

req.clone({
  setHeaders: {
    Authorization: `Bearer ${token}`
  }
});

3. Why is clone() required?

HttpRequest objects are immutable. Therefore, we create a modified request using clone() rather than changing the original request.

4. What does next(req) do?

It passes the request to the next interceptor in the chain, or ultimately to the HTTP backend.

5. Can we have multiple interceptors?

Yes.

withInterceptors([
  authInterceptor,
  loggingInterceptor,
  errorInterceptor
])

Each interceptor should ideally handle a specific cross-cutting concern.

6. Can an interceptor modify the response?

Yes. Because next(req) returns an Observable, RxJS operators can process the response:

return next(req).pipe(
  tap(response => {
    console.log(response);
  })
);

7. Where should JWT validation happen?

The interceptor may attach the JWT:

Angular Interceptor
       ↓
Authorization: Bearer <token>
       ↓
ASP.NET Core Web API
       ↓
JWT Authentication Middleware
       ↓
Validate Token

The ASP.NET Core Web API must validate the JWT and enforce authorization. The Angular interceptor only helps send the token.

Interview one-line answer

An Angular HTTP interceptor is middleware around HttpClient that allows centralized processing of outgoing HTTP requests and incoming HTTP responses, commonly for authentication headers, logging, error handling, and other cross-cutting concerns.

← Back to Articles