Handling expired cookie in Angular 2 - javascript

I am experimenting with Angular 2 and trying to use document.cookie as my access-token storage.
What I have done is I set a cookie with an accesstoken & expiration of 30 mins. Now the application will set the cookie on load so subsequent api process handle pretty well till 30 mins. My problem is i want to check if cookies is expired and then fetch new accesstoken set it to the cookie and call respective method.
Here's my code
import { Injectable } from '#angular/core';
import { Http, Response, Headers } from '#angular/http';
import { Observable, Operator} from 'rxjs/Rx';
import { AccessToken } from './access-token.service';
#Injectable()
export class DataService {
constructor(
private _accessToken : AccessToken,
private _http: Http
) {}
request(url:string){
var token = this._accessToken.readCookie();
return this._http.get(url,{
headers : new Headers({
"Authorization" : "Bearer " + token
})
})
.map(data => data.json().result)
.catch(this._handleError)
}
fetchData(dataurl:string){
var token = this._accessToken.readCookie();
if(!token){
this._accessToken.getToken()
.subscribe(
response => {
this._accessToken.setCookie(response);
}
)
// I want to call this.request() after the accesstoken has been set to the document.cookie
} else {
return this.request(dataurl)
}
}
getAllProducts(){
return this.fetchData('https://localhost/api/products');
}
private _handleError (error: Response) {
console.error(error);
return Observable.throw(error.json().error || 'Server error');
}
}
The this._accessToken.getToken() method make a http.post request to an endpoint and gets an accesstoken.
The this._accessToken.setCookie() method then sets a document.cookie with the accesstoken and expiration time of 30 mins.
I'm not sure how i go about sequencing the fetchData() method incase of cookie expiration so that it first gets accesstoken sets it to cookie then only calls the request method.
Thanks

just move the call inside the subscribe(...) callback
if(!token){
this._accessToken.getToken()
.subscribe(
response => {
this._accessToken.setCookie(response);
// I want to call this.request() after the accesstoken has been set to the document.cookie
}
)
} else {

Related

How to wait Promise in constructor

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.

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.

Angular2 Http Call not firing

Context :
Following several tutorials, I am testing authentication with Angular2 and JWT. I come with a component and a service :
app.component.ts
user.service.ts
App component (and template) contains the subscription to an observable that shows the user logged in status. The Observable item is kept in the user service, and changes (fine) when user logs in and out.
The authentication token is written in "localStorage" as "auth_token". It contains a validity value (time) that should force the user to login again after a time.
What I'd like to do is to CHECK the token validity on app init. First, I tried to do it from the user.service CONSTRUCTOR, then (fail), I tried to do it from ngOnInit in the app.component, then (fail again), I tried to do it on event call (click on a button) from the app component, but fails again!
Some shortened code :
//app.component.html
//...
<a md-button class="app-icon-button" aria-label="checklogin" (click)="checkLogin()">
<md-icon svgIcon="check"></md-icon>
</a>
//...
//app.component.ts
//...
checkLogin(){
console.log('CHECK LOGIN FUNCTION');
let token = localStorage.getItem('auth_token');
if(token){
console.log('TOKEN FOUND');
this.userService.checkToken(token);
}else{
console.log('NO TOKEN FOUND');
}
}
//...
//user.service.ts
//...
checkToken(token){
console.log('CHECK TOKEN FUNCTION');
console.log('TOKEN : '+token);
let headers = new Headers();
headers.append('Content-Type','application/json');
return this.http
.post(
'/url/script.php',
JSON.stringify(token),
{ headers }
)
.map(res => res.json())
.map((res) => {
console.log('SCRIPT RESULT : ');
if(res.valid){
console.log('TOKEN IS VALID');
return true;
}else{
console.log('TOKEN NOT VALID');
return false;
}
});
}
//...
I did skip the observable part, and subscription.
Problem :
The problem actually is that the app NEVER CALLS the script!
When I do click on the "checkLogin" button (when token exists),
console shows 'CHECK LOGIN FUNCTION',
console shows 'TOKEN FOUND',
console shows 'CHECK TOKEN FUNCTION',
console shows 'TOKEN : '****************************** (token),
But it never shows 'SCRIPT RESULT',
and when using firebug to check if the http call is done, there is NO CALL to the script.php. Looks like the this.http part is just ignored...
Thanks for reading/help
Service starts working when subscription used only when consumer subscribe to output result, using .subscribe method.
You need: this.userService.checkToken(token).subscribe()
Your checkToken() method is returning an Observable that you need to subsrcibe to. An observable will never to execute unless it's subscribed to.
checkLogin(){
console.log('CHECK LOGIN FUNCTION');
let token = localStorage.getItem('auth_token');
if(token){
console.log('TOKEN FOUND');
this.userService.checkToken(token).subscribe(result => {
console.log(result);
}),
error => {
console.log(error);
});
} else {
console.log('NO TOKEN FOUND');
}
}
Ajax call's which use Observables will work only if you have an subscriber.
So you need to subscribe to that Observable. It is an Angular 2 feature. When you don't subscribe the Observable, it will never make that call.
And also you don't need to return anything from the subscriber, because you actually can't return anything.
this.userService.checkToken(token).subscribe((res) => {
console.log('SCRIPT RESULT : ');
if(res.valid) {
console.log('TOKEN IS VALID');
} else {
console.log('TOKEN NOT VALID');
}
});
checkToken(token){
console.log('CHECK TOKEN FUNCTION');
console.log('TOKEN : '+token);
let headers = new Headers();
headers.append('Content-Type','application/json');
return this.http
.post(
'/url/script.php',
JSON.stringify(token),
{ headers }
)
.map(res => res.json());
}
Have You tried using Postman and try to call function you need?
Also, why do You need to validate a token if angular2-jwt can do this for You?
You can do just like this:
install angular2-jwt with npm.
Include in app.module.ts:
import { AUTH_PROVIDERS } from 'angular2-jwt';
add to providers:
providers: [
AUTH_PROVIDERS,
],
and for example auth.service.ts looks like this:
import { Injectable, Inject } from '#angular/core';
import { Http, Response, Headers, RequestOptions, RequestMethod } from '#angular/http';
import { Router } from '#angular/router';
import { Observable } from 'rxjs/Observable';
import { Configuration } from '../../app.config';
import { RegisterViewModel } from '../../model/viewModel/registerViewModel';
import { LoginViewModel } from '../../model/viewModel/loginViewModel';
import { tokenNotExpired, AuthHttp } from 'angular2-jwt';
#Injectable()
export class AuthService {
private actionUrl: string;
constructor(private _http: Http, private _config: Configuration, private _router: Router, private _authHttp: AuthHttp){
this.actionUrl = _config.apiUrl;
}
register(user: RegisterViewModel){
let headers = new Headers({ 'Content-Type': 'application/json' });
//Admin in this system can only register users. that is why auth
return this._authHttp.post(this.actionUrl + '/Account/Register', JSON.stringify(user), { headers : headers })
.do(response => {
console.log(response.toString());
});
}
login(user: LoginViewModel) {
let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' });
return this._http.post('http://localhost:56181/api/token', "username=" + user.userName + "&password=" + user.password + "&userId=" + user.userId, { headers : headers })
.do(response => {
if(response){
let authResult = response.json();
this.setUser(authResult);
this._router.navigate(['']);
}
});
}
public isAuthenticated(): boolean {
//angular2-jwt has this function to check if token is valid
return tokenNotExpired();
}
private setUser(authResult: any): void {
localStorage.setItem('id_token', authResult.id_token);
}
public logout(): void {
localStorage.removeItem('id_token');
this._router.navigate(['']);
}
}
also remember that angular2-jwt has default name for token in localstorage as id_token or else you will have to use angular2-jwt help class to specify other token name.
You can check if it is working by simply doing this:
in app.component.ts:
export class AppComponent {
constructor(private _auth: AuthService){
}
}
and in app.component.html:
<li>
<a class="nav-link" [routerLink]="['/login']" *ngIf="!_auth.isAuthenticated()">Login</a>
</li>
<li>
<a class="nav-link" (click)="_auth.logout()" *ngIf="_auth.isAuthenticated()">Log Out</a>
</li>
also You can read a little bit documentation about it in:
https://auth0.com/blog/introducing-angular2-jwt-a-library-for-angular2-authentication/

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