I have the following code snippet:
@Injectable()
class RefreshTokenInterceptor implements HttpInterceptor {
private readonly renewTokens$: Observable<string | undefined> = defer(() =>
this.tokens.accessToken$.pipe(
take(1),
switchMap((accessToken: string | undefined) =>
accessToken
? this.auth.getAccessTokenSilently()
: this.auth.loginWithRedirect().pipe(switchMap(() => this.auth.getAccessTokenSilently()))
),
tap((accessToken: string) => this.tokens.renewAccessToken(accessToken)),
share()
)
);
constructor(private readonly auth: AuthService, private readonly tokens: TokenService) {}
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
return next.handle(request).pipe(
catchError((error: unknown) =>
error instanceof HttpErrorResponse && error.status === 401
? this.renewTokens$.pipe(
switchMap(
(accessToken: string | undefined) => next.handle(withBearerTokenInAuth(request, accessToken))
)
)
: throwError(() => error))
);
}
}
The part I'm curios about is renewTokens$ observable and the way it works.
Let's imagine the situation where our accessToken has expired and we did 3 parallel request which require authorization.
The results in network tab for the snippet above would be following:

The next things happened:
- All 3 requests failed with 401
- Request for refreshment of
accessTokenhas been fired - All 3 previously failed requests were retried with the new token and, as a result, succeeded.
It's not really clear for me why we have only one request for token(while it's a desired behaviour though). After a little debugging I see that factory function passed to defer is being invoked 3 times(because of 3 requests) and as I understand each time new shared observable is returned. How come that all of the requests wait for one same observable to emit value? I thought that each request is supposed to receive a separate observable returned from the factory function of defer(meaning that each unauthorized request should fire separate token request)
UPD
Implementation of TokenService:
@Injectable({
providedIn: 'root'
})
export class TokenService {
private readonly accessTokenSubj: BehaviorSubject<string | undefined> =
new BehaviorSubject<string | undefined>(undefined);
constructor(private readonly auth: AuthService) {
auth.isAuthenticated$.pipe(
switchMap(
(isAuthenticated: boolean) => isAuthenticated ? auth.getAccessTokenSilently() : of(undefined)
)
).subscribe((accessToken: string | undefined) => this.accessTokenSubj.next(accessToken))
}
renewAccessToken(accessToken: string) {
this.accessTokenSubj.next(accessToken);
}
get accessToken$(): Observable<string | undefined> {
return this.accessTokenSubj.asObservable();
}
}
AuthService is provided by @auth0/auth0-angular