HTTP interceptor that automatically attaches authentication tokens to outgoing requests based on protected resource configuration, with support for different interaction types and scope mapping.
HTTP interceptor that automatically acquires and attaches authentication tokens to HTTP requests based on configured protected resources and scopes.
/**
* HTTP interceptor for automatically attaching authentication tokens to requests
* Implements Angular's HttpInterceptor interface
*/
class MsalInterceptor implements HttpInterceptor {
/**
* Intercepts HTTP requests and adds authentication headers when required
* @param req - The outgoing HTTP request
* @param next - The next handler in the interceptor chain
* @returns Observable of HTTP events with potential authentication headers
*/
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>;
}Configuration object for defining which resources require authentication and what scopes to request.
/**
* Configuration object for MsalInterceptor
* Defines protected resources and authentication behavior
*/
interface MsalInterceptorConfiguration {
/** Required interaction type for token acquisition (popup or redirect) */
interactionType: InteractionType.Popup | InteractionType.Redirect;
/** Map of protected resources to their required scopes */
protectedResourceMap: Map<string, Array<string | ProtectedResourceScopes> | null>;
/** Optional authentication request configuration */
authRequest?: MsalInterceptorAuthRequest | ((msalService: MsalService, req: HttpRequest<unknown>, originalAuthRequest: MsalInterceptorAuthRequest) => MsalInterceptorAuthRequest);
}
/**
* Union type for interceptor authentication requests
* Excludes scopes property as they are determined by the protected resource map
*/
type MsalInterceptorAuthRequest = Omit<PopupRequest, "scopes"> | Omit<RedirectRequest, "scopes"> | Omit<SilentRequest, "scopes">;
/**
* Configuration for protected resource scopes with HTTP method specificity
*/
interface ProtectedResourceScopes {
/** HTTP method this scope configuration applies to */
httpMethod: string;
/** Required scopes for this resource and method, or null to unprotect */
scopes: Array<string> | null;
}Usage Examples:
import { NgModule } from "@angular/core";
import { HTTP_INTERCEPTORS } from "@angular/common/http";
import {
MsalInterceptor,
MsalInterceptorConfiguration,
MSAL_INTERCEPTOR_CONFIG,
ProtectedResourceScopes
} from "@azure/msal-angular";
import { InteractionType } from "@azure/msal-browser";
// Basic protected resource configuration
const interceptorConfig: MsalInterceptorConfiguration = {
interactionType: InteractionType.Redirect,
protectedResourceMap: new Map([
// Microsoft Graph API
["https://graph.microsoft.com/v1.0/me", ["user.read"]],
["https://graph.microsoft.com/v1.0/users", ["user.read.all"]],
// Custom API endpoints
["https://api.myapp.com/", ["api://myapp-id/access_as_user"]],
// Wildcard patterns
["https://api.contoso.com/*", ["https://api.contoso.com/.default"]],
// Unprotected resource (no token attached)
["https://public-api.example.com/", null]
])
};
@NgModule({
providers: [
{ provide: MSAL_INTERCEPTOR_CONFIG, useValue: interceptorConfig },
{ provide: HTTP_INTERCEPTORS, useClass: MsalInterceptor, multi: true }
]
})
export class AppModule { }Configure different scopes based on HTTP methods:
import { ProtectedResourceScopes } from "@azure/msal-angular";
const methodSpecificConfig: MsalInterceptorConfiguration = {
interactionType: InteractionType.Popup,
protectedResourceMap: new Map([
[
"https://api.myapp.com/users",
[
// GET requests need read permission
{ httpMethod: "GET", scopes: ["api://myapp/users.read"] },
// POST/PUT/DELETE need write permission
{ httpMethod: "POST", scopes: ["api://myapp/users.write"] },
{ httpMethod: "PUT", scopes: ["api://myapp/users.write"] },
{ httpMethod: "DELETE", scopes: ["api://myapp/users.write"] }
]
],
[
"https://api.myapp.com/admin",
[
// All methods need admin permission
{ httpMethod: "*", scopes: ["api://myapp/admin.access"] }
]
]
])
};Configure authentication requests dynamically based on the HTTP request:
import { HttpRequest } from "@angular/common/http";
import { MsalService } from "@azure/msal-angular";
const dynamicInterceptorConfig: MsalInterceptorConfiguration = {
interactionType: InteractionType.Redirect,
protectedResourceMap: new Map([
["https://graph.microsoft.com/", ["user.read"]]
]),
authRequest: (msalService: MsalService, req: HttpRequest<unknown>, originalAuthRequest) => {
// Add extra parameters based on request
if (req.url.includes('/admin/')) {
return {
...originalAuthRequest,
prompt: "consent",
extraQueryParameters: { "domain_hint": "organizations" }
};
}
// Add correlation ID for tracking
return {
...originalAuthRequest,
correlationId: crypto.randomUUID()
};
}
};Wildcard and Pattern Matching:
const patternConfig: MsalInterceptorConfiguration = {
interactionType: InteractionType.Popup,
protectedResourceMap: new Map([
// Exact URL match
["https://graph.microsoft.com/v1.0/me", ["user.read"]],
// Wildcard - protects all endpoints under this path
["https://api.myapp.com/*", ["api://myapp/access"]],
// Multiple wildcards for different environments
["https://*.myapp.com/api/*", ["api://myapp/access"]],
// Protocol-agnostic (matches both http and https)
["//api.internal.com/*", ["api://internal/access"]],
// Query parameter preservation
["https://search.api.com/query*", ["api://search/read"]]
])
};const multiTenantConfig: MsalInterceptorConfiguration = {
interactionType: InteractionType.Redirect,
protectedResourceMap: new Map([
// Different scopes for different tenants
["https://tenant1.api.com/", ["api://tenant1-id/access"]],
["https://tenant2.api.com/", ["api://tenant2-id/access"]],
// B2C protected resources
["https://b2c.api.com/", ["https://tenant.onmicrosoft.com/api/read"]]
]),
authRequest: (msalService, req, originalAuthRequest) => {
// Dynamic authority based on request
if (req.url.includes('b2c.api.com')) {
return {
...originalAuthRequest,
authority: "https://tenant.b2clogin.com/tenant.onmicrosoft.com/B2C_1_SignUpSignIn"
};
}
return originalAuthRequest;
}
};import { Component, OnInit } from "@angular/core";
import { HttpClient, HttpErrorResponse } from "@angular/common/http";
import { MsalService } from "@azure/msal-angular";
import { catchError, retry } from "rxjs/operators";
import { of } from "rxjs";
@Component({
selector: 'app-api-consumer',
template: `
<div *ngIf="data">{{ data | json }}</div>
<div *ngIf="error" class="error">{{ error }}</div>
`
})
export class ApiConsumerComponent implements OnInit {
data: any;
error: string | null = null;
constructor(
private http: HttpClient,
private authService: MsalService
) {}
ngOnInit() {
this.fetchProtectedData();
}
fetchProtectedData() {
this.http.get('https://graph.microsoft.com/v1.0/me')
.pipe(
retry(1), // Retry once if token acquisition fails
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
this.error = 'Authentication failed. Please log in again.';
// Optionally trigger re-authentication
this.authService.loginRedirect();
} else if (error.status === 403) {
this.error = 'Access denied. You do not have permission to access this resource.';
} else {
this.error = 'An error occurred while fetching data.';
}
return of(null);
})
)
.subscribe(data => {
if (data) {
this.data = data;
this.error = null;
}
});
}
}// Optimize for high-traffic applications
const optimizedConfig: MsalInterceptorConfiguration = {
interactionType: InteractionType.Redirect,
protectedResourceMap: new Map([
// Cache-friendly resource patterns
["https://api.myapp.com/", ["api://myapp/access"]]
]),
authRequest: {
// Enable token caching
forceRefresh: false,
// Set appropriate cache timeout
extraQueryParameters: {
"max_age": "3600" // 1 hour
}
}
};
// Exclude specific endpoints from interception for better performance
const selectiveConfig: MsalInterceptorConfiguration = {
interactionType: InteractionType.Popup,
protectedResourceMap: new Map([
// Only intercept specific API endpoints
["https://graph.microsoft.com/v1.0/", ["user.read"]],
// Explicitly exclude public endpoints (null means no token)
["https://api.myapp.com/public/", null],
["https://api.myapp.com/health", null],
["https://api.myapp.com/version", null]
])
};