7

In the code below I have posted two examples of what the code is returning in the console. For an interceptor, I'm supposed to be returning an observable. I have converted the local storage promise into an observable using switchmap. I am still getting back null with this method. My observable is wrapped around the function, so I should be getting a value other than null. Thanks!

Interceptor.js

import { fromPromise } from 'rxjs/observable/fromPromise';

accessToken: string; 
emptyToken: any;


    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

//example

        this.storage.get('token').then((val) => {
             this.accessToken = val
            return console.log(this.accessToken, ' this is returning null value for token')
        })

//trying to return actual header
          return fromPromise(this.storage.get('token')).switchMap(access => {

            this.accessToken = access
            console.log(this.accessToken, ' this is returning null value for token')
            const authReq = req.clone({
                setHeaders: {
                Authorization: this.accessToken
                }
                });

                return next.handle(authReq)
           })
}
}

I've added the updated the code below, please ignore the code above and still getting the same result. As one of the answers said below, they are correct in all of their assumptions. It's boiled down to getting the token inside of the login observable. The issue is not with the interceptor, that code works fine. I need to somehow get my async value without returning null. The this.storage.ready() method gives me the same result as well. The auth service is being called after my interceptor, so therefore I don't have any token generated yet. How would I go about calling my auth service first?

login(userName: string, password: string, route: string = null): any {
     this.storage.set('tokens', 'able to retrieve this token value inside interceptor');

    this.logout(false);

    this.doLogin(userName, password)
      .subscribe(response => {

        let token:string = JSON.stringify(response.access_token);
        this.storage.set('token', token)

      }

      }

interceptor.ts

get(url: string, options?: RequestOptionsArgs): Observable<Response> {
  return Observable.fromPromise(
    this.getRequestOptionArgs(options)
  ).mergeMap((options) => {
    return super.get(url, options)
  })
  }

private getRequestOptionArgs(options?: RequestOptionsArgs) {
    return this.storage.get('token').then((token) => {
      console.log(token, 'token for the get')
      if (options == null) {
        options = new RequestOptions();
      }

      if (options.headers == null) {
        options.headers = new Headers();
      }

      if (token !== null) {
        options.headers.append('Authorization', 'Bearer ' + token);
      }
      options.headers.append('Content-Type', 'application/json');

      return options;
    });
  }
userlkjsflkdsvm
  • 961
  • 1
  • 21
  • 53

4 Answers4

2

I think the problem is that your StorageService is returning null, rather than this being a problem with the Interceptor.

The storage service is using a Promise, meaning it will only emit one value. If you are getting null then I guess you don't yet have the token in storage.

Where do you get the token from in the first place? The server? In which case this first request won't have the token.

But... you've said that when you access localStorage directly (synchronously) then you get a value and it all works. So I'm assuming you already have a token in storage.

Looking at the docs for Ionic Storage it looks like there is another possibility. The storage has a ready method, so perhaps the store isn't ready.

You could try something like this (untested) where you first wait for the ready promise to resolve before trying to get the token:

import { Injectable } from '@angular/core';
import { HttpErrorResponse, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';

import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/observable/fromPromise';

@Injectable()
export class Interceptor implements HttpInterceptor {

    constructor (private storage: StorageService) {}

    intercept (request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        // chain the ready promise with the get token promise
        return Observable.fromPromise(this.storage.ready().then(this.storage.get('token')))
            .mergeMap((token: string) => {

                const authReq = request.clone({
                    setHeaders: {
                        Authorization: token
                    }
                });

                return next.handle(authReq);

            });
    }
}

Just a guess...

Daniel Crisp
  • 1,945
  • 1
  • 15
  • 23
0

The issue is happening because this.storage.get('token').then() is a promise and the promise .then() is only setting the this.token AFTER the call has been made already.

You need to add a method to the constructor that will fetch the token from localStorage and store it on the interceptor. This will fire off when you create an instance of the interceptor.

Interceptor

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

const tokenStorageKey = 'token'
const tokenType = 'Bearer'

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
    private token: any;
    constructor (private storage: StorageService) {
        this.getToken();
    }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        let contentTypeSet = req.headers.set('Content-Type', 'application/json');
        let authHeaderSet = contentTypeSet.set('Authorization', `${tokenType} ${this.token}`)

        const authReq = req.clone({
            headers: authHeaderSet
        });

            return next.handle(authReq);
        }
    }

    private getToken(): void{
        this.storage.get(tokenStorageKey).then((token: any) => {
            this.token = token;
        });
    }
}

You can also add a publish event that lets all subscribed methods know the token has been fetched.

Wesley Coetzee
  • 4,768
  • 3
  • 27
  • 45
  • Thanks for the answer! I'm looking at your answer here -https://stackoverflow.com/questions/45250609/ionic-3-set-default-requests-header-from-ionic-storage/45298747#45298747 and I've implemented it like this. However, I'm getting the same result when adding the get token in the constructor. – userlkjsflkdsvm Sep 12 '17 at 15:21
  • are you sure the token is available in localstorage? – Wesley Coetzee Sep 13 '17 at 04:28
  • When I call token I'm able to get the value in the console. When I use this.storage.set('token' , token) my assumption is since the line above has the token value which is token, then placing token inside the storage.set saved it to the local storage. Also I saw your github code, I'm having to remove the post interceptor for the time being because the body isn't being JSON stringified, it's one string, if that makes sense. But that's besides the point. – userlkjsflkdsvm Sep 13 '17 at 04:49
0

Fortunately, I already had to tackle this situation, while developing a JWT solution (even before native interceptors existed).

The behavior you actually want is this:

  1. Send request

    1.1. if token doesn't exist in local storage, append "Authorization Bearer"

    1.2. else read token from local storage

  2. Get a response with a successful "Authorization" token

  3. save this token to local storage

Please also keep in mind that all interceptors are called for every request. Also, when registering them, order matters! The flow is like this:

Request created -> interceptorA, interceptorB -> sent to server -> received from server -> interceptorA, interceptorB -> subscribe function so this is what happens in your this.doLogin(...).subscribe(...)

So, what I have done and would suggest to you, is having two types of interceptors. One AuthorizationAppendingInterceptor that will append the Authorization Bearer, if no token exists for the current user.

The second one should be sth. like AuthorizationReceivedInterceptor, which will filter the event to be an instance of HttpRespone and look for the header Authorization, which will be sent by the server on a successful login. Only then, in the receiving interceptor, you can write the token to the local storage.


Some code:

AuthorizationAppendingInterceptor

Given that this.authService is your instance that handles the token stuff

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  // Get the auth header from the service.
  const headers: HttpHeaders = this.authService.appendAuthorizationHeader(req.headers);

  // Clone the request to add the new header.
  const authReq: HttpRequest<any> = req.clone({ headers: headers });

  // Pass on the cloned request instead of the original request.
  return next.handle(authReq);
}

AuthorizationReceivingInterceptor

Given that AuthService.AUTHORIZATION_HEADER = 'Authorization' and this.authService is your instance that handles the token stuff.

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next
  .handle(req)
  .do(event => {
        if (event instanceof HttpResponse) {
          const response: HttpResponse<any> = event as HttpResponse<any>;
          if (!response.headers || !response.headers.has(AuthService.AUTHORIZATION_HEADER)) {
            return;
          }

          this.authService.setToken(response.headers.get(AuthService.AUTHORIZATION_HEADER));
        }
      }
  );

}


Just for the sake of completeness, here goes the appendAuthorizationHeader method from the authService

public appendAuthorizationHeader(headers: HttpHeaders): HttpHeaders {
  if (!this.token || !this.token.canAppendToHeaders()) { return; }
  return headers.append(AuthService.AUTHORIZATION_HEADER, this.token.getTokenString());
}

With this code implemented, your authentication mechanism should work without any more adjustments in your application logic and also supports automatic re-login, since once the token is invalid, the AuthorizationAppendingInterceptor will automatically trigger and after that, the AuthorizationReceivingInterceptor will handle the updating of your local storage token.

Daniel Bunte
  • 150
  • 2
0

why don't you do stuff like this for your service:

createAThing(userId :string, timestamp :string, reason: string): Promise<Appointment> {

    let bodySearchParam = new URLSearchParams();
    bodySearchParam.append('userId', userId );
    bodySearchParam.append('timestamp', this.datetotimestamp(timestamp).toString());
    bodySearchParam.append('reason', reason);
    let body = bodySearchParam.toString();

    console.log(body) // check if my body is ok
    return this.http.post(this.AppointmentUrlWrite,body)
                  .toPromise()
                  .then(response =>
                    console.log(response.json().data)
                  )
                  .catch(this.handleError);
  }

in my humble opinion, it would work better than your method

moonshine
  • 799
  • 2
  • 11
  • 24