Skip to main content
Angular HTTP Interceptor: Complete Guide to Requests, Responses, Tokens & Errors
0

Angular HTTP Interceptor: Complete Guide to Requests, Responses, Tokens & Errors

Learn Angular HTTP Interceptors with examples. Understand request and response handling, authentication tokens, headers, error handling, retry, and interceptor configuration.

Read in:

Angular HTTP Interceptor: Deep Dive into Request and Response Handling

Angular applications often communicate with backend APIs using HttpClient. As an application grows, you may need to perform the same operation for many API requests.

For example, you may need to add an authorization token to every request, add common headers, handle API errors globally, log requests, or retry a failed request. Instead of writing the same code inside every service, Angular provides HTTP Interceptors.

An interceptor sits in the HttpClient request and response pipeline. It can inspect, modify, or handle HTTP requests before they reach the backend and responses before they reach the component or service.

What is an Angular HTTP Interceptor?

An Angular HTTP interceptor is a piece of code that runs when an HTTP request passes through Angular's HttpClient.

It can work with both:

  • Outgoing HTTP requests
  • Incoming HTTP responses

For example, suppose your application has 50 API calls, and every API call needs an authorization token. Without an interceptor, you might have to add the token manually to every request.

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

This becomes difficult to maintain. With an interceptor, you can add the token automatically to every required request.

Component
   ↓
Service
   ↓
HttpClient
   ↓
Interceptor
   ↓
Backend API
   ↓
Interceptor
   ↓
Service
   ↓
Component

This gives us a central place to handle common HTTP-related operations.

Why Use an HTTP Interceptor?

There are several common reasons to use an interceptor in Angular.

1. Add Authorization Tokens

One of the most common use cases is adding an access token to API requests.

Authorization: Bearer <token>

Instead of adding this header manually in every service, the interceptor can add it automatically.

2. Add Common HTTP Headers

An application may need common headers such as:

Authorization
Content-Type
Accept
X-Client-Version

An interceptor can add these headers where required.

3. Handle Errors Globally

Instead of handling common errors in every API call, an interceptor can handle them in one place.

For example:

  • 401 - Unauthorized
  • 403 - Forbidden
  • 404 - Resource not found
  • 500 - Server error

A 401 response can also be used to redirect the user to the login page or start a token refresh flow.

4. Retry Failed Requests

An interceptor can retry certain failed requests. For example, a temporary network failure may be retried automatically.

However, retrying should be done carefully. You should not blindly retry every request, especially operations that change server data.

5. Logging

Interceptors can be useful for logging:

  • Request URL
  • HTTP method
  • Response status
  • Request duration

This can help during development and debugging.

6. Show and Hide a Loader

An interceptor can maintain a global loading indicator while HTTP requests are running.

For example:

Request starts
    ↓
Show loader
    ↓
API request
    ↓
Response/Error
    ↓
Hide loader

This avoids adding loader logic to every component.

How to Create an Interceptor in Angular?

Modern Angular applications can create functional interceptors using HttpInterceptorFn. You can generate one using the Angular CLI:

ng generate interceptor auth

Depending on your Angular version and project setup, Angular may generate a functional interceptor. A basic functional interceptor looks like this:

import { HttpInterceptorFn } from '@angular/common/http';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
  return next(req);
};

Here:

  • req: represents the outgoing HTTP request.
  • next: passes the request to the next interceptor or the backend.
  • next(req): continues the HTTP request pipeline.

If you do not call next(req), the request will not continue.

Basic Interceptor Syntax

The basic syntax is:

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

Think of next as the next step in the HTTP pipeline. You can modify the request before calling next() and use RxJS operators to handle the response.

How to Add an Authorization Token?

HTTP requests in Angular are immutable. This means you should not directly modify the existing request.

Instead, use the clone() method.

For example:

import { HttpInterceptorFn } from '@angular/common/http';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const token = localStorage.getItem('token');
  if (token) {
    req = req.clone({
      setHeaders: {
        Authorization: `Bearer ${token}`
      }
    });
  }
  return next(req);
};

Here, the interceptor gets the token from storage and creates a new request containing the authorization header. The original request is not modified.

Why Do We Use clone()?

This is an important concept when working with Angular interceptors. HttpRequest objects are immutable.

For example, this is not the correct approach:

req.headers.set('Authorization', `Bearer ${token}`);
Instead, create a cloned request:
const modifiedReq = req.clone({
  setHeaders: {
    Authorization: `Bearer ${token}`
  }
});

Then pass the cloned request to next():

return next(modifiedReq);

This makes the request modification predictable and avoids changing the original request object.

Registering the Interceptor

Creating an interceptor is not enough. Angular also needs to know that the interceptor should be used. For a standalone Angular application, you can register it when configuring the application.

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]))
  ]
};

After registration, requests made through Angular's HttpClient will pass through the interceptor.

Handling the Response

An interceptor can also inspect the response.

RxJS operators such as tap() can be used for logging or other side effects.

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({
      next: (event) => {
        console.log('Response:', event);
      }
    })
  );
};

The interceptor receives HTTP events, not only the final response. Therefore, if you need to work specifically with the final HttpResponse, you can check the event type.

import { HttpInterceptorFn, HttpResponse } from '@angular/common/http';
import { tap } from 'rxjs';
export const loggingInterceptor: HttpInterceptorFn = (req, next) => {
  return next(req).pipe(
    tap(event => {
      if (event instanceof HttpResponse) {
        console.log('Status:', event.status);
      }
    })
  );
};

Global Error Handling

Interceptors are also commonly used for handling HTTP errors. The RxJS catchError() operator can be used for this.

import { HttpInterceptorFn } from '@angular/common/http';
import { catchError, throwError } from 'rxjs';
export const errorInterceptor: HttpInterceptorFn = (req, next) => {
  return next(req).pipe(
    catchError(error => {
      if (error.status === 401) {
        console.log('User is unauthorized');
      }
      if (error.status === 500) {
        console.log('Internal server error');
      }
      return throwError(() => error);
    })
  );
};

It is important to rethrow the error when the current interceptor is not supposed to completely handle it. Otherwise, the service or component may not receive the error.

Retrying Failed Requests

Angular interceptors can also work with RxJS retry operators.

For example:

import { HttpInterceptorFn } from '@angular/common/http';
import { retry } from 'rxjs';
export const retryInterceptor: HttpInterceptorFn = (req, next) => {
  return next(req).pipe(
    retry(2)
  );
};

This attempts the request again when it fails. However, be careful when using retry globally.

For example, automatically retrying a POST request could potentially create duplicate operations if the server processed the original request, but the response was lost.

For this reason, retry logic should normally be limited to suitable failures and request types.

Multiple Interceptors

Angular applications can have multiple interceptors.

For example:

HTTP Request
     ↓
Auth Interceptor
     ↓
Logging Interceptor
     ↓
Error Interceptor
     ↓
Backend
     ↓
Error Interceptor
     ↓
Logging Interceptor
     ↓
Auth Interceptor
     ↓
HTTP Response

You can register multiple interceptors:

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

The order can matter because interceptors form a chain.

Each interceptor can perform work before passing the request forward and can also process the response coming back.

Important: Do Not Add Tokens to Every Request

Although adding an authorization token globally is common, you may not want to send it to every URL.

For example, you might have public endpoints:

/api/login
/api/register
/api/public/articles

You can skip authentication for selected URLs.

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  if (
    req.url.includes('/login') ||
    req.url.includes('/register')
  ) {
    return next(req);
  }
  const token = localStorage.getItem('token');
  if (token) {
    req = req.clone({
      setHeaders: {
        Authorization: `Bearer ${token}`
      }
    });
  }
  return next(req);
};

In a larger application, it is usually better to use a clear URL or API configuration strategy instead of scattering includes() checks throughout the interceptor.

Interceptor vs Service

A common question is: why not put all this logic inside a service? Services should generally focus on application-specific API operations.

For example:

getUsers() {
  return this.http.get('/api/users');
}

An interceptor can handle concerns that apply across many requests.

For example:

Service

:- Get users

Interceptor

:- Add token
:- Log request
:- Handle common errors
:- Track request

This separation keeps the services cleaner and reduces duplicate code.

Common Use Cases

Some common real-world interceptor use cases are:

Use Case

Example

Authentication

Add access token

Headers

Add common headers

Error handling

Handle 401/403/500

Logging

Log requests and responses

Retry

Retry temporary failures

Loader

Show global loading indicator

Monitoring

Track API performance

Token refresh

Get a new access token

Request modification

Change URL or headers

📂 Categories

🏷️ Tags

Frequently Asked Questions

Does an Angular interceptor run for every HTTP request?

An interceptor runs for requests made through Angular's configured HttpClient. You can also add conditions inside the interceptor when certain APIs should be excluded from the interceptor logic.

Where should authentication tokens be stored in an Angular application?

The storage approach depends on the application's security design. Tokens are commonly managed through an authentication service, and the interceptor reads the current token when preparing an API request.

What happens if an interceptor does not call next()?

The request will not continue through the interceptor chain or reach the backend. next() is responsible for passing the request to the next interceptor or, when there are no more interceptors, to the HTTP backend.

Version History 4 updates
  1. Sep 6, 2026

    Updated: title, description, content, categories, tags, featuredImage, images, status, faqs, seriesName, seriesOrder, wordCount, readingTimeMinutes, slug, rejectionReason

  2. Sep 6, 2026

    Updated: title, description, content, categories, tags, featuredImage, images, status, faqs, seriesName, seriesOrder, wordCount, readingTimeMinutes, slug, rejectionReason

  3. Sep 6, 2026

    Updated: title, description, content, categories, tags, featuredImage, images, status, faqs, seriesName, seriesOrder, wordCount, readingTimeMinutes, slug, rejectionReason

  4. Sep 6, 2026

    Updated: title, description, content, categories, tags, featuredImage, images, status, faqs, seriesName, seriesOrder, wordCount, readingTimeMinutes, slug, rejectionReason

Discussion