Emiliano S.
Emiliano S.

Reputation: 309

How to retrieve data from an Observable and use it in an Interceptor?

I'm building the front-end of an application in Angular 8. This application uses an OAuth 2 implementation to manage authentication (password grant) so any HTTP request (with the exception of ones to the token endpoint) needs to have on its header a valid access_token.

To provide said token I've made an Angular interceptor that retrieve the token from another service and then attach it to the intercepted HTTP request. The token retrieval method doesn't give directly the token but an observable which eventually resolves to a valid token, I made this choice because the access token may not be instantly available, if the token is expired the application needs to refresh it with an HTTP call and then the refreshed token can be passed to the HTTP interceptor.

The problem which I encounter is that despite my many attempts the interceptor doesn't wait for the token to be retrieved so at the end the interceptor is skipped and the HTTP request is made without any token attached.

This is the code of my interceptor, retrieveValidToken is the Observable which returns the token.

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { FacadeService } from './facade.service';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class HttpInterceptorService implements HttpInterceptor {

  constructor(private facadeService: FacadeService) { }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (req.url.includes('localhost:3000') && !req.url.endsWith('token')) {
      this.facadeService.retrieveValidToken()
        .subscribe(
          (res) => {
            const clone = req.clone({ setHeaders: { Authorization: `Bearer ${res}` } });
            return next.handle(clone);
          },
          (err) => {
            const clone = req.clone({ setHeaders: { Authorization: `Bearer ` } });
            return next.handle(clone);
          }
        );
    } else {
    return next.handle(req);
    }
  }
}

Upvotes: 2

Views: 171

Answers (2)

Alex Vovchuk
Alex Vovchuk

Reputation: 2926

Observables are asynchronous. The code outside the subscribe method will not wait for the code inside.

You should return observable by itself, not only result inside its subscription:

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (req.url.includes('localhost:3000') && !req.url.endsWith('token')) {
      return this.facadeService.retrieveValidToken()
        .subscribe(
          res => {
            const clone = req.clone({ setHeaders: { Authorization: `Bearer ${res}` } });
            return next.handle(clone);
          }
        );
    } else {
    return next.handle(req);
    }
  }

Something similar:

How use async service into angular httpClient interceptor

Upvotes: 2

Ivan Tkachyshyn
Ivan Tkachyshyn

Reputation: 451

The problem is that 'intercept' method should return observable immediately, so instead of subscribing to 'this.facadeService.retrieveValidToken()' use the following code:

return this.facadeService.retrieveValidToken().pipe( mergeMap(token => next.handle(req.clone({ setHeaders: { Authorization: 'Bearer ${token}' })) ) )

Upvotes: 0

Related Questions