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
Post a Comment