can anyone let know how can we validate the token stored in local storage for routing protection
I have saw some of tutorials but all of them checking if there is any token is present in local storage or not like below4
export class AuthGuard implements CanActivate {
constructor(private routes: Router) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable < boolean > | Promise < boolean > | boolean {
if (localStorage.getItem('token') != null) {
return true;
} else {
this.routes.navigate(['/login']);
return false;
}
}
}
and we can easily bypass this method by creating a token with random value
can anyone let me know more efficient way to validate the login token?
token validation in angular authguard
As rightly said by Heiko & Jimmy, its not the secure way to validate token at the client side.
However, assuming that you want to implement it at client side only without any server validation.
To do that in the client side
This is the best article I suggest
https://www.syncfusion.com/blogs/post/best-practices-for-jwt-authentication-in-angular-apps.aspx
Related
We have this scenario, it has an application to manage users and app's permissions, and also it has a JWT service to generate JWT.
The frontends call to JWT service (through own login form) in order to get JWT and send it in the http header to an specific backend.
All this frontends and backends are build in the same technologies.
We want to start to implement Node.js, so we are developing our first backend with node.js. But I would like to still use the JWT service and the application to manage users and app's permissions.
The new frontend is build in Angular, it calls to JWT Service and it makes petitions to the backend with the JWT, until here everything works.
But I have to create a user's table in my new database in order to create some database's constraint and logs. So the backend has a UserRepository and it implements jwt-strategy in this way:
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { UserService } from 'src/core/user/user.service';
import { CreateUserDto } from 'src/core/user/dtos';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private userService: UserService, private config: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.JWT_SECRET,
});
}
async validate(payload: any) {
// 1. Search the user is backend's table's database
let user = await this.userService.getOneByName(payload.username);
if(user == null || user == undefined) {
// 2. if the user is not register in the backend's table's database, It must be registered
const newUser: CreateUserDto = {name: payload.username, lastName: '', email: '', roles: payload.role};
user = await this.userService.createOne(newUser);
// 3. it returns the registered user
return user;
}
// it returns the user from backend database
return user;
}
}
how about security?
is this code a good way to implement a external jwt in node.js?
I recently enabled Angular Universal in my project. Everything works as expected, except for one strange issue. Whenever I refresh page or click a link in email that navigates to webpage, I see Login page for brief second then actual page loads.
Sample project created and uploaded to Github. Remember the delay may not as long as in my real project.
Github repo link: https://github.com/pavankjadda/angular-ssr-docker
Problem:
As it turns out when Ng Express Engine loads the web page, it does not have access to cookies. Hence it redirects user to Login page, but as soon as browser loads JavaScript (Angular), which checks for cookies and validates Authentication guards, redirects user to actual webpage. The ideal solution would be making cookies available on server side (sending it through request) and making sure Authentication guards passes. I tried send the cookies through server.ts, but couldn't get it working.
Work Around:
Until I figure out the solution here is the work around I followed. Whenever we check for cookies, determine if the platform is server, if yes return true. Here are the few places where you can make this change
Make sure authservice.ts returns true when the platform is server
/**
* Returns true if the 'isLoggedIn' cookie is 'true', otherwise returns false
*
* #author Pavan Kumar Jadda
* #since 1.0.0
*/
isUserLoggedIn(): boolean {
return isPlatformServer(this.platformId) || (this.cookieService.get('isLoggedIn') === 'true' && this.cookieService.check('X-Auth-Token'));
}
Do the same thing in Authentication guards
#Injectable({
providedIn: 'root'
})
export class CoreUserAuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router, #Inject(PLATFORM_ID) private platformId: any,) {
}
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
const url: string = state.url;
return this.checkLogin(url);
}
/**
* Returns true if the user is Logged In and has Core user role
*
* #author Pavan Kumar Jadda
* #since 1.0.0
*/
private checkLogin(url: string): boolean {
// Return if the platform is server
if (isPlatformServer(this.platformId))
return true;
if (this.authService.isUserLoggedIn() && this.authService.hasCoreUserRole()) {
return true;
}
if (this.authService.isUserLoggedIn() && !this.authService.hasCoreUserRole()) {
this.router.navigate(['/unauthorized']);
}
// Store the attempted URL for redirecting
this.authService.redirectUrl = url;
// Navigate to the login page with extras
this.router.navigate(['/login']);
return false;
}
}
Note: Added work around here, in case if anyone has similar problem. When I have an actual solution to the problem, I will update this answer.
change your isUserLoggedIn() function in auth service to:
public async isUserLoggedIn(): Promise<boolean> {
const isLoggedIn = await this.cookieService.check("token");
return isLoggedIn;
}
I'm working in an Angular 10 project, I am also using firebase hosting and cloud firestore (for DB). I am using AngularFire in my project as well.
My project is already able to get documents from my firestore collection, and display them (also can edit, delete, and create them). I also set up authentication, where I use AngularFireAuth to sign in and sign out. I also have route guards to only allow users access to info after signing in.
I've discovered that Firestore also has rules, and that you should set them up to secure your collection. Currently, I basically have no rules (test mode), but I want to add a basic "only users can access anything" rule, but am running into an issue.
I think this is the issue, currently, after logging in my app will store the user in local storage. I think that I need to store this a different way so I am re-signed in from previously given creds instead of just checking if there local storage. I only get ERROR FirebaseError: Missing or insufficient permissions errors when my guard checks the local storage to ensure sign-in, if I sign-in first, I don't get the error.
So, how should I save user data so that I don't have to sign-in on every refresh, but that I can verify the auth to firebase? I know I could store the email/password to local storage and check to re sign-in, but that seems insecure to me.
I think the above is the issue, but not 100% sure.
This is my firestore rule:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
allow read, write: if request.auth != null
match /{document=**} {
allow read, write: if request.auth != null //should only allow users?
}
}
}
Here is my auth service (where I handle sign-in/sign-out and check if local storage has user.
export class AuthService {
constructor(private aFAuth: AngularFireAuth, public router: Router) {
//I honestly don't know if I need this
this.aFAuth.authState.subscribe((user) => {
if (user) {
localStorage.setItem('my-test-app-currentUser', JSON.stringify(user));
} else {
localStorage.setItem('my-test-app-currentUser', null);
}
});
}
async signIn(email: string, password: string) {
this.aFAuth
.signInWithEmailAndPassword(email, password).then((result) => {
localStorage.setItem('my-test-app-currentUser', JSON.stringify(result.user));
this.router.navigate(['']);
}).catch((error) => {
window.alert(error.message);
});
}
//this is the func that needs to change, if I have storage, I need to be able to sign-in with it again
isSignedIn(): boolean {
if (!localStorage.getItem('my-test-app-currentUser')) {
return false;
}
return true;
}
signOut() {
return this.aFAuth.signOut().then(() => {
localStorage.removeItem('my-test-app-currentUser');
window.alert('You have been signed-out');
});
}
}
Here my guard:
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
// return true;
if (this.auth.isSignedIn()) {
return true;
} else {
this.router.navigate(["sign-in"]);
return false;
}
}
Any help would be appreciated.
Firebase already stores the user credentials in local storage, and automatically restores them when you reload the page.
Restoring them does require a check against the server though, so it happens asynchronously. For that reason, any code that depends on the user's authentication state should be inside the this.aFAuth.authState.subscribe handler, so that it runs whenever the authentication state changes.
So instead of handling the navigation when the signInWithEmailAndPassword call completes, which happens only when you actively sign the user in, the navigation should be in the auth listener, which runs both on active sign in and on a restore.
So something like:
export class AuthService {
constructor(private aFAuth: AngularFireAuth, public router: Router) {
//I honestly don't know if I need this
this.aFAuth.authState.subscribe((user) => {
if (user) {
localStorage.setItem('my-test-app-currentUser', JSON.stringify(user));
this.router.navigate(['']);
} else {
localStorage.setItem('my-test-app-currentUser', null);
}
});
}
async signIn(email: string, password: string) {
this.aFAuth
.signInWithEmailAndPassword(email, password).then((result) => {
window.alert(error.message);
});
}
...
In your canActivate you'll probably want to use the AngularFireAuthGuard. which ensures that unauthenticated users are not permitted to navigate to protected routes. I think this might replace your entire need for local storage.
Also see the AngularFire documentation on Getting started with Firebase Authentication and Route users with AngularFire guards.
I would like to create a route guard for protecting routes against unauthorized users.
I am using jsonwebtoken for authorization, and at the moment storing that in localStorage.
My idea is, when a user wants to access a protected admin route, authguard sends the token for validation to the nodeJS/Express server that after validation returns a true or 401 (whether the user is admin) to the client side.
auth service:
isLoggedIn(){
let headers = new HttpHeaders().set('x-auth-token',localStorage.getItem('token') || '');
return this.http.post('http://localhost:3000/api/users/check-auth', {}, { headers: headers }).toPromise();
}
authGuard service:
canActivate(){
return this.sign.isLoggedIn().then(res => {return res;}).catch(ex => {return ex});
}
My purpose would be to avoid manually setting a token key in the localstorage by the user to see the guarded route, even if he would not be able to implement any XHR request.
Could you please verify if its a good or bad idea and come up with better solution on security side?
Many thanks!
A good practice would be to manage roles (or permissions) at the model level on the server-side. For example a User class could have a roles property, such as :
auth.service.ts
myUser.roles = ['ROLE_ADMIN']
This way, when your user logins, you can store the information in your auth.service.ts
// auth.service.ts
get isAdmin() {
return this.user.roles.includes('ROLE_ADMIN')
}
Note that usually you want to store this information in you app state management, whether it be plain rxjs, ngrx, ngxs...
Finally you would add an AuthInterceptor which would redirect your user if your API returns a 401.
I am authenticating my Single Page App (Angular4) with Azure AD, and using Adal.js for the same. On the login page, I click a button that redirects to Microsoft AAD and upon successful login it redirects back to application home page, and receives id_token and user info from JWT.
I need the access_token for back-end API access, which I am trying to acquire through the the ADAL AuthenticationContext's getCachedToken() method, and sending the clientId as parameter:
this.context.getCachedToken(this.configService.AdalConfig.clientId)
But this method returns the same token which is stored in session storage as id_token (adal.idtoken). It basically creates a new item in session storage by with a concatenated key, which has same value as id_token
adal.access_token.key + clientId = id_token
ex: adal.access_token.key239f6fc7-64d2-3t04-8gfd-501efc25adkd = <id-token-value>.
I also tried to fetch access_token with AuthenticationContext.acquireToken() method, but it too gave the id_token back.
Where am I going wrong?
EDIT: posting the code.
I am calling the function login(), and after successful login, trying to get the access token in home page via get accessToken() property accessor in adal.config.ts.
config.service.ts
import { Injectable } from '#angular/core';
#Injectable()
export class ConfigService {
constructor() {}
public get AdalConfig(): any {
return {
tenant: 'common',
clientId: <application-id>,
redirectUri: window.location.origin + '/',
postLogoutRedirectUri: window.location.origin + '/'
};
}
}
adal.service.ts
import { ConfigService } from './config.service';
import { Injectable } from '#angular/core';
import { adal } from 'adal-angular';
let createAuthContextFn: adal.AuthenticationContextStatic = AuthenticationContext;
#Injectable()
export class AdalService {
private context: adal.AuthenticationContext;
constructor(private configService: ConfigService) {
this.context = new createAuthContextFn(configService.AdalConfig);
}
login() {
this.context.login();
}
logout() {
this.context.logOut();
}
handleCallback() {
this.context.handleWindowCallback();
}
public get userInfo() {
return this.context.getCachedUser();
}
public get accessToken() {
return this.context.getCachedToken(this.configService.AdalConfig.clientId);
// return this.context.acquireToken(this.configService.AdalConfig.clientId, function(message, token, response) {
// console.log(message, token, response);
// });
}
public get isAuthenticated() {
return this.userInfo && this.accessToken;
}
}
Actually, after a bit of reading, turned out that connecting SPA's to Azure AD requires OAuth 2.0 Implicit Grant flow. The Microsoft documentation says:
In this scenario, when the user signs in, the JavaScript front end
uses Active Directory Authentication Library for JavaScript (ADAL.JS)
and the implicit authorization grant to obtain an ID token (id_token)
from Azure AD. The token is cached and the client attaches it to the
request as the bearer token when making calls to its Web API back end,
which is secured using the OWIN middleware.
So, it's the id_token itself that I need to send to the back-end APIs, which in turn can be validated and used. More info about validation is given here:
Just receiving an id_token is not sufficient to authenticate the user;
you must validate the id_token's signature and verify the claims in
the token per your app's requirements. The v2.0 endpoint uses JSON Web
Tokens (JWTs) and public key cryptography to sign tokens and verify
that they are valid.
You can choose to validate the id_token in client
code, but a common practice is to send the id_token to a backend
server and perform the validation there. Once you've validated the
signature of the id_token, there are a few claims you will be required
to verify.
I've faced an issue like yours when I was trying to send the token to a .Net Core API endpoint.
It worked for me when I sent the token from the adal.access.token.key node, on sessionStorage.
Using adal.access.token.key or adal.idtoken token values (they are the same) didn't work for me.
Valid token on adal.access.token.key node.