9

I have a HTTP interceptor and before every request I check if the access token is expired, if it is, I subscribe to a http.post call from my service and then subscribe to it and when I get a new access token I call next.handle(request) like this:

        this.auth.refreshAccessToken().subscribe((token: string) => {
          this.auth.newAccessToken = token;
          request = request.clone({
            setHeaders: {
              Authorization: `Bearer ${token}`
            }
          });
          return next.handle(request);
        });

The issue is then it is throwing TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.

Which makes me think I'm making that http.post call wrong right there.

EDIT 1: I haven't had the chance to test this thoroughly but so far it seems that everything works. I had a console.log before returning the whole map but it didn't fire, however, everything else worked and I update the currentUser everywhere/permissions everytime I get a new access token and that DID happen, so for all intents and purposes it seems to work, here's the updated code:

          mergeMap(token => {
            this.auth.newAccessToken = token;
            request = request.clone({
              setHeaders: {
                Authorization: `Bearer ${token}`
              }
            });
            return next.handle(request);
          })
SebastianG
  • 8,563
  • 8
  • 47
  • 111
  • Try adding `return` before refreshAccessToken call like: `return this.auth.refreshAccessToken...` – artsnr Apr 16 '19 at 15:16
  • 1
    You can’t use subscribe here. You need to instead return the observable using pipe() and an RxJS operator such as map() or switchMap() – Alexander Staroselsky Apr 16 '19 at 15:18
  • @AlexanderStaroselsky I ended up doing it with mergeMap and I have updated my code, everything seems to work, if you want to post it as an answer I'm happy to mark it as the correct one. I did endup putting the return before the whole thing but I tried that before using mergemap and the compiler would scream at me so I'd say the mergeMap was def. the helpful/right answer, sorry @artsnr! – SebastianG Apr 16 '19 at 16:18

1 Answers1

25

You would not subscribe() inside the interceptor, instead you would return an Observable<HttpEvent<any>>. This can be done by utilizing RxJS pipeable operators ('rxjs/operators') such as tap (for side effects like setting newAccessToken) and switchMap or mergeMap in combination with pipe(), returning an observable of type Observable<HttpEvent<any>> to satisfy the HttpInterceptor interface:

import { Injectable } from '@angular/core';
import {
  HttpEvent, HttpInterceptor, HttpHandler, HttpRequest
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { switchMap, tap } from 'rxjs/operators';
import { AuthService } from './auth.service';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private auth: AuthService) { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return this.auth.refreshAccessToken().pipe(
      tap(token => this.auth.newAccessToken = token), // side effect to set token property on auth service
      switchMap(token => { // use transformation operator that maps to an Observable<T>
        const newRequest = request.clone({
          setHeaders: {
            Authorization: `Bearer ${token}`
          }
        });

        return next.handle(newRequest);
      })
    );
  }
}

Here is an example in action. Please check out the logs to see key information being logged out.

Hopefully that helps!

Alexander Staroselsky
  • 37,209
  • 15
  • 79
  • 91