Reputation: 3
I have a "Token" service that returns an access-token. Then I have many other services that use this service to get an access-token before they can make an API call to the backend with the token included in the request header.
The problem is that many of these services are calling the Token service almost simultaneously, so before the first call to the token service has returned and has been "cached" the next call is fired...
Resulting into multiple calls to the backend and getting multiple tokens.
Can somebody please tell me how to stop the multiple calls to the backend. Or how to run the Token service before everything else and cache the result and only then allow the application/services to run/bootstrap.
I'am using angular 2.4.4
Any help/suggestion is appreciated
import {Injectable} from '@angular/core';
import {Http} from '@angular/http';
import 'rxjs/add/operator/toPromise';
export class Token{
private cachedTokenObject = {
'tokenKey':false,
'userName':"[email protected]",
'password':"1234",
'applicationId':"12344"
};
constructor(private _http: Http){}
getAccessToken():Promise<Object>{
if( this.cachedTokenObject.tokenKey ){
console.log("returning chached token");
return Promise.resolve( this.cachedTokenObject );
}else{
console.log("getting new token...");
const tokenHeaders = "username=" + this.cachedTokenObject.userName + "&password=" + this.cachedTokenObject.password +"&grant_type=password";
return this._http.post("https://someurl.com", tokenHeaders)
.toPromise()
.then( result => {
if( result.json().hasOwnProperty('access_token') ){
this.cachedTokenObject.tokenKey = result.json()['access_token'];
return this.cachedTokenObject;
}else{
console.log('"access_token" property not found in access object');
return {};
}
} )
.catch(error =>{
console.error('*** getAccessToken error ***', error);
Promise.reject(error);
});
}
}
}
import {Injectable} from '@angular/core';
import {Http} from '@angular/http';
import 'rxjs/add/operator/toPromise';
import { Headers, RequestOptions } from '@angular/http';
import {AccessTokenService} from './access-token.service';
@Injectable()
export class LanguageService{
private cachedLanguages:any;
constructor(private _http: Http, private _accessTokenService:AccessTokenService){}
getLanguages(){
return this._accessTokenService.getAccessToken().then( token => {
if(this.cachedLanguages){
console.log('returning cached languages', this.cachedLanguages);
return Promise.resolve(this.cachedLanguages);
}else{
let headers = new Headers({ 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token['tokenKey']});
let options = new RequestOptions({ headers: headers });
return this._http.get('https://someUrl/languages', options)
.toPromise()
.then(resp => {
this.cachedLanguages = JSON.parse( resp.json() );
return this.cachedLanguages;
})
.catch(error =>{
console.error('*** LanguageService error ***', error);
Promise.reject(error);
});
}
});
}
}
Upvotes: 0
Views: 1858
Reputation: 1779
I had a similar issue and I solved it using RxJs operators. Here is my solution:
private postRequest(): Observable<any> {
let data: URLSearchParams = new URLSearchParams();
let obs = this.http.postWithHeaders(
'token', data, { 'Content-Type': 'application/x-www-form-urlencoded' })
.map((response) => {
...
})
.catch((error) => {
...
});
return obs;
}
The postRequest is responsible for retrieving a refresh token. It's important to note that it returns an observable.
In the constructor I create an observable that will defer the postRequest and I share my observable:
this.source = Observable.defer(() => {
return this.postRequest();
}).share();
The share operator will share the subscription between multiple subscribers. Here is a usefull to better understand it: RxJs - getting started
I then created a method that executes the observable:
refreshToken(): Observable<any> {
return this.source
.do((data) => {
//extract the access token and save it to local storage
}, error => {
...
});
}
You can test the code by subscribing to the refreshToken method multiple times and then counting the number of requests the browser makes:
this.tokenRefreshService.refreshToken().subscribe(x=>{...})
this.tokenRefreshService.refreshToken().subscribe(x=>{...})
this.tokenRefreshService.refreshToken().subscribe(x=>{...})
Hope that makes sense.
Upvotes: 2
Reputation: 8911
Is there anything preventing you from storing the token in Local Storage? ie window.localStorage.setItem('token_name', token);
And then your Token Service could have a public function to retrieve the token, something like:
retrieveToken() {
return window.localStorage.getItem(tokenName);
}
If the token does not exist, then make a call to the backend. As for retrieving the token before application startup, there are numerous ways to handle this depending upon your architecture. For instance, upon Login, store the token in Local Storage prior to navigating to the next page. You could also setup Guards on your routes to verify a token exists in storage.
There is a nice library to handle auto-attaching your authentication header to Http requests called Angular-JWT. It also contains helper functions to check token expiration.
Hope that helps.
Upvotes: 0