Centralizing Cross‑Cutting Concerns with AngularJS $http Interceptors
Learn how AngularJS $http interceptors centralize authentication headers, error handling, and logging to reduce duplication and improve maintainability.
02 Apr 2026, 03:23 UTC

The Problem: Redundant Request Logic
In large AngularJS applications, developers often repeat the same logic across many controllers or services—attaching a JWT to every request, redirecting on 401 responses, or logging API calls. Manually adding these steps to each $http call is error‑prone and creates a maintenance burden when the API changes.
The $http interceptor provides a centralized pipeline that every request and response must pass through, separating cross‑cutting concerns from business logic.
The Smallest Suitable Design
An interceptor is a factory that returns an object with up to four methods: request, requestError, response, and responseError. For a basic authentication and error‑handling solution you need only one factory registered during the module’s config phase.
Implementation Example
app.factory('authInterceptor', function($q) { return { request: function(config) { // Add Authorization header to all requests var token = localStorage.getItem('auth_token'); if (token) { config.headers.Authorization = 'Bearer ' + token; } return config; }, responseError: function(rejection) { // Centralized handling for expired sessions if (rejection.status === 401) { window.location.href = '/login'; } return $q.reject(rejection); } };}); Registration
Interceptors must be pushed onto $httpProvider.interceptors inside a .config() block. Because $httpProvider is only available during configuration, you reference the factory by name.
app.config(function($httpProvider) { $httpProvider.interceptors.push('authInterceptor');}); Trust and Data Boundaries
Interceptors sit at the boundary between the client app and the external API, making them ideal for enforcing security and data integrity.
- Outgoing trust: The
requestinterceptor is the final gate. It reads the token from secure storage (e.g.,localStorageor a cookie) and applies it consistently, preventing individual developers from forgetting the header. - Incoming trust: The
responseandresponseErrorinterceptors filter incoming data. You can transform raw API error messages into user‑friendly notifications before they reach a controller.
Operational Checks and Verification
Verify interceptor behavior using the browser’s Network tab rather than relying only on console logs.
- Header check: Trigger an API call. In Developer Tools, inspect the request headers and confirm that
Authorizationcontains the expected token. - Error flow: Change a request URL to a non‑existent endpoint or use a tool to return 401. Verify that the
responseErrorlogic redirects to the login page. - Execution order: Remember that request interceptors run in the order they were added, while response interceptors run in reverse order (like a stack).
Failure Modes and Limitations
Infinite Loop Risk
A common failure occurs when a responseError interceptor attempts to recover by making another $http call (e.g., refreshing a token). If that refresh also fails with 401, the interceptor triggers itself again, creating an infinite loop that can crash the tab. Fix: Add a guard condition or a custom header (e.g., config.skipAuthRefresh = true) to the refresh request so the interceptor ignores it.
Performance Bottlenecks
Because every AJAX request passes through the interceptor chain, any synchronous, heavy work (such as large JSON parsing or DOM manipulation) blocks the request and adds latency to every API call. Keep interceptor logic lightweight and defer heavy processing to a service or web worker.
When to Change This Design
The interceptor pattern works best for truly global concerns. Move logic out of interceptors into specific services when:
- Conditional logic: Only a small subset of requests need a particular header. Adding complex
if/elseblocks to the request interceptor makes the code brittle. - Unique error handling: An endpoint requires a recovery flow that differs from the global 401/500 logic. Handle that error in the
.catch()of the specific$httppromise instead.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.