I want to refresh the token before the custom http request if it is expired. I try my code when I'm sure that the token is expired but it gives the following console result:
Token refresh is required app.js:1:92855
updateToken() method inside app.js:1:93301
tokenNotExpired?: false app.js:1:92674
Token refresh is required app.js:1:92855
updateToken() method inside app.js:1:93301
tokenNotExpired?: false app.js:1:92674
Token refresh is required app.js:1:92855
updateToken() method inside app.js:1:93301
tokenNotExpired?: false app.js:1:92674
Token refresh is required app.js:1:92855
updateToken() method inside app.js:1:93301
tokenNotExpired?: false app.js:1:92674
............ lots of the same sentences and finally exception:
EXCEPTION: Uncaught (in promise): Error: Error in :0:0 caused by: too much recursion
k@http://localhost/xxx/node_modules/zone.js/dist/zone.min.js:1:11750
............
As I understand during refreshing the token it goes into an infinite loop. I've tested the updateToken() method somewhere else with a button and it works fine.
What am I doing wrong?
custom http service
import { Injectable } from '@angular/core';
import { Http, XHRBackend, RequestOptions, Request, RequestOptionsArgs, Response, Headers } from '@angular/http';
import { tokenNotExpired } from "angular2-jwt";
import { Observable } from "rxjs/Observable";
@Injectable()
export class HttpService extends Http {
constructor (backend: XHRBackend, options: RequestOptions) {
let token = localStorage.getItem('access_token'); // your custom token getter function here
options.headers.set('Authorization', `Bearer ${token}`);
super(backend, options);
}
request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> {
let token = localStorage.getItem('access_token');
if (typeof url === 'string') { // meaning we have to add the token to the options, not in url
if (!options) {
// let's make option object
options = {headers: new Headers()};
}
options.headers.set('Authorization', `Bearer ${token}`);
} else { // we have to add the token to the url object
url.headers.set('Authorization', `Bearer ${token}`);
}
console.log("tokenNotExpired?: " + tokenNotExpired('access_token'));
if(tokenNotExpired('access_token')){ // if token is NOT expired
return super.request(url, options).catch(this.catchAuthError(this));
}else{ // if token is expired
console.log("Token refresh is required");
return this.updateToken()
.flatMap((result: boolean) => {
console.log("updateToken result");
console.log(result);
if (result) {
return super.request(url, options).catch(this.catchAuthError(this));
} else {
return Observable.throw(new Error('Can\'t refresh the token'));
}
});
}
}
updateToken(): Observable<any> {
console.log("updateToken() method inside");
let body: string = 'refresh_token=' + localStorage.getItem('refresh_token') + '&client_id=' + localStorage.getItem('resource') + '&grant_type=refresh_token';
return super.post(
localStorage.getItem("authUrl"),
body,
new RequestOptions({headers: new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' })})
)
.map((response: Response) => {
let returnedBody: any = response.json();
console.log("post returnedBody");
console.log(returnedBody);
if (typeof returnedBody.access_token !== 'undefined'){
localStorage.setItem('access_token', returnedBody.access_token);
localStorage.setItem('refresh_token', returnedBody.refresh_token);
console.log("Token Refreshed: refreshed access_token");
console.log(localStorage.getItem('access_token'));
return true;
}
else {
return false;
}
});
}
private catchAuthError (self: HttpService) {
return (res: Response) => {
console.log(res);
return Observable.throw(res);
};
}
}
app module
@NgModule({
imports: [ .......... ],
declarations: [ ....... ],
providers: [
{
provide: HttpService,
useFactory: (backend: XHRBackend, options: RequestOptions) => {
return new HttpService(backend, options);
},
deps: [XHRBackend, RequestOptions]
}
],
bootstrap: [ Application ]
})
Inside your updateToken
method you are calling super.post
which would be equivalent to Http.prototype.post.apply(this...)
, and super.post
will call internally this.request()
.
The this
context being your custom HttpService
, it ends up in a recusrsive call of the HttpService
request
method. You should call super.request
instead :
return super.request(
new Request({
method: RequestMethod.Post,
url: localStorage.getItem("authUrl"),
body,
headers: new Headers({
'Content-Type': 'application/x-www-form-urlencoded'
})
})
)
.map((response: Response) => {
let returnedBody: any = response.json();
console.log("post returnedBody");
console.log(returnedBody);
if (typeof returnedBody.access_token !== 'undefined') {
localStorage.setItem('access_token', returnedBody.access_token);
localStorage.setItem('refresh_token', returnedBody.refresh_token);
console.log("Token Refreshed: refreshed access_token");
console.log(localStorage.getItem('access_token'));
return true;
} else {
return false;
}
});
Also note that it might not be the best idea to create a custom Http service.
But maybe you could create a service that gets http injected, because you probably won't need to be authenticated just to fetch some simple static data from an ajax call.
That would also avoid the problem you encountered with recursive call stack exceeded.
@Injectable()
export class MyAuhtHttpService{
constructor(private http:Http){}
}