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
1) 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
2) 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);
}
}