Angular4应用中adal-angular4隐式流acquireToken首次调用报错问题
Hey there, I’ve run into this exact issue with adal-angular4 in Angular 4 apps using implicit flow against Azure AD APIs. Let’s break down why this happens and walk through the fixes:
Why This Happens
In implicit flow, after the first login, adal needs to asynchronously store the returned id_token and access_token in its cache. If you call acquireToken before this caching completes, you’ll get a login_required error. Subsequent calls work because the tokens are already cached by then.
Fix 1: Fix Route Guard Async Handling
Most often, the problem comes from route guards trying to fetch tokens synchronously before adal is ready. Update your guard to return an Observable and wait for the token fetch to complete:
import { Injectable } from '@angular/core'; import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Observable, of } from '@angular/router'; import { AdalService } from 'adal-angular4'; import { map, catchError, switchMap } from 'rxjs/operators'; @Injectable() export class ApiAuthGuard implements CanActivate { constructor(private adalService: AdalService) {} canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> { const apiResource = route.data['apiResource'] as string; return this.adalService.acquireToken(apiResource).pipe( map(token => { // Store the token for your HTTP interceptor to use sessionStorage.setItem('api-access-token', token); return true; }), catchError(error => { // Handle silent login first, then fall back to interactive login if needed if (error.error === 'login_required') { return this.adalService.acquireTokenSilent(apiResource).pipe( map(silentToken => { sessionStorage.setItem('api-access-token', silentToken); return true; }), catchError(() => { this.adalService.login(); return of(false); }) ); } this.adalService.login(); return of(false); }) ); } }
Fix 2: Pre-Fetch Tokens on App Initialization
Kick off a token fetch when your app loads to ensure the cache is populated before any API calls happen. Add this to your AppComponent:
import { Component, OnInit } from '@angular/core'; import { AdalService } from 'adal-angular4'; import { catchError } from 'rxjs/operators'; @Component({ selector: 'app-root', templateUrl: './app.component.html' }) export class AppComponent implements OnInit { private apiResource = 'https://your-azure-api-resource-url'; constructor(private adalService: AdalService) {} ngOnInit(): void { // Try silent token acquisition first, fall back to interactive if needed this.adalService.acquireTokenSilent(this.apiResource).pipe( catchError(() => this.adalService.acquireToken(this.apiResource)) ).subscribe( token => sessionStorage.setItem('api-access-token', token), () => this.adalService.login() ); } }
Fix 3: Add Retry Logic to Your HTTP Interceptor
If an API call fires before the token is ready, let your interceptor retry the request automatically:
import { Injectable } from '@angular/core'; import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'; import { Observable } from 'rxjs'; import { AdalService } from 'adal-angular4'; import { switchMap, retry, catchError } from 'rxjs/operators'; import { throwError } from 'rxjs'; @Injectable() export class AuthInterceptor implements HttpInterceptor { private apiResource = 'https://your-azure-api-resource-url'; constructor(private adalService: AdalService) {} intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return this.adalService.acquireToken(this.apiResource).pipe( switchMap(token => { const authenticatedRequest = request.clone({ setHeaders: { Authorization: `Bearer ${token}` } }); return next.handle(authenticatedRequest); }), retry(1), // Retry once if the first call fails due to missing token catchError(error => { if (error.status === 401) { this.adalService.login(); return throwError(() => new Error('Session expired - please log in again')); } return throwError(() => error); }) ); } }
Fix 4: Verify Adal Configuration
Double-check your adal config to ensure the cache is persisted properly (localStorage is more reliable than sessionStorage for initial loads):
import { AdalConfig } from 'adal-angular4'; export const adalConfig: AdalConfig = { tenant: 'your-azure-tenant-id', clientId: 'your-app-client-id', redirectUri: window.location.origin + '/', postLogoutRedirectUri: window.location.origin + '/', cacheLocation: 'localStorage', // Critical for persistent token caching navigateToLoginRequestUrl: false };
One last thing: Make sure your Azure AD app registration has the correct API permissions granted (and admin consent if needed), and that the resource value you’re passing to acquireToken matches the API’s audience URI exactly.
内容的提问来源于stack exchange,提问作者nesh_s

