API Protection + Permission Headers + Global Error Handling + Auto Logout in Angular

API Protection + Permission Headers + Global Error Handling + Auto Logout in Angular

You will get:

  • ✅ Attach Access Token automatically
  • ✅ Add optional Permission header (enterprise style)
  • ✅ Global API error handling
  • ✅ Auto logout when refresh fails
  • ✅ Centralized ApiService pattern
  • ✅ Works perfectly with Task B Refresh Token system

1️⃣ Add Permission Metadata to API Calls (Enterprise Pattern)


// api-permissions.ts
export const ApiPermissions = {
  UsersGetAll: 'USERS_VIEW',
  UsersCreate: 'USERS_CREATE',
  ReportsView: 'REPORTS_VIEW'
} as const;

export type ApiPermissionValue = typeof ApiPermissions[keyof typeof ApiPermissions];

2️⃣ Create Enterprise ApiService (Centralized)

Instead of calling HttpClient everywhere.


// api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { ApiPermissionValue } from './api-permissions';

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

  constructor(private http: HttpClient) {}

  get(url: string, permission?: ApiPermissionValue): Observable {
    return this.http.get(url, { headers: this.buildHeaders(permission) });
  }

  post(url: string, body: any, permission?: ApiPermissionValue): Observable {
    return this.http.post(url, body, { headers: this.buildHeaders(permission) });
  }

  put(url: string, body: any, permission?: ApiPermissionValue): Observable {
    return this.http.put(url, body, { headers: this.buildHeaders(permission) });
  }

  delete(url: string, permission?: ApiPermissionValue): Observable {
    return this.http.delete(url, { headers: this.buildHeaders(permission) });
  }

  private buildHeaders(permission?: ApiPermissionValue): HttpHeaders {
    let headers = new HttpHeaders();

    // Optional: permission header (for logging/auditing backend)
    if (permission) {
      headers = headers.set('X-Permission', permission);
    }

    return headers;
  }
}

3️⃣ Update AuthInterceptor to Add Token + Permission Header Support


// auth.interceptor.ts (Enterprise Final)
import { Injectable } from '@angular/core';
import {
  HttpInterceptor,
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpErrorResponse
} from '@angular/common/http';

import { Observable, BehaviorSubject, throwError } from 'rxjs';
import { catchError, filter, switchMap, take } from 'rxjs/operators';

import { TokenService } from './token.service';
import { AuthService } from './auth.service';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  private isRefreshing = false;
  private refreshTokenSubject = new BehaviorSubject(null);

  constructor(
    private tokenService: TokenService,
    private authService: AuthService
  ) {}

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

    // Skip auth endpoints
    if (req.url.includes('/login') || req.url.includes('/refresh-token')) {
      return next.handle(req);
    }

    const accessToken = this.tokenService.getAccessToken();
    const authReq = accessToken ? this.addToken(req, accessToken) : req;

    return next.handle(authReq).pipe(
      catchError((error: HttpErrorResponse) => {
        if (error.status === 401) {
          return this.handle401Error(authReq, next);
        }
        return throwError(() => error);
      })
    );
  }

  private addToken(request: HttpRequest, token: string) {
    return request.clone({
      setHeaders: { Authorization: `Bearer ${token}` }
    });
  }

  private handle401Error(request: HttpRequest, next: HttpHandler) {

    if (!this.isRefreshing) {

      this.isRefreshing = true;
      this.refreshTokenSubject.next(null);

      const refreshToken = this.tokenService.getRefreshToken();

      if (!refreshToken) {
        this.forceLogout();
        return throwError(() => new Error("Refresh token missing"));
      }

      return this.authService.refreshToken(refreshToken).pipe(
        switchMap((res) => {

          this.isRefreshing = false;

          this.tokenService.setTokens(res.accessToken, res.refreshToken);
          this.refreshTokenSubject.next(res.accessToken);

          return next.handle(this.addToken(request, res.accessToken));
        }),
        catchError(err => {
          this.isRefreshing = false;
          this.forceLogout();
          return throwError(() => err);
        })
      );
    }

    return this.refreshTokenSubject.pipe(
      filter(token => token !== null),
      take(1),
      switchMap(token => next.handle(this.addToken(request, token!)))
    );
  }

  private forceLogout() {
    this.tokenService.clearTokens();
    window.location.href = "/login";
  }
}

4️⃣ Global Error Handler (Enterprise)


// global-error.interceptor.ts
import { Injectable } from '@angular/core';
import {
  HttpInterceptor,
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpErrorResponse
} from '@angular/common/http';

import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable()
export class GlobalErrorInterceptor implements HttpInterceptor {

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

    return next.handle(req).pipe(
      catchError((error: HttpErrorResponse) => {

        // Example: show a common message for all errors
        if (error.status === 0) {
          console.error("Network error / CORS issue");
        }

        if (error.status === 500) {
          console.error("Server error");
        }

        return throwError(() => error);
      })
    );
  }
}

5️⃣ Register Multiple Interceptors (Correct Order)


// app.module.ts
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './core/auth/auth.interceptor';
import { GlobalErrorInterceptor } from './core/auth/global-error.interceptor';

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

6️⃣ Example Usage in Component (Enterprise Style)


// users.component.ts
import { Component, OnInit } from '@angular/core';
import { ApiService } from '../core/api/api.service';
import { ApiPermissions } from '../core/api/api-permissions';

@Component({
  selector: 'app-users',
  template: `
    

Users

{{ u.name }}
` }) export class UsersComponent implements OnInit { users: any[] = []; constructor(private api: ApiService) {} ngOnInit(): void { this.api.get('https://localhost:5001/api/users', ApiPermissions.UsersGetAll) .subscribe(res => this.users = res); } }

🔥 Super Enterprise Add-ons (BEST PRACTICE)

  • ✔ Add an AuthGuard (login protection) – prevent opening app without token
  • ✔ Add PermissionGuard (Task D) – prevent unauthorized page access
  • ✔ Add Backend Permission Validation – even if frontend blocks, backend must validate

✅ Interview Final Answers (Super Strong)

  • Q) How do you secure API calls in Angular?
    ✅ Using HttpInterceptor to attach access token automatically.
  • Q) How do you handle token expiry?
    ✅ If 401 occurs, interceptor refreshes token and retries original request.
  • Q) How do you handle global errors?
    ✅ Global error interceptor logs or shows toast message.
  • Q) How do you force logout?
    ✅ If refresh fails, clear tokens and redirect to login.

Comments

Popular posts from this blog

Debouncing & Throttling in RxJS: Optimizing API Calls and User Interactions

Promises in Angular

Comprehensive Guide to C# and .NET Core OOP Concepts and Language Features