How to refresh a JWT Token using Angular Http Interceptor? - javascript

I'm trying to refresh a JWT token after it expires, using AuthInterceptor and ErrorInterceptor, two Angular HttpInterceptor methods. I send a token via headers of AuthInterceptor request:
export class AuthInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const Token = this.authService.getToken(); // localStorage.getItem('token', 'provider', ...)
if (Token) {
// if token exists (logged), send custom header values with request
const authReqRepeat = req.clone({
headers: req.headers
.set('Authorization', 'Bearer ' + Token.token)
.set('X-Auth-Provider', Token.provider)
});
return next.handle(authReqRepeat);
} else {
return next.handle(req);
}
}
}
RefreshToken method is called in ErrorInterceptor, it generates a new token object that will be used in the request that failed (because token expired):
export class ErrorInterceptor implements HttpInterceptor {
constructor(private alertService: AlertService, private authService: AuthService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(catchError((err: HttpErrorResponse) => {
if (err.status === 401) {
this.authService.logout();
} else if (err.status === 403) {
// Token expired error, so I need to generate a new token
const Token = this.authService.getToken();
return this.authService
.refreshToken(Token.refreshToken)
.pipe(flatMap((newToken) => {
// store new access token on local storage
localStorage.setItem('token', newToken.token);
localStorage.setItem('expiration', newToken.expiresIn);
// retry request with new access token generated
return next.handle(req.clone({
headers: req.headers
.set('Authorization', 'Bearer ' + newToken.token)
.set('X-Auth-Provider', Token.provider)
}));
}));
} else {
if (err.error.message) {
this.alertService.error(err.error.message);
} else {
this.alertService.error('Não foi possível completar a requisição.');
}
return throwError(err);
}
}));
}
}
I had to use HttpBackEnd to stop a infinite looping through AuthInterceptor after a new request is called by HttpErrorInterceptor:
export class AuthService {
constructor (private http: HttpClient, private handler: HttpBackend) {}
refreshToken(token: string) {
const http = new HttpClient(this.handler);
return http.post<{token: string, expiresIn: string}>(`${BACKEND_URL}/refreshtoken`, {token: token});
}
}
Node.js request:
//routes
router.post('/refreshtoken', auth, user.refreshToken);
// controllers
module.exports = async(req, res, next) => {
try {
if (req.headers['x-auth-provider'] === 'jwt') {
// refresh token
const token = req.headers.authorization.split(' ')[1];
const decodedToken = await jwt.verify(token, process.env.JWT_Key);
req.userData = {id: decodedToken.userId, email: decodedToken.email, role: decodedToken.role};
}
next();
} catch (error) {
if (error.name && error.name === 'TokenExpiredError') {
res.status(403).json({success: false, message: 'Token inválido.'});
}
res.status(401).json({success: false, message: 'Autenticação falhou.'});
}
};
exports.refreshToken = wrap(async(req, res, next) => {
const user = await User.findOne({ refresh_token: req.body.token });
if (user) {
const newToken = await jwt.sign(
{ email: user.email, userId: user._id, role: user.role },
process.env.JWT_Key,
{ expiresIn: '15m' });
const expiresAt = moment().add(900, 'second');
res.status(200).json({success: true, token: newToken, expiresIn: JSON.stringify(expiresAt.valueOf())});
} else {
res.status(401).json({success: false, message: 'Autenticação falhou.'});
}
});
It seems to work, however i messed up something because my Node.js server throws some errors: UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client.
What am I doing wrong?

Related

Angular 10: repeat the same http request after obtaining the refresh token

I am trying to achieve the following in my HTTP calls
If an API request returns 401 then call the refresh token endpoint to get the token.
Retry the same HTTP call with the updated token
Here is the relevant code
// this method invoke when the HTTP interceptor returns 401 status code
handle401(request: HttpRequest<any>, next: HttpHandler) {
if (!this.refreshTokenInProgress) {
this.refreshTokenInProgress = true;
this.refreshTokenSubject.next(null);
return this.getToken((data: any) => {
this.refreshTokenInProgress = false;
this.refreshTokenSubject.next(data);
request = request.clone({ headers: request.headers.set('Authorization', `Bearer ${data}`) });
return next.handle(request);
})
} else {
return this.refreshTokenSubject.pipe(
filter(token => token != null),
take(1),
switchMap((accessToken) => {
request = request.clone({ headers: request.headers.set('Authorization', `Bearer ${accessToken}`) });
return next.handle(request);
})
);
}
}
Obtain the refresh token
getToken(cb: any) {
let poolData = {
UserPoolId: environment.cognitoUserPoolId, // Your user pool id here
ClientId: environment.cognitoAppClientId // Your client id here
};
let userPool = new CognitoUserPool(poolData);
let cognitoUser = userPool.getCurrentUser();
cognitoUser?.getSession((err: any, session: any) => {
const refresh_token = session.getRefreshToken();
cognitoUser?.refreshSession(refresh_token, (refErr, refSession) => {
const userToken = localStorage.getItem('token');
cb(userToken);
});
})
}
While executing I am getting the new token from the getToken method, but the retry of the same HTTP call is not happening.
The execution of HTTP request stops after obtaining the refresh token from the getToken method.
Can somebody please help on this issue
if you use catchError should return Observable
your getToken function has not return
maybe you can watch this
https://stackoverflow.com/a/73364684/19768317
Assuming you want to get userToken from getToken(). getToken() should return something, right now it's not returning anything. If some methods like getSession or refreshSession are async methods then those should be waited too.
async handle401(request: HttpRequest<any>, next: HttpHandler) {
if (!this.refreshTokenInProgress) {
this.refreshTokenInProgress = true;
this.refreshTokenSubject.next(null);
const token = await this.getToken(); // put "cb" param here, wait for returned token, then continiue
this.refreshTokenInProgress = false;
this.refreshTokenSubject.next(token);
request = request.clone({ headers: request.headers.set('Authorization', `Bearer ${token}`) });
return next.handle(request);
} else {
return this.refreshTokenSubject.pipe(
filter(token => token != null),
take(1),
switchMap((accessToken) => {
request = request.clone({ headers: request.headers.set('Authorization', `Bearer ${accessToken}`) });
return next.handle(request);
})
);
}
}
getToken(cb: any) {
let poolData = {
UserPoolId: environment.cognitoUserPoolId, // Your user pool id here
ClientId: environment.cognitoAppClientId // Your client id here
};
let userPool = new CognitoUserPool(poolData);
let cognitoUser = userPool.getCurrentUser();
cognitoUser?.getSession((err: any, session: any) => {
const refresh_token = session.getRefreshToken();
cognitoUser?.refreshSession(refresh_token, (refErr, refSession) => {
const userToken = localStorage.getItem('token');
cb(userToken);
return userToken;
});
})
}

Angular interceptor - how do I logout if refresh token interceptor fails?

Info
I am creating an interceptor to use my refresh token to update my access token if I get a 401. The workflow looks like this now:
Sends request > gets 401 > sends refresh request > updates access token > sends new request
I am currently working with promises instead of observables.
Question
How do I logout if the last request fails?
Sends request > gets 401 > sends refresh request > updates access token > sends new request > fails > log out
I have a simple method for logging out, but I cannot find where to put it within the interceptor.
Code
export class RefreshInterceptor implements HttpInterceptor {
currentUser: User | null = null;
private isRefreshing = false;
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(
null
);
constructor(private authenticationService: AuthenticationService) {
this.authenticationService.currentUser.subscribe(
user => (this.currentUser = user)
);
}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
catchError(error => {
// check if user is signed in
if (!this.currentUser) {
return throwError(error);
}
// handle only 401 error
if (error instanceof HttpErrorResponse && error.status === 401) {
return from(this.handle401Error(request, next));
} else {
return throwError(error);
}
})
);
}
/**
* Adds the new access token as a bearer header to the request
* #param request - the request
* #param token - the new access token
*/
private async addToken(request: HttpRequest<any>, token: string) {
const currentUser = this.authenticationService.currentUserValue;
if (currentUser && currentUser.accessToken) {
return request.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
}
return request;
}
private async handle401Error(request: HttpRequest<any>, next: HttpHandler) {
// check if it is currently refreshing or not
if (!this.isRefreshing) {
this.isRefreshing = true;
this.refreshTokenSubject.next(null);
// send refresh request
const token = await this.authenticationService.getRefresh();
// update bearer token
const newRequest = await this.addToken(request, token);
// update values for next request
this.isRefreshing = false;
this.refreshTokenSubject.next(token);
return next.handle(newRequest).toPromise();
} else {
const token = this.refreshTokenSubject.value();
const newRequest = await this.addToken(request, token);
return next.handle(newRequest).toPromise();
}
}
}
I solved it with the following approach:
Modified the header of the outgoing, changed request (added a retry header so that I could identify it later).
Created a new interceptor for logout
Looked for a request with the retry header. Signed that request out.
Refresh token interceptor
if (currentUser && currentUser.accessToken) {
return request.clone({
setHeaders: {
Authorization: `Bearer ${token}`,
Retry: "true"
}
});
}
Logout interceptor
#Injectable()
export class LogoutInterceptor implements HttpInterceptor {
constructor(private authenticationService: AuthenticationService) {}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
catchError(error => {
// handle only 401 error
if (error instanceof HttpErrorResponse && error.status === 401) {
from(this.handleRequest(request));
return throwError(error);
}
return next.handle(request);
})
);
}
private async handleRequest(request: HttpRequest<any>) {
const isRetriedRequest = request.headers.get("retry");
if (isRetriedRequest) {
await this.authenticationService.logout();
}
}
}

Angular 5 HttpInterceptor refresh token before request

I need to refresh token in HttpInterceptor before the request is made, to do it I check the access token before the request and call refresh if it's expired.
Currently, my interceptor looks like this:
#Injectable()
export class TokenInterceptor implements HttpInterceptor {
private refreshTokenSubject = new BehaviorSubject(null);
private refreshTokenObservable = this.refreshTokenSubject.asObservable();
private isRefreshingToken = false;
constructor(private authService: AuthService) {
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const tokenData = AuthService.getCurrentSessionData();
if (!this.isRefreshingToken) {
// if no token set - make request as-is
const tokenSet = tokenData && tokenData.token;
if (!tokenSet) {
return next.handle(request);
}
// proceed if token not expired
const tokenExpired = new Date(tokenData.expirationDate) < new Date();
if (!tokenExpired) {
return next.handle(this.setAuthHeader(request, tokenData.token));
}
// check if we can refresh the token and logout instantly if not
const tokenRefreshable = tokenData.refreshToken && new Date(tokenData.refreshTokenExpirationDate) > new Date();
if (!tokenRefreshable) {
this.authService.logout();
return Observable.throw('');
}
this.isRefreshingToken = true;
// make all subsequent requests wait for new token
this.refreshTokenSubject.next(null);
// make refresh request
return this.authService.refreshToken()
.switchMap((res: any) => {
AuthService.storeSessionData(res, Utils.getLocalStorageItem(STORAGE_KEYS.STAY_LOGGED_IN));
this.isRefreshingToken = false;
// let subsequent awaiting proceed
this.refreshTokenSubject.next(res.access_token);
return next.handle(this.setAuthHeader(request, res.access_token));
})
.catch((err) => {
this.authService.logout();
return Observable.throw('');
})
.finally(() => {
this.isRefreshingToken = false;
});
} else {
// if token refreshing in progress - wait for new token
return this.refreshTokenObservable
.filter(token => token !== null)
.take(1)
.switchMap((token) => {
return next.handle(this.setAuthHeader(request, token));
});
}
}
private setAuthHeader(request, token) {
return request.clone({
setHeaders: {
Authorization: `Bearer ${token}`,
},
});
}
}
The problem is that this.authService.refreshToken() never makes the request and subsequent requests never proceed. I guess it's because nothing subscribes to the observable returned from HttpClient, here is the refreshToken method code:
public refreshToken() {
const tokenData = AuthService.getCurrentSessionData();
return this.http.post(
`${environment.apiPath}/auth/refresh`,
{ refresh_token: tokenData.refreshToken },
);
}
How can I fix this code to make refreshToken request and let other requests proceed after it as intended?
#Injectable()
export class RequestInterceptorService implements HttpInterceptor {
#BlockUI() blockUI: NgBlockUI;
isRefreshingToken = false;
tokenSubject: BehaviorSubject<string> = new BehaviorSubject<string>(null);
oldToken = localStorage.getItem('access_token');
constructor(private authService: AuthService, private localStorageToken: AppLocalStorage,
private route: ActivatedRoute, private router: Router) {}
addToken(req: HttpRequest<any>, token: string): HttpRequest<any> {
return req.clone({ setHeaders: { Authorization: 'Bearer ' + token, 'Access-Control-Allow-Origin': '*' }});
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpSentEvent | HttpHeaderResponse
| HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> {
return next.handle(this.addToken(req, this.authService.getAuthToken()))
.catch(error => {
if (error instanceof HttpErrorResponse) {
switch ((<HttpErrorResponse>error).status) {
case 400:
return this.handle400Error(error);
case 401: this.authService.refresh().subscribe((data) => { // A call has been made at an instant where network get 401 status code,It will prevent the asynchronous way of handling network calls.
this.localStorageToken.setRefreshTokens(data); //localstorageToken is used to store all the response, including access token, expiry time and refresh token to get stored in localstorage.
}
);
return this.handle401Error(req, next);
// default: this.logoutUser();
}
} else {
return Observable.throw(error);
}
});
}
handle400Error(error) {
if (error && error.status === 400 && error.error && error.error.error === 'invalid_grant') {
// If we get a 400 and the error message is 'invalid_grant', the token is no longer valid so logout.
// console.log('Bad Error');
return this.logoutUser();
}
return Observable.throw(error);
}
handle401Error(req: HttpRequest<any>, next: HttpHandler) {
if (!this.isRefreshingToken) {
this.isRefreshingToken = true;
// Reset here so that the following requests wait until the token
// comes back from the refreshToken call.
this.tokenSubject.next(null);
return this.authService.refreshToken()
.switchMap((newToken: string) => {
newToken = localStorage.getItem('access_token');
if (newToken) {
this.tokenSubject.next(newToken);
if (this.oldToken === newToken) {
return this.logoutUser();
} else {
return next.handle(this.addToken(req, newToken));
}
}
// If we don't get a new token, we are in trouble so logout.
return this.logoutUser();
})
.catch(error => {
// If there is an exception calling 'refreshToken', bad news so logout.
return this.logoutUser();
})
.finally(() => {
this.isRefreshingToken = false;
});
} else {
return this.tokenSubject
.filter(token => token != null)
.take(1)
.switchMap(token => {
return next.handle(this.addToken(req, token));
});
}
}
logoutUser() {
// Route to the login page (implementation up to you)
this.router.navigate(['login']).then(() => { this.blockUI.stop(); });
return Observable.throw('');
}
}
Now the AuthService are for getting the refresh token and also for login,
This service are called whenever refresh token are needed, I gave 2 sec gap for fetching the refresh token
export class AuthService {
private TokenApi = AppSettings.DEVELOPMENT_API;
private newToken = ' ';
private current_token: string;
private refresh_token: string = localStorage.getItem('refresh_token');
constructor(private http: HttpClient, private localStorageToken: AppLocalStorage) {
}
login(username: string, password: string): Observable<TokenParams> {
const headers = new HttpHeaders({'content-type': 'application/x-www-form-urlencoded'});
const loginApi = this.TokenApi + '/oauth/token?username=' + username + '&password=' + password + '&grant_' +
'type=password....';
return this.http.post<TokenParams>(loginApi, '', {headers: headers});
}
refresh(): Observable<TokenParams> {
this.refresh_token = localStorage.getItem('refresh_token');
const refreshToken = this.TokenApi + '/oauth/token?refresh_token=' + this.refresh_token + '&grant_' +
'type=refresh_token...';
return this.http.post<TokenParams>(refreshToken, '' );
}
logout() {
this.localStorageToken.emptyLocalStorage();
}
getAuthToken() {
this.current_token = localStorage.getItem('access_token');
return this.current_token;
}
refreshToken(): Observable<string> {
this.newToken = localStorage.getItem('access_token');
this.current_token = this.newToken;
return Observable.of(localStorage.getItem('access_token')).delay(2000);
}
}

JWT expired token handling with Angular Interceptor keeps refreshing

I have an interceptor in Angular that I am using to refresh a token if it is expired, but the application seems to get caught in an endless call of 401 errors to the API when the token is successfully refreshed. When I step through the code, the token does indeed refresh if expired but then tries to refresh repeatedly.
I should also note that upon clicking the button again and calling the API again, the app picks up the new token and works properly afterward. Would love to get this working without so many console errors in the first place though.
Here is the interceptor (old)
import { Injectable, Injector } from "#angular/core";
import { Router } from "#angular/router";
import {
HttpClient,
HttpHandler, HttpEvent, HttpInterceptor,
HttpRequest, HttpResponse, HttpErrorResponse
} from "#angular/common/http";
import { AuthService } from "./auth.service";
import { Observable } from "rxjs/Observable";
#Injectable()
export class AuthResponseInterceptor implements HttpInterceptor {
currentRequest: HttpRequest<any>;
auth: AuthService;
constructor(
private injector: Injector,
private router: Router
) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.auth = this.injector.get(AuthService);
var token = (this.auth.isLoggedIn()) ? this.auth.getAuth()!.token : null;
if (token) {
// save current request
this.currentRequest = request;
return next.handle(request)
.do((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// do nothing
}
})
.catch(error => {
return this.handleError(error)
});
}
else {
return next.handle(request);
}
}
handleError(err: any) {
if (err instanceof HttpErrorResponse) {
if (err.status === 401) {
// JWT token might be expired:
// try to get a new one using refresh token
console.log("Token expired. Attempting refresh...");
this.auth.refreshToken()
.subscribe(res => {
if (res) {
// refresh token successful
console.log("refresh token successful");
// re-submit the failed request
var http = this.injector.get(HttpClient);
http.request(this.currentRequest).subscribe(
(result: any) => {
console.log(this.currentRequest);
}, (error: any) => console.error(error)
);
}
else {
// refresh token failed
console.log("refresh token failed");
// erase current token
this.auth.logout();
// redirect to login page
this.router.navigate(["login"]);
}
}, error => console.log(error));
}
}
return Observable.throw(err);
}
}
EDIT:
Updated code to working solution
import { Injectable, Injector } from "#angular/core";
import { Router } from "#angular/router";
import {
HttpClient,
HttpHandler, HttpEvent, HttpInterceptor,
HttpRequest, HttpResponse, HttpErrorResponse, HttpHeaders
} from "#angular/common/http";
import { AuthService } from "./auth.service";
import { Observable, Subject } from "rxjs";
#Injectable()
export class AuthResponseInterceptor implements HttpInterceptor {
auth: AuthService;
currentRequest: HttpRequest<any>;
constructor(
private injector: Injector,
private router: Router
) { }
logout() {
this.auth.logout();
this.router.navigate(["login"]);
}
intercept(
request: HttpRequest<any>,
next: HttpHandler): Observable<HttpEvent<any>> {
this.auth = this.injector.get(AuthService);
let token = (this.auth.isLoggedIn()) ? this.auth.getAuth()!.token : null;
this.currentRequest = request;
return next.handle(request).
catch((error) => {
if (error instanceof HttpErrorResponse && error.status === 401) {
return this.auth.refreshToken()
.switchMap(() => {
let token = (Response) ? this.auth.getAuth() : null;
console.log(token);
if (token) {
this.currentRequest = request.clone({
setHeaders: {
Authorization: `Bearer ${token.token}`
}
});
}
return next.handle(this.currentRequest);
}).
catch((e) => {
this.logout();
console.error(e);
return Observable.empty();
});
}
return Observable.throw(error);
});
}
}
Auth.service
constructor(private http: HttpClient,
#Inject(PLATFORM_ID) private platformId: any) {
}
// performs the login
login(username: string, password: string): Observable<boolean> {
var url = "api/token/auth";
var data = {
username: username,
password: password,
client_id: this.clientId,
// required when signing up with username/password
grant_type: "password",
// space-separated list of scopes for which the token is issued
scope: "offline_access profile email"
};
return this.getAuthFromServer(url, data);
}
// try to refresh token
refreshToken(): Observable<boolean> {
var url = "api/token/auth";
var data = {
client_id: this.clientId,
// required when signing up with username/password
grant_type: "refresh_token",
refresh_token: this.getAuth()!.refresh_token,
// space-separated list of scopes for which the token is issued
scope: "offline_access profile email"
};
return this.getAuthFromServer(url, data);
}
// retrieve the access & refresh tokens from the server
getAuthFromServer(url: string, data: any): Observable<boolean> {
return this.http.post<TokenResponse>(url, data)
.map((res) => {
let token = res && res.token;
// if the token is there, login has been successful
if (token) {
// store username and jwt token
this.setAuth(res);
// successful login
return true;
}
// failed login
return Observable.throw('Unauthorized');
})
.catch(error => {
return new Observable<any>(error);
});
}
// performs the logout
logout(): boolean {
this.setAuth(null);
return true;
}
// Persist auth into localStorage or removes it if a NULL argument is given
setAuth(auth: TokenResponse | null): boolean {
if (isPlatformBrowser(this.platformId)) {
if (auth) {
localStorage.setItem(
this.authKey,
JSON.stringify(auth));
}
else {
localStorage.removeItem(this.authKey);
}
}
return true;
}
// Retrieves the auth JSON object (or NULL if none)
getAuth(): TokenResponse | null {
if (isPlatformBrowser(this.platformId)) {
var i = localStorage.getItem(this.authKey);
if (i) {
return JSON.parse(i);
}
}
return null;
}
// Returns TRUE if the user is logged in, FALSE otherwise.
isLoggedIn(): boolean {
if (isPlatformBrowser(this.platformId)) {
return localStorage.getItem(this.authKey) != null;
}
return false;
}
return this.auth.refreshToken(response:any)
//response can be true or null
let token=(response)?this.auth.getAuth():null;
//In token we have an object of type TokenResponse
console.log(token)
.switchMap(() => {
if (token) {
this.currentRequest = request.clone({
setHeaders: { //I think it's toke.token
Authorization: `Bearer ${token.token}`
}
});
....
NOTE: Try to change "var" for "let"
NOTE2: At first you have
var token = (this.auth.isLoggedIn()) ? this.auth.getAuth()!.token : null;
// May be remove "!"?
let token = (this.auth.isLoggedIn()) ? this.auth.getAuth().token : null;
If you want separate the error handle you can do some like
handleError(err: any) {
if (err instanceof HttpErrorResponse) {
if (err.status === 401) {
this.auth.refreshToken()
.switchMap(res=>{ //<--switchMap, not susbcribe
if (res) {
console.log("refresh token successful");
// re-submit the failed request
var http = this.injector.get(HttpClient);
//Modify there this.currentRequest if was neccesary
return next.handle(this.currentRequest).catch(error:any=>
{
console.error(error);
return Observable.throw(error);
});
}
else {
console.log("refresh token failed");
this.auth.logout();
this.router.navigate(["login"]);
}
})
}
}
return Observable.throw(err);
}

Angular 2 authenticate state

I've implemented a login page using Angular 2. After login, I get jsonwebtoken, userId, userRole, userName from server. I'm storing this info in localstorage so that I can access it any time and maintain login state if user refreshes page.
AuthService.ts
import {Injectable} from "#angular/core";
#Injectable()
export class AuthService {
redirectUrl: string;
logout() {
localStorage.clear();
}
isLoggedIn() {
return localStorage.getItem('token') !== null;
}
isAdmin() {
return localStorage.getItem('role') === 'admin';
}
isUser() {
return localStorage.getItem('role') === 'user';
}
}
To check the login status, I'm just checking if token exists in localstorage. As localstorage is editable so just adding any token in localstorage would bypass login page. Similarly, if client edit user role in localstorage, client can easily access admin or user pages.
How do I solve these problems?
This is more like a general problem, I want to know how websites maintain login status?
P.S.
NodeJS Server side login code to generate jsonwebtoken
const jwt = require('jsonwebtoken');
const User = require('../models/User');
/**
* POST /login
* Sign in using username and password
*/
exports.postLogin = (req, res, next) => {
User.findOne({username: req.body.username})
.then(user=> {
if (!user) {
res.status(401);
throw new Error('Invalid username');
}
return user.comparePassword(req.body.password)
.then(isMatch=> {
if (isMatch != true) {
res.status(401);
throw new Error('Invalid password');
}
let token = jwt.sign({user: user}, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_TIMEOUT
});
return res.status(200).json({
success: true,
token: token,
userId: user._id,
role:user.role,
name:user.name
});
});
})
.catch(err=>next(err));
};
-Thanks
Tokens are supposed to be unique and hard to type (as of a big length). Also, they should be refreshed with some frequency. Better to read oAuth docs on this
Roles should not be stored on client side. Only checking on server.
Also, when using oAuth consider using Scopes.
You digitally sign the authentication token on the server side:
jwt.sign({user: user}, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_TIMEOUT
})
This signature then should be verified by the server side on subsequent requests. It becomes invalid when client changes the content of the token.
store token in localStorage/sessionStorage and validate token with server whenever required. I am having following implementation to validate token
UserProfileService.ts
#Injectable()
export class UserProfileService {
private isLoggedIn: boolean = false;
private apiEndPoint: string;
constructor(private http: Http) {
this.apiEndPoint = environment.apiEndpoint;
}
login(token: string) {
localStorage.setItem('auth_token', token);
this.isLoggedIn = true;
}
logout(){
localStorage.removeItem('auth_token');
this.isLoggedIn = false;
}
isAuthorized(): Observable<boolean> {
if (!this.isLoggedIn) {
let authToken = localStorage.getItem('auth_token');
if(authToken){
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Accept', 'application/json');
headers.append('Authorization', `Bearer ${authToken}`);
return this.http.get(`${this.apiEndPoint}/validate-token`, { headers: headers })
.map(res => {
let serverResponse = res.json();
this.isLoggedIn = serverResponse['valid'];
if (!this.isLoggedIn) {
localStorage.removeItem('auth_token');
}
return this.isLoggedIn;
})
.catch(this._serverError);
}
}
return Observable.of(this.isLoggedIn);
}
private _serverError(err: any) {
localStorage.removeItem('auth_token');
if(err instanceof Response) {
console.log(err.json());
return Observable.of(false);
}
return Observable.of(false);
}
}
AuthService.ts
#Injectable()
export class CanActivateAuthGuard implements CanActivate, CanActivateChild, CanLoad {
constructor(private userProfileService: UserProfileService, private router: Router) { }
canLoad(route: Route) {
return this.userProfileService.isAuthorized().map(authorized => {
if(authorized) {
return authorized;
} else {
let url = `/${route.path}`;
this.router.navigate(['/login'], { queryParams: { redirectTo: url } });
return authorized;
}
});
}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
) {
return this.userProfileService.isAuthorized().map(authorized => {
if(authorized) {
return authorized;
} else {
this.router.navigate(['/login'], { queryParams: { redirectTo: state.url } });
return authorized;
}
});
}
canActivateChild(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
) {
return this.canActivate(route, state);
}
}

Categories

Resources