how can I use refresh token in angular 4 - javascript

I have completed steps of authorization and obtained the access token and refresh token by Laravel Passport.
My Angular frontend and Laravel backend work fine.
My main questions are:
How and when should I use the refresh token to make new access token?
Should this be done in the background or should the user have to click on a button to refresh the token?
Should the Angular page be reloaded when creating the new token?

I am using JWT authentication in my angular project where the token is set by the API.
The approach I'm taking when the token is expired in explained below -
Expose a new API which will take an expired token and return newly created token.
The API should check the token expiry in every REST API call.
In case, the token is expired, the API should return a status (as per the standards, 498 - expired/invalid).
In angular,create a service layer (token refresher) which delegates every API calls to the server (internally using the http service).
The job of this service is to check the status of API response (if it is 498) and internally make an additional call to refresh the token.
The service can then re-initiate the original call with newly created token to get the response.
All the api services will call the token refresher to get the response.
On a broader level, token refresher is a wrapper over the default http service which performs the additional check.
This will avoid the annoying page loads and make the application faster.
EDIT - Example of HTTP Interceptor
import { Injectable } from "#angular/core";
import { XHRBackend, RequestOptions, Request, RequestOptionsArgs, Response, Http, Headers } from "#angular/http";
import { Store } from "#ngrx/store";
import { Observable } from "rxjs/Rx";
import { Observer } from "rxjs/Observer";
import { Response as ApiResponse } from "../../models/base/response.model";
import { ToastModel } from "../../redux/app-reducers";
import { ReducerActions } from "../../redux/reducer-actions";
#Injectable()
export class HttpInterceptor extends Http {
constructor(private _XHRBackend: XHRBackend,
private _RequestOptions: RequestOptions,
private _ToastStore: Store<ToastModel>,
private _LoaderStore: Store<boolean>) {
super(_XHRBackend, _RequestOptions);
}
public request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
return this.handleResponse(super.request(url, options));
}
public get(url: string, options?: RequestOptionsArgs): Observable<Response> {
this.beforeRequest(url);
return super.get(url, this.getRequestOptionArgs(options));
}
public post(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
this.beforeRequest(url, body);
return super.post(url, body, this.getRequestOptionArgs(options));
}
public put(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
this.beforeRequest(url, body);
return super.put(url, body, this.getRequestOptionArgs(options));
}
public delete(url: string, options?: RequestOptionsArgs): Observable<Response> {
this.beforeRequest(url);
return super.delete(url, this.getRequestOptionArgs(options));
}
private getRequestOptionArgs(options?: RequestOptionsArgs): RequestOptionsArgs {
if (options == null) {
options = new RequestOptions();
}
if (options.headers == null) {
options.headers = new Headers();
}
options.headers.append('Content-Type', 'application/json');
return options;
}
private handleResponse(response: Observable<Response>): Observable<Response> {
return response
.catch(this.onCatch)
.do(this.onSuccess.bind(this), this.onError.bind(this))
.finally(this.afterResponse.bind(this));
}
private beforeRequest(url: string, body?: string): void {
this._LoaderStore.dispatch({ type: ReducerActions.Loader.Set, payload: true });
}
private afterResponse(): void {
this._LoaderStore.dispatch({ type: ReducerActions.Loader.Set, payload: false });
}
private onCatch(error: any, caught: Observable<Response>): Observable<Response> {
console.log("interceptor catch called");
return Observable.throw(error);
}
private onSuccess(res: Response): void {
let response: ApiResponse<any> = res.json();
if (!response.message) {
return;
}
let toast: ToastModel = {
text: response.message,
duration: 5000,
type: "success"
};
this._ToastStore.dispatch({ type: ReducerActions.Toast.Update, payload: toast });
}
private onError(error: any): void {
let toast: ToastModel = {
text: "Error occurred!",
duration: 5000,
type: "failure"
};
this._ToastStore.dispatch({ type: ReducerActions.Toast.Update, payload: toast });
}
}
In the above example, handleResponse callback is the hook to do anything you want. (In this case, token refresh API call).
I hope this helps. :)

Related

How to handle the Internal server error? Nestjs

How to handle the error so that if the user does not provide a token, then an UnauthorizedException is thrown.
At the moment I am getting this error:
{
"statusCode": 500,
"message": "Internal server error"
}
ts:
canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
try {
const jwt = request.headers.authorization.split(' ')[1];
if (!jwt) {
throw new UnauthorizedException('Token is not provided.');
}
return this.jwtService.verify(jwt);
} catch (e) {
return false;
}
}
You can try to recreate auth module from the documentation.
Or try to console.log() on each line.
By default, JWT internal module works well. It can encode and decode all that you need automatically.
https://docs.nestjs.com/security/authentication
I use a middleware for this purpose. I'll share a basic version of it.
auth-middleware.ts
import {HttpStatus,Injectable,Logger,LoggerService,NestMiddleware,} from '#nestjs/common';
import { NextFunction } from 'express';
import { Request, Response } from 'express';
#Injectable()
export class AuthMiddleware implements NestMiddleware {
constructor(
private readonly authenticationService: AuthService,
// (I use Firebase auth. You can inject JWT service here instead)
private readonly logger: LoggerService, // Good'ol logger
) {}
public async use(req: Request, res: Response, next: NextFunction) {
// Checks if req has authorization header
const header = req.headers['authorization'];
if (!header) {
// If no headers are present, returns a 401.
// I use problem+json
// Thats why you are seeing more fields in the response instead of just a
// code and message
return res
.status(HttpStatus.UNAUTHORIZED)
.json({
title: 'Unauthorized',
detail: 'Invalid Token',
type: 'https://app-site.com/login',
status: HttpStatus.UNAUTHORIZED,
instance: 'login/null',
})
.setHeader('Content-Type', 'application/problem+json');
}
// Splitting "Bearer token" to ["Bearer","token"]
const token = header.split(' ')[1];
// Validating token with auth service
// It returns a "tuple" for me...you can have it return whatever you want
const [
authClaims, // Custom claims that is extracted from the JWT
result, // JWT Validation result (boolean)
authProviderUid, // Firebase UID
] = await this.authenticationService.verifyToken(token);
if (
!result || // If JWT is invalid
authClaims.accountStatus === AccountStatusList.Banned ||
authClaims.accountStatus === AccountStatusList.Suspended
) {
// You shall not pass
return res
.status(HttpStatus.UNAUTHORIZED)
.json({
title: 'Unauthorized',
detail: 'Invalid Token',
type: 'https://app-site.com/login',
status: HttpStatus.UNAUTHORIZED,
instance: 'login/null',
})
.setHeader('Content-Type', 'application/problem+json');
}
// Attaches the claims, result and UID with req for the next middleware(s)/controller
req['authResult'] = { authClaims, result, authProviderUid };
//Reassuring
this.logger.log('Token verified', AuthMiddleware.name);
// next function from express
next();
}
}
Next, In the module(s) your controllers are declared,
api.module.ts
import { MiddlewareConsumer, Module, NestModule, RequestMethod, } from '#nestjs/common';
#Module({
imports: [
//...
],
controllers: [
AuthController,
ProfileController,
SubscriptionController
],
providers: [
//...
],
})
export class ApiModule implements NestModule {
public async configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuthMiddleware)
// Exclude some paths
.exclude({ path: '/api/v1/auth/sign-up', method: RequestMethod.POST })
.forRoutes( // Your controller classes you want to run the middleware on
ProfileController,
SubscriptionController,
AuthController
);
}
}
How it works
Every request goes through the specified middleware (if path not excluded). If the request is unauthorized, throws an error before it reaches the controller.
If the request is at the controller, the request is authenticated. You have to take care of the authorization part with guards etc...
Authentication and Authorization are different.
I'd suggest to use middleware for authentication and guards for authorization.
Links :
NestJS Middleware Documentation
Problem Details

Show login modal on unauthorized response angular 7

I need to show a login modal every time the server returns a http unauthorized status (401), and in that case, stop the page loading... for example, I'm logged in but trying to access an protected resource that only admin users can do it.. so in that case I would like to show an modal with login and password to the user. It could be on navigating to a protected route or on delete event for example.
I tried to do it in an ApiInterceptor:
#Injectable({providedIn: 'root'})
export class ApiInterceptor implements HttpInterceptor {
constructor(
...
) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
req = req.clone({ url: environment.baseUrl + req.url });
if (this.authService.validToken) {
req = req.clone({ headers: req.headers.set('Authorization', `Bearer ${this.authService.validToken}`) });
}
if (!req.headers.has('Content-Type')) {
req = req.clone({ headers: req.headers.set('Content-Type', 'application/json') });
}
return next.handle(req).pipe(catchError(resp => this.handleError(resp)));
}
private handleError(httpError: HttpErrorResponse) {
if (httpError.status === this.UNAUTHORIZED) {
// opening login modal here, but can't stop the request to prevent user to se unauthorized data, and after login, how can I redirect user to the same resource he tried to access?
}
return throwError(httpError);
}
}
Need help here, if someone have an idea in how to do it will be appreciated!
Your ApiInterceptor looks like it's for adding a bearer token to the request. I'd call this the TokenInterceptor or similar, and create a new one for handling unauthorised requests.
I'd create a new interceptor and call this UnauthorisedRequestInterceptor. Something similar to this:
#Injectable({ providedIn: 'root' })
export class UnauthorisedRequestInterceptor implements HttpInterceptor {
constructor(private router: Router) { }
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
map(event => {
return event;
}),
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
this.router.navigate(['/auth/login']);
}
return throwError(error);
})
);
}
}
This will intercept every http request, and if the returned status is 401, it will redirect you to your login page.
Then add this into your list of providers in app.module.ts:
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: UnauthorisedRequestInterceptor,
multi: true
}
]
As for redirecting users on access to a protected route, this should be done in your auth guard.
Every protected data that need roles or permissions should be on the server & have specific authorization to access it, if there is already protected data on your application you should consider moving it onto your server or add at least a guard.
If you want to redirect the user after a 401 you have to inject the Router service in your interceptor & use the navigate function (cf this.router.navigate(['/myRoute']); )

Ionic3 Events not working

I am using events in my Ionic3 app.
For e.g I am using events to redirect the user to login screen whenever any API response gives HTTP 401.
So in my app.component.ts file I am doing:
import { Component, ViewChild } from '#angular/core';
import { StatusBar } from '#ionic-native/status-bar';
import { Events } from 'ionic-angular';
import { Network } from '#ionic-native/network';
import { Toast } from '../utilities/toast';
import { LocalStorage } from '../utilities/localstorage';
import { Platform, MenuController, Nav } from 'ionic-angular';
#Component({
templateUrl: 'app.html'
})
export class MyApp {
#ViewChild(Nav) nav: Nav;
rootPage: any;
pages: Array<{title: string, pageName: string}>;
guardian: any;
constructor(
public platform: Platform,
public menu: MenuController,
public statusBar: StatusBar,
public events: Events,
public network: Network,
public toast: Toast,
public storage: LocalStorage)
{
console.log('before unauthorised'); //This line works when a 401 occurs
events.subscribe('unauthorised', () => {
console.log('user unauthorised take to login page'); //While this doesn't
this.storage.clear();
this.nav.setRoot('LoginPage');
});
}
}
And in my api services file I am publishing the event:
import { Http } from '#angular/http';
import { Injectable } from '#angular/core';
import { Toast } from '../utilities/toast';
import { Events } from 'ionic-angular';
import { LocalStorage } from '../utilities/localstorage';
#Injectable()
export class ServiceProvider {
constructor(public http: Http,
private toast: Toast,
public events: Events,
private storage: LocalStorage) {
}
getErrorMessages(errors) {
if (errors.status == 401) { //<= unauthorised
this.toast.present('You need to login first!');
this.events.publish('unauthorised');
}
let error_messages = [];
if (errors.status == 422) { //<= validation error
let validation_messages = JSON.parse(errors.text())
for (var key in validation_messages) {
if (validation_messages.hasOwnProperty(key)) {
var messages = validation_messages[key];
error_messages.push(...messages);
}
}
} else { //<= timeout or http code 500, 405 etc.
error_messages.push('Technical error occured... please try again later.');
}
return error_messages;
}
}
What could have been the problem? The code looks correct as per the ionic documentation.
EDIT I am adding the child service code. So basically the service provider is the parent class for all the api services. For e.g the auth service class extends the service class above and has the following method for fetching auth user:
getAuthUser() {
console.log('will fetch auth');
let headers = new Headers({
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer ' + this.getAuthToken()
});
let options = new RequestOptions({ headers: headers });
return new Promise((resolve, reject) => {
this.http.get(this.getApiUrl() + '/me', options)
.timeout(this.getTimeOut())
.map(res => res.json())
.subscribe(response => {
resolve(response);
this.events.publish('auth_user_fetched');
}, errors => {
reject(this.getErrorMessages(errors));
});
});
}
Not that I am not using try catch here.
What is happening is that app.component.ts acts as a Parent and your provider as a child. Therefore, the event cannot be published and subscribed.
In your code console.log('before unauthorised'); //This line works when a 401 occurs works because app.component.ts is a file that gets called everytime you do some activity. And this console is written in the constructor(resulting in it called everytime).
What you can do is instead of using event for unauthorized functionality. Create a function in your provider itself that will do
unauthorized() {
console.log('user unauthorised take to login page');
this.storage.clear();
this.nav.setRoot('LoginPage');
}

ng2-smart-table's ServerDataSource not sending credentials to web api for windows authentication

This is going to be a long question.
For windows authentication to work with angular I have wrapper for the http calls as shown below
import { Injectable } from '#angular/core';
import { Http, Response, RequestOptions, Headers, RequestOptionsArgs } from '#angular/http';
import { Config } from '../_helpers/config';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class HTTPUtility {
public baseApiUrl: string;
constructor(private http: Http, private config: Config) {
this.baseApiUrl = this.config.getByKey('baseApiUrl') || '';
}
public getApiUrl(url) {
return this.baseApiUrl + url;
}
public get(url: string, options?: RequestOptions) {
if (!options) {
options = this.getDefaultHeaders();
}
return this.http.get(this.getApiUrl(url), options)
.catch(this.handleError);
}
private getDefaultHeaders() {
let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' });
headers.append('Accept', 'application/json');
return new RequestOptions({ headers, withCredentials: true });
}
public handleError(response: Response) {
return Observable.throw(JSON.parse(response.json().Message) || 'Server error');
}
}
If you observe new RequestOptions({ headers, withCredentials: true }); is allowing browser to send credentials to server for windows authentication.
And it's working great for everything.
Now coming to the issue, I have sampleComponent in which i'm using ServerDataSource as shown below:
import { Component, OnInit, NgZone } from '#angular/core';
import { Http, RequestOptions } from '#angular/http';
import { Ng2SmartTableModule, ServerDataSource } from 'ng2-smart-table';
#Component({
selector: 'criteria',
templateUrl: './criteria.component.html',
styles: [require('./criteria.scss')],
})
export class SampleComponent implements OnInit {
Source: ServerDataSource;
settings: any;
constructor(
private http: Http) {
this.Source = new ServerDataSource(http, { endPoint: 'https://xxxxxx.org/yyy/GetCriterias'});
}
ngOnInit(): void {
// this.settings = {}// assigning the settings.
}
}
As you can see ServerDataSource is accepting Http instance and I have checked there documentation and haven't found any way to pass to RequestOptions. So the web api call made by ng2-smart-table fails with 401 status as credentials is not passed.
To resolve this issue I have made changes directly to ng2-smart-table source file to be specific 'server.data-source.js' and here is the change
ServerDataSource.prototype.createRequestOptions = function () {
var requestOptions = {withCredentials : true}; // this where I have added the withCredntials flag
requestOptions.params = new URLSearchParams();
requestOptions = this.addSortRequestOptions(requestOptions);
requestOptions = this.addFilterRequestOptions(requestOptions);
return this.addPagerRequestOptions(requestOptions);
};
With this change everything is working fine as of now.
But I could have issue in future, if we upgrade the package in that case I have to again make changes.
So if any one can help me to fix the issue in some other way please let me know.
Links: https://github.com/akveo/ng2-smart-table/blob/master/src/app/pages/examples/server/advanced-example-server.component.ts
Thanks.

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/

Categories

Resources