or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

authentication-service.mdcomponents-module.mdevent-broadcasting.mdhttp-interceptor.mdindex.mdroute-protection.md
tile.json

http-interceptor.mddocs/

HTTP Token Interceptor

HTTP interceptor that automatically attaches authentication tokens to outgoing requests based on protected resource configuration, with support for different interaction types and scope mapping.

Capabilities

MsalInterceptor

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

MsalInterceptorConfiguration

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 { }

HTTP Method-Specific Protection

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

Dynamic Authentication Requests

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

Complex Resource Protection Patterns

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

Multi-Tenant and B2C Configuration

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

Error Handling and Fallbacks

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

Performance Optimization

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