Jibeee
Jibeee

Reputation: 852

Implement http request in interceptor angular2

I'm trying to handle 401 response from interceptor using httpClientModule. The authentication is based on JWT, with accessToken and refreshToken. If the accessToken is expired, I need to request the api to get a new one with the refreshToken. I would like to block the request when the a new token should be generated, get a new one, and then make the request with the new accessToken attached.

In this way, what is the best method to make http request from the interceptor ?

My interceptor :

@Injectable()

export class JwtService implements HttpInterceptor { 

 constructor(public inj: Injector){}

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


    if ( this.shouldGenerateNewAccessToken(req.url) ){

        const auth = this.inj.get(AuthService);
        auth.getNewAccessToken().then( token => {

            if (token){

                const headers = { 'Authorization' : token };
                const clone = req.clone( { setHeaders : headers } );
                return next.handle(clone);

            } else { return next.handle(req); }

        }, error => {

             return next.handle(req); 

        });
    }
    else {

        if (APP_CONFIG['accessToken']){

            const headers = { 'Authorization' : APP_CONFIG['accessToken'] };
            const clone = req.clone( { setHeaders : headers });
            return next.handle(clone);

        } else { 

             return next.handle(req);

        }

    }

}


shouldGenerateNewAccessToken(url : string) : Boolean {

    let lastupdate = APP_CONFIG['accessTokenTimestamp'];
    let now = new Date().getTime(); 

    // Let say the token expires after 5s
    if ((now - lastupdate) > 5000 && APP_CONFIG['refreshToken'] && url != APP_CONFIG['apiEndPont']+'getaccesstoken'){
        return true;
    }
    else 
        return false;
}

Auth logic

getNewAccessToken() : Promise<any>{

    return new Promise( (resolve, reject)=>{

        this.http.post(this.api+ 'getaccesstoken', JSON.stringify({refreshToken: APP_CONFIG['refreshToken'] }), { "headers" : this.headers } ).subscribe( data  => {
            let res : any = data;
            APP_CONFIG['accessToken'] = res.accessToken; 
            APP_CONFIG['accessTokenTimestamp'] = new Date().getTime();
            resolve(APP_CONFIG['accessToken']);

        }, err => {console.log('error'); reject(null); })
    });
}

getuserinfos(){

   return this.http.get(this.api+ 'getuserinfos', { "headers" : this.headers } ).subscribe( data  => {
      console.log('result getUserInfos =>', data);

  }, 
  ( err : HttpErrorResponse ) => { 

     if ( err.error instanceof Error ) { console.log('An error occurred requete login:', err.error.message); }
     else { 
         console.log('error => ', err)
    }

});

}

I get the following error when I call getUserInfos() and the token is expired :

error => TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.

Is this linked to this behavior ?

More rarely, an interceptor may choose to completely handle the request itself, and compose a new event stream instead of invoking next.handle(). This is acceptable behavior, but keep in mind further interceptors will be skipped entirely. It is also rare but valid for an interceptor to return multiple responses on the event stream for a single request. Source

Upvotes: 1

Views: 1313

Answers (1)

Jibeee
Jibeee

Reputation: 852

I finally managed it with a different implementation. Here is my interceptor :

@Injectable()

export class JwtService implements HttpInterceptor { 

constructor(public inj: Injector){}

private fixUrl(url: string) {
    if (url.indexOf('http://') >= 0 || url.indexOf('https://') >= 0)
        return url;
    else
        return APP_CONFIG['apiEndPoint'] + url;
}


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

    // Clone request
    var headers = {}
    if (APP_CONFIG['accessToken']){
         headers = { 'Authorization' : APP_CONFIG['accessToken'] };
    }   
    const cloneRequest = req.clone( { setHeaders : headers });


    return next.handle(cloneRequest).do(data => {

        if (data instanceof HttpResponse) {
          // Some logic 
        }

    })
    .catch((res)=> {

        if (res.status === 401 || res.status === 403) {

            if (APP_CONFIG['accessToken'])
            {    
                const auth = this.inj.get(AuthService);
                return auth.getUpdatedAccessToken().flatMap( token => {

                    // Clone the previous request
                    let clonedRequestRepeat = req.clone({
                        headers: req.headers.set('Authorization' ,  APP_CONFIG['accessToken'] ),
                        url: this.fixUrl(req.url)
                    });

                    // Request agin 
                    return next.handle(clonedRequestRepeat).do(event => {

                        if (event instanceof HttpResponse) {
                            console.log('Repeat response of server : ', event);
                        }
                    });

                });
            }else { return Observable.throw('Not authenticated'); }

        }
        else { // Not 401 
             return Observable.throw(res);
        }

    })

}


}

AuthService.ts

getUpdatedAccessToken()  : Observable<any>{

 return this.http.post(this.api+ 'getaccesstoken', JSON.stringify({refreshToken: APP_CONFIG['refreshToken'] }), { "headers" : this.headers } )
            .map((response: any) => {
                if (response.code == 0){
                    APP_CONFIG['accessToken'] = response.accessToken; 
                }
                return response
        })
}

Upvotes: 2

Related Questions