Ionic 4 refresh token

this.storage.get(‘refresh_token’).then( ) returns a promise so it is returning a promise object instead of the token

I am working on a ionic 4 angular project where I am using jwt for authentication. Using the http interceptor I was able to send the access token as the authorization headers bearer token. Because jwt expires very quickly so now I need to refresh the token. I am using a python and flask backend where after successful login the server response contains both the access i.e. jwt and refresh token. In my python server to the refresh the token I need to do a post request to the refresh end point with the refresh token as as the authorization headers bearer token. In response the server send me the access token.

The steps that I have followed is :

  1. After successful log in I am saving the access token and the refresh token in the ionic storage.
  2. Sending the access token with each request using the angular http interceptor.
  3. If there is an error the server response with the appropriate error response code then I was sending a refresh token request adding the refresh token as the bearer token authorization header
  4. Then from the server response saving the access token in the ionic storage again
  5. Adding the new access token with each request.

Now the problem that I am facing is when I send the refresh token request instead of sending the refresh token as the authorization header the request is sending a “Bearer [object Promise]”.

The problem is in my authservice and getAccessTokenUsingRefreshToken( ) function which returns an observable.
as this.storage.get(‘refresh_token’).then( ) returns a promise so it is returning a promise object instead of the token.
The code of my authservice is as follows:

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpErrorResponse, HttpRequest } from '@angular/common/http';
import { BehaviorSubject, throwError, Observable, from } from 'rxjs';
import { Platform, AlertController } from '@ionic/angular';
import { Storage } from '@ionic/storage';
import { JwtHelperService } from '@auth0/angular-jwt';
import { tap, catchError, mergeMap } from 'rxjs/operators';
import { User } from '../models/user.model';

@Injectable({
  providedIn: 'root'
})
export class AuthenticationService {
  constructor(
    private http: HttpClient,
    private helper: JwtHelperService,
    private storage: Storage,
    private platform: Platform,
    private alertController: AlertController) {
    // this.platform.ready().then(() => {
    //   this.checkToken();
    // });
  }
  url = 'http://localhost:5000'; 
  ACCESS_TOKEN = 'access_token';
  REFRESH_TOKEN = 'refresh_token';
  user = null;
  token;
  // refreshToken;
  authenticationState = new BehaviorSubject(false);





  register(user: User): Observable<User> {
    // if (user.id === null)
    console.log(user);
    return this.http.post<User>(`${this.url}/register`, user)
      .pipe(
        tap(res => {
          this.storage.set(this.ACCESS_TOKEN, res[this.ACCESS_TOKEN]);
          this.storage.set(this.REFRESH_TOKEN, res[this.REFRESH_TOKEN]);
          this.user = this.helper.decodeToken(res[this.ACCESS_TOKEN]);
          // console.log(this.storage.get(this.REFRESH_TOKEN));
          this.authenticationState.next(true);
        }),
      );
  }


  login(data) {
    return this.http.post(`${this.url}/auth`, data)
      .pipe(
        tap(res => {

          this.storage.set(this.ACCESS_TOKEN, res[this.ACCESS_TOKEN]);
          this.storage.set(this.REFRESH_TOKEN, res[this.REFRESH_TOKEN]);
          this.user = this.helper.decodeToken(res[this.ACCESS_TOKEN]);
          // this.storage.get(this.REFRESH_TOKEN);
          // console.log(this.storage.get(this.ACCESS_TOKEN));
          // console.log(this.getRefreshToken());
          this.authenticationState.next(true);
        }),
      );
  }

  logout() {
    this.storage.remove(this.ACCESS_TOKEN).then(() => {
      this.authenticationState.next(false);
    });
    this.storage.remove(this.REFRESH_TOKEN);
  }


  private addToken(token: any) {
    if (token) {
      const httpOptions = {
        headers: new HttpHeaders({
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${token}`
        })
      };
      return httpOptions;
    }
  }

 getAccessTokenUsingRefreshToken() {
    const refreshToken = this.storage.get('refresh_token').then((result) => {
      return result;
    });

    const httpOptions = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${refreshToken}`
      })
    };
    return this.http.post<any>(`${this.url}/token/refresh`, 'body', httpOptions ).pipe(tap(tokens => {
      console.log(tokens['access_token']);
      console.log(tokens);
      this.storage.set(this.ACCESS_TOKEN, tokens[this.ACCESS_TOKEN]);
      console.log(this.storage.get('access_token'));
    }));

  }


  checkToken(): Promise<any> {
    return this.storage.get(this.ACCESS_TOKEN).then(token => {
      if (token) {
        this.token = token;

        if (!this.helper.isTokenExpired(this.token)) {
          this.user = this.helper.decodeToken(this.token);
          this.authenticationState.next(true);
        } else {
          this.storage.remove(this.ACCESS_TOKEN);
          this.storage.remove(this.REFRESH_TOKEN);
        }
      }
    });
  }

  getToken() {
    return this.storage.get(this.ACCESS_TOKEN);
  }
  isAuthenticated() {
    return this.authenticationState.value;
  }


}



This is my http interceptor code

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { Observable, from, throwError, BehaviorSubject } from 'rxjs';
import { Storage } from '@ionic/storage';
// import { _throw } from 'rxjs/observable/throw';
import { catchError, mergeMap, switchMap, filter, take } from 'rxjs/operators';
import { AlertController } from '@ionic/angular';
import { AuthenticationService } from './authentication.service';


@Injectable({
  providedIn: 'root'
})
export class InterceptorService implements HttpInterceptor {
  private isRefreshing = false;
  private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);

  constructor(private storage: Storage, private alertCtrl: AlertController, private authenticationService: AuthenticationService) { }
  intercept(req: HttpRequest<any>, next: HttpHandler):
    Observable<HttpEvent<any>> {
    let promise = this.storage.get('access_token');

    return from(promise).pipe(mergeMap(token => {
      const clonedReq = this.addToken(req, token);
      return next.handle(clonedReq).pipe(catchError(error => {
        if (error instanceof HttpErrorResponse && error.status === 401) {
          // console.log('executed');
          console.log(req);
          return this.handle401Error(req, next);
        } else {
          return throwError(error.message);
        }
      })
      );
    }
    ));
  }

  // Adds the token to your headers if it exists
  private addToken(request: HttpRequest<any>, token: any) {
    if (token) {
      let clone: HttpRequest<any>;
      clone = request.clone({
        setHeaders: {
          Accept: `application/json`,
          'Content-Type': `application/json`,
          Authorization: `Bearer ${token}`
        }
      });
      return clone;
    }

    return request;
  }


  private handle401Error(request: HttpRequest<any>, next: HttpHandler) {
    if (!this.isRefreshing) {
      this.isRefreshing = true;
      this.refreshTokenSubject.next(null);

      return this.authenticationService.getAccessTokenUsingRefreshToken().pipe(
        switchMap((token: any) => {
          this.isRefreshing = false;
          console.log(token);
          console.log('executed');
          this.refreshTokenSubject.next(token.access_token);
          return next.handle(this.addToken(request, token.access_token));
        }));

    } else {
      return this.refreshTokenSubject.pipe(
        filter(token => token != null),
        take(1),
        switchMap(access_token => {
          return next.handle(this.addToken(request, access_token));
        }));
    }
  }


}

What if you made getAccessTokenUsingRefreshToken async then await the value of refreshToken?

async getAccessTokenUsingRefreshToken() {

    const refreshToken = await this.storage.get('refresh_token');
    console.log("refreshToken", refreshToken)
    console.log("typeof refreshToken", typeof refreshToken)
    const httpOptions = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${refreshToken}`
      })
    };
    return this.http.post<any>(`${this.url}/token/refresh`, 'body', httpOptions ).pipe(tap(tokens => {
      console.log(tokens['access_token']);
      console.log(tokens);
      this.storage.set(this.ACCESS_TOKEN, tokens[this.ACCESS_TOKEN]);
      console.log(this.storage.get('access_token'));
    }));

  }

@neilbo21 I have tried that already but it is not working. The problem is it creates a promise with an observable. If I do

return this.http.post<any>(`${this.url}/token/refresh`, 'body', httpOptions ).pipe(tap(tokens => {
      console.log(tokens['access_token']);
      console.log(tokens);
      this.storage.set(this.ACCESS_TOKEN, tokens[this.ACCESS_TOKEN]);
      console.log(this.storage.get('access_token'));
    }));

nothing in the console log. If I only use pipe without tap then I get this in the console “Observable {isScalar: false, source: Observable, operator: MapOperator}operator: MapOperator {project: ƒ, thisArg: undefined}source: Observableoperator: FilterOperator {predicate: ƒ, thisArg: undefined}source: Observable {isScalar: false, source: Observable, operator: MergeMapOperator}isScalar: false__proto: Object_isScalar: false__proto_: Object”

I have posted few weeks ago about the refresh token. It is still not working This is StackBlitz https://stackblitz.com/github/fazla043264/ionic4refreshtoken . This is my stackoverflow question for more details https://stackoverflow.com/questions/56336739/ionic-storage-get-returns-a-promise-bearer-object-promise-how-to-return-a-va/56337006?noredirect=1#comment99319001_56337006

your problem is in ‘getAccessTokenUsingRefreshToken()’ when save access_token
‘this.storage.set(this.ACCESS_TOKEN, tokens[this.ACCESS_TOKEN]);’

this ‘this.storage.set()’ return a promise, but if you not process this promises with ‘then’ or ‘async/await’, the incerteptor (you while saves new access_token in ‘getAccessTokenUsingRefreshToken’ ) try recovery the new data of acces_token, but not this save correctly before.

You try save correctly access_token in storage
‘this.storage.set(this.ACCESS_TOKEN, tokens[this.ACCESS_TOKEN]).then( bla bla bla);’
or
‘await this.storage.set(this.ACCESS_TOKEN, tokens[this.ACCESS_TOKEN]);’

Tagging you here just for reference @arvindhebbal

I have already answered it .

    getAccessTokenUsingRefreshToken(): Observable<string> {
    return from(this.storage.get('refresh_token')).pipe(
      switchMap(refreshToken => {
        const httpOptions = {
          headers: new HttpHeaders({
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${refreshToken}`
          })
        };
        return this.http.post<any>(`${this.url}/token/refresh`, {}, httpOptions);
      }),
      map(response => response.access_token),
      tap(accessToken => this.storage.set(this.ACCESS_TOKEN, accessToken))
    );
  }

for details https://stackoverflow.com/questions/56336739/ionic-storage-get-returns-a-promise-bearer-object-promise-how-to-return-a-va/56337006?noredirect=1#comment99319001_56337006

2 Likes