We use an HttpInterceptor to add bearer token to our API requests. This is for an internal application that requires all users to be authenticated and authorized, even for the home page.
I have the code as shown below. On first render, for the first API call to render the first set of data, the token is null
, and the token received first from the getAccessToken()
subscription is also null
. Everything is fine for subsequent API calls, even if the user presses F5
.
We are running angular-auth-oidc-client@15.0.5 and angular 16.1. This became a problem when we upgraded from the previous version of angular-auth-oidc-client that did not rely on rxjs subscribe
import { Injectable, Injector } from "@angular/core";
import { Observable } from "rxjs";
import {
HttpInterceptor,
HttpEvent,
HttpRequest,
HttpHandler,
} from "@angular/common/http";
import { OidcSecurityService } from "angular-auth-oidc-client";
import { Router } from "@angular/router";
import { TokenValidatorService } from "../../services/token-validator.service";
export const UseOidcInterceptorHeader = "X-Oidc-Interceptor";
@Injectable()
export class TokenInterceptor implements HttpInterceptor {
token = null;
constructor(
public oidcSecurityService: OidcSecurityService,
private tokenValidatorService: TokenValidatorService,
private router: Router
) {
this.oidcSecurityService.getAccessToken().subscribe((token) => {
this.token = token
});
}
intercept(
req: HttpRequest<unknown>,
next: HttpHandler
): Observable<HttpEvent<unknown>> {
// we do not intercept authentication calls to "companyAuthServer"
if (req.url.includes("companyAuthServer")) {
return next.handle(req);
}
if (this.token !== null) {
if (!this.tokenValidatorService.checkUserIsInAdGroup(this.token)) {
this.router.navigate(["/oidc/unauthorized"]);
}
const requestForward = req.clone({
setHeaders: { Authorization: "Bearer " + this.token },
});
return next.handle(requestForward);
} else {
console.error(
"OidcSecurityService, token not fetched: NO authorize header!",
req.urlWithParams
);
}
return next.handle(req);
}
}
When you subscribe in your constructor and store the async result to this.token
, of course this.token
will not be set yet, the first time intercept()
is called.
Instead of subscribing in the constructor, you can return an observable that waits for the async call to getAccessToken()
, then switches to handling the next request:
intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
if (req.url.includes("companyAuthServer")) {
return next.handle(req);
}
return this.oidcSecurityService.getAccessToken().pipe(
tap(token => {
if(!token || !this.tokenValidatorService.checkUserIsInAdGroup(token)) {
this.router.navigate(["/oidc/unauthorized"]);
}
}),
map(token => req.clone({ setHeaders: { Authorization: `Bearer ${token}` }}),
switchMap(authRequest => next.handle(authRequest))
);
}
Note: this isn't tested, so may need some adjustments, but hopefully gives you the idea.