How to wait Promise in constructor - javascript

I have Angular Service which make some http requests, but I need to get headers for those requests from Promise. Here how it works right now, I convert my promise to Observable:
export class SomeService {
constructor(private http: HttpClient, private auth: AuthenticationService) {}
getInfo(id: string): Observable<any> {
return this.auth.getToken().pipe(mergeMap((token: any) => {
let httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
})
}
return this.http.get(`${this.serverURL}/info`, httpOptions);
}))
}
}
getToken() {
return from(this.storage.get(TOKEN_KEY));
}
But obviously I have like 20-50 requests and it's not too good to fetch auth token with every request.
I want to fetch my token once and use it for all request. Also I have other header which comes from Promise I need to use in my request. So, how can I get my async headers once (probably in constructor) in this case?

First off consider if optimizing this code is actually needed. Optimizing for performance is often only useful in parts of code which are run very frequently. When you say you do some 20 to 50 requests it does not sound like it's used a lot (other parts of your app are probably a lot more cpu intensive).
That being said: if you still want to solve this you could indeed fetch the token in your constructor.
export class SomeService {
// We store the observable here for later use
private getTokenObservable: Observable<string>;
constructor(private http: HttpClient, private auth: AuthenticationService) {
// Retrieve the token now and store the observable
this.getTokenObservable = getToken();
}
getInfo(id: string): Observable<any> {
// Zip the two observables together
return zip(
// Re use the previously stored observable
this.getTokenObservable,
// Also request the last login
this.auth.getLastLogin()
).pipe(mergeMap((result) => {
const token = result[0];
const lastLogin = result[1];
let httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
})
}
return this.http.get(`${this.serverURL}/info`, httpOptions);
}))
}
}
getToken() {
return from(this.storage.get(TOKEN_KEY));
}
This works because you can subscribe multiple times to the same observable. So we request and store the getToken observable only once and then re use it for each request.
Also note how we use the zip operator provided by rxjs. This allows us to merge two observables together so we can handle the result of both observables in a single function.

You can write separate service for getting the token and call the service only once. For next time when you need token check if already you have token value in variable of service, so can skip http request and simply return token to requester.

private _tokenObsevable = new BehaviorSubject<string>(null);
constructor(...) {
this.auth.getToken().subscribe(token => this._tokenObservable.next(token))
}
getInfo(...) {
this._tokenObservable.pipe(
switchMap(token => // rest the same
)
optional you can create getter like
get tokenObservable() {
return this._tokenObservable.pipe(filter(val => !!val))
}
in this case you get only non null values, but also if token won't appeare you'll get stuck

In this case you can create a utility function in a file and can import it everywhere you need this token, or can create a service if this token is coming form a server call and then can inject it in all the places you need this token.
This token can also be saved in a const files and imported in the place wherever required.

Related

How to add Firebase JWT to an Angular HTTP Request?

I have my own HTTP Service which provides GET, POST, PUT and DELETE methods and enables me to send the JWT from my Firebase user in the HTTP request header.
Now I'm able to get my user object from a redux store, and I can retrieve the JWT via the getIdToken() method on this user object. Unfortunately this method provides me with a Promise<string>.
Is there any way, I can build my Http-Options Object with this JWT-Header, before actually sending the request via Angular's HttpClient?
My Code:
async delete<T = any>(appendix: string): Observable<T> {
const options: any = await this.httpOptions();
return this.http.delete<T>(this.url + appendix, options);
}
private async httpOptions(): Promise<{ headers: { uid: string } }> {
const user: User = this.authQuery.getValue();
const token: string = await user.getIdToken().catch();
return {
headers: {uid: token},
};
}

Asynchronously authenticate before request

So I have an API and I am trying to authenticate by hitting an endpoint with credentials (this part I've gotten working) and then save the received token and use it in all subsequent requests.
My problem is that the authenticate() method is asynchronous, but all other request methods like get() need the token from the authenticate() method. So I can't just export my get() method because the export is synchronous (as I've read) and it will be exported before authentication happens. I could authenticate for every request but that seems wasteful and inefficient.
I am not sure what to do here, I'm using axios, what's the proper way of doing this?
Edit
I'll be a bit more specific here. I have created an axios instance:
var instance = axios.create({
baseURL: `http://${config.server}:${config.port}`,
timeout: 1000,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
})
I want to get the authentication token, and include it in the instance header:
async function authenticate(instance) {
const result = await instance.post(
'/session',
{
'username': config.username,
'password': config.password
}
)
instance['X-Token'] = result.data.token
}
Now I want to export that instance to be used in other files
You can use async/await. This is semi-pseudocode:
async function doStuff() {
const result = await axios.authenticate();
const token = // extract token from whatever format of result is
const data = await axios.get(/* supply token to get */);
}
Alternatively, you can just use then:
function doStuff(token) {
const token = // extract token from whatever format of result is
const data = await axios.get(/* supply token to get */);
}
axios.authenticate().then(result => {
const token = // extract token from whatever format of result is
doStuff(token);
}
With Axios you have the ability to set default values for all requests.
So for just a single axios instance you can do...
async function authenticate(instance) {
const result = await instance.post(
'/session',
{
'username': config.username,
'password': config.password
}
)
instance.defaults.headers.common['X-Token'] = result.data.token;
}
Alternatively, (which it sounds like you want to do) you can add it for the default Axios export. Then all requests will automatically have the header.
async function authenticate(endpoint, username, password) {
const res = await axios.post(`${endpoint}/session`, { username, password });
axios.defaults.headers.common['X-Token'] = result.data.token;
}
Then you don't have to worry about passing around an instance between all parts of your app and can just use import * as axios from 'axios' and have the header set.
Axios also provides and extremely helpful function called interceptors which you can use to inspect a request prior to making it. You can use to check to make sure that the request has the auth header and if it doesn't you can perform that logic. I came up with this and it seems to work well!
axios.interceptors.request.use(async (config) => {
// request intercepted, check (1) the header is missing and (2) that the intercepted request isn't authorizing
if (!config.headers.common['X-Token'] && config.authorizing !== true) {
const { endpoint, username, password } = appConfig;
// make a request to get your token AND pass our custom config
const result = await axios.post(`${endpoint}/session`, { username, password }, { authorizing: true });
// update axios to include the header for future requests
axios.defaults.headers.common['X-Token'] = result.data.token;
}
return config;
});
Two things that you'll want to note -- not only do I check for the existence of your X-token header I also check for a new authorization value in the config. You want to check for that config value, because we are going to use it as a flag to let the interceptor know if it should skip a request. If you don't do this, the authorization request will trigger another authorization request and infinite loop.

Refreshing token for parallel HTTP requests using HttpInterceptor

I'm working on an Ionic app and trying to cash in the refresh token when a user gets a 401 response on an HTTP request. I found a few examples floating around online and was able to get this one (https://www.intertech.com/Blog/angular-4-tutorial-handling-refresh-token-with-new-httpinterceptor/) working with the exception of multiple requests coming in at once.
The problem I'm having is the first call in the series of calls invokes the refresh token and retries successfully, while the other ones never get retried. If I take the .filter and .take off the subject return for requests where a refresh is already in progress, the calls do get retried but without the new token. I'm pretty new when it comes to observables and subjects so I'm not really sure what the problem could be.
requests
this.myService.getData().subscribe(response => {this.data = response.data;});
this.myService.getMoreData().subscribe(response => {this.moreData = response.data;});
this.myService.getEvenMoreData().subscribe(response => {this.evenMoreData = response.data;});
interceptor
#Injectable()
export class HttpInterceptor implements HttpInterceptor {
isRefreshingToken: boolean = false;
tokenSubject = new BehaviorSubject<string>(null);
tokenService: tokenService;
constructor(private authService: AuthService, private injector: Injector) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
return this.authService.getUser().flatMap(user => {
request = this.addToken(request, next, user.accessToken);
return next
.handle(request)
.catch(error => {
if (error instanceof HttpErrorResponse) {
switch ((<HttpErrorResponse>error).status) {
case 401:
return this.handle401(request, next, user);
}
} else {
return Observable.throw(error);
};
})
});
}
addToken(request: HttpRequest<any>, next: HttpHandler, accessToken: string): HttpRequest<any> {
return request.clone({ setHeaders: { Authorization: 'Bearer ' + accessToken }})
}
handle401(request: HttpRequest<any>, next: HttpHandler, user: any) {
if (!this.isRefreshingToken) {
this.isRefreshingToken = true;
this.tokenSubject.next(null);
this.tokenService = this.injector.get(tokenService);
return this.tokenService.refresh(user.refreshToken)
.switchMap(refreshResponse => {
if (refreshResponse) {
this.authService.setUser(refreshResponse.id_token, refreshResponse.access_token, refreshResponse.refresh_token);
this.tokenSubject.next(refreshResponse.accessToken);
return next.handle(this.addToken(request, next, refreshResponse.access_token));
}
else {
//no token came back. probably should just log user out.
}
})
.finally(() => {
this.isRefreshingToken = false;
});
}
else {
return this.tokenSubject
.filter(token => token != null)
.take(1)
.switchMap(token => {
return next.handle(this.addToken(request, next, token));
});
}
}
}
It looks to me like you didn't have the right token:
You had:
this.tokenSubject.next(refreshResponse.accessToken);
Should be:
this.tokenSubject.next(refreshResponse.access_token);
I actually ended up solving this by moving the subject to my auth service and doing a next in the setUser method. Then in the else statement in my 401 method, I returned the subject from a new method on my auth service and that fixed it. I still needed the take(1) but was able to get rid of the filter since I ended up not using a BehaviorSubject.
I faced a similar issue in the past. For some unknown reason (at least to me), when I intercept the 401, I make the refresh and retry, but retry operation goes cancelled.
Nevertheless, I realised that I can read the JWT expiration on client-side, so I tricked the system by saving the token expiration time. I then made routing events (say onViewWillEnter) check the expiration and, if token expired, refresh it.
This mechanism is totally transparent to the user, ensures that auth token nor refresh token expire if the user stays too long without performing HTTP requests and, most importantly, reduces latencies as you never get a 401 response (which, in your scenario, translates to three requests).
One simple way to achieve this is by means of a guard:
canActivate(route: ActivatedRouteSnapshot,
state: RouterStateSnapshot) {
if (this.refreshTokenService.isExpired) {
this.tokenEvent_.next();
return false;
} else {
this.refreshTokenService.refresh();
}
where refreshTokenService is a utility service that has the tokens and a method for performing refresh via HTTP. tokenEvent is a rxjs/Subject: it is subscribed in guard constructor and each time a new event comes, it redirects to login page.
Adding this guard on every route ensures that the token is always non-expired.

Angular - Waiting for a token value from Session Storage

I am using Angular 2.0 to write a custom HTTP Provider which allows me to attach a bearer token to each HTTP Request to the API. This is essentially what ADAL JS does, but I can not use that library in my application.
The problem is this - before I make a call to my HTTP API, I need to wait unit both tokens are present in session storage. Once I have both, I can then send the request.
My HTTP Client class looks like this:
import { Injectable } from '#angular/core';
import { Http, Headers } from '#angular/http';
function getWindow(): any {
return window;
}
#Injectable()
export class HttpClient {
private _window: Window;
constructor(private http: Http) { }
createAuthorizationHeader(headers: Headers) {
let keys = sessionStorage.getItem("adal.token.keys").split("|");
let key1 = keys[0]; // web
let key2 = keys[1]; // api
if (!key1 || !key2) {
// I NEED TO WAIT FOR BOTH KEYS!
}
let accessToken = sessionStorage.getItem("adal.access.token.key" + key2);
headers.append('Authorization', 'Bearer ' + accessToken);
}
get(url) {
let headers = new Headers();
this.createAuthorizationHeader(headers);
return this.http.get(url, {
headers: headers
});
}
post(url, data) {
let headers = new Headers();
this.createAuthorizationHeader(headers);
return this.http.post(url, data, {
headers: headers
});
}
}
I would like to avoid using a Timer (setTimeout) for obvious reasons. I would like to use an ES6 promise type of thing.

Using JWT in my angular 2 application and storing it in localStorage. However, how do I handle for when that item doesn't exist?

I've created a token-service.ts that calls my back end auth API which returns a JWT. I store this JWT in localstorage as shown here in my getToken():
getToken() {
this.http.post('myAuthEndpoint', { credentials })
.subscribe((res) => {
const token = res.headers.get('Authorization')
localStorage.setItem('id_token', token);
});
}
In my app.component.ts, I am calling the getToken() in my ngOnInit method.
However, here's what I have in my app.component.html:
<navigation-top></navigation-top>
<router-outlet></router-outlet>
And this is where I have an issue - In my NavigationTop component, I am calling my getNavigationTop() from my top-navigation.service.ts to populate the links and stuff. The API call I make in getNavigationTop() requires the auth token that I get in my getToken(), but its null on init.
How can I handle this case? Right now it works when I reload the page after the first load, because then I can get the value from localStorage:
getNavigationTop(): Observable<any> {
let headers = new Headers({ 'Authorization': localStorage.getItem('token') });
let options = new RequestOptions({ headers: headers });
let data = this.http
.get('my url', options)
.map((res: Response) => {
return res.json().navTop;
})
.catch(this.handleError);
return data;
}
Thanks
Move those to a service and add it into your main module as a provider.
Then do this below. You can then inject the service into any component and call them at will.
#Injectable()
export class TokenService {
getToken() {
this.http.post('myAuthEndpoint', { credentials })
.subscribe((res) => {
const token = res.headers.get('Authorization')
localStorage.setItem('id_token', token);
this.getNavigationTop(token);
});
}
getNavigationTop(token?): Observable<any> {
let headers = new Headers({ 'Authorization': token ? token: localStorage.getItem('token') });
and then in component:
import {TokenService} from '..../';
...
#Component
export class NavigationTop {
constructor(private tokenService: TokenService) {
}
ngOnInit() {
this.tokenService.getToken();
}
Now if you run getNavigationTop again later, it will check for token argument(optional) first but if none exist, try localstorage instead.

Categories

Resources