I like to understand the JWT handling of token.
I have created a login page to check if user exist in DB? If yes, I used jwt sign a token and return jwt token.
jwt.sign({userdata}, secretKey, (err, token) => {
res.json({
token
After I get the token I understand I have store it in local storage.
localStorage.setItem("token", token);
After this I am lost! How can I redirect the login to a protected URL once the token is stored?
Then my next question is how can I make use of the local stored token in the protected route?
For example login.html will invoke a login function call and return the token then I want to go to /admin/admin.html. In /admin/admin.html, i have protected routes that need to use the token. How can I use it ? How can I know the user is the same user using the protected route since? I know the localstored token has the user information. Does that mean every protected route I have to post a user information and compare to local token?
Some examples of the code will be useful. Thanks
You can do something like that
login() {
try{
const tk = response.token; // from api response
if (tk) {
const expiresInDuration = response.expiresIn;
setAuthTimer(expiresInDuration); // setTimer to not send rest call everytime if user is visiting many times
const now = new Date();
const expirationDate = new Date(
now.getTime() + expiresInDuration * 1000
);
this.saveAuthData(this.token, expirationDate, role);
navigate(['/home']); // function which should redirect to your desired url
}
}, (err) => {
console.log(err)
});
}
// function to auto logout after specified time
setAuthTimer(duration: number) {
this.tokenTimer = setTimeout(() => {
this.logout();
}, duration * 1000);
}
saveAuthData(token, expirationDate, role) {
localStorage.setItem('token', token);
localStorage.setItem('expiration', expirationDate.toISOString());
}
// after delete and log out
clearAuthData() {
localStorage.removeItem('token');
localStorage.removeItem('expiration');
}
// function to login user if its data is already present in the localStorage
autoAuthUser() {
authInformation = getAuthData();
if (authInformation) {
const now = new Date();
const expiresIn = authInformation.expirationDate.getTime() - now.getTime();
if (expiresIn > 0) {
this.token = authInformation.token;
this.isAuthenticated = true;
this.setAuthTimer(expiresIn / 1000);
}
}
}
For your question regarding same user is accessing the protected route as local storage is storing token specific to user that should take care of the task
You have to use a library that verifies your stored JWT token. You can use https://www.npmjs.com/package/jsonwebtoken . This library includes a method that verifies your JWT jwt.verify(token, secretOrPublicKey, [options, callback]). To be able to verify a token, you must provide the secret key that is used to sign your tokens. If the token is verified successfully, you can redirect the user to its designated page. As long as the token is stored and not expired, the user is remembered in the browser.
This is an approach for JS apps, however, if you're using PHP/Laravel, the token is stored in a HTTP cookie and I recommend using jwt-auth library, it will handle the JWT processes for you.
Related
I am trying to create a product verification system, and I have the login part down. My question is how are there any API (s) that can verify something like an activation code and return if it succeeded or not.
Btw, you might have to scroll horizontally to see all of the code
//How would I add a verification system
document.getElementById('redeemButton').addEventListener('click', () => {
var code = document.getElementById('redeemCodeBox').value;
var product = document.getElementById('productCode').value;
const fetchPromise = fetch(`https://www.mywebsite.com/api/redeem?product=${product}&code=${code}`);
fetchPromise.then( response => {
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.json();
})
.then( json => {
console.log(json[0].name);
})
.catch( error => {
console.error(`Could not get products: ${error}`);
throw `Please Enter a Valid Activation Code`;
});
});
I don't think you should be dependent on a third party to verify an Activation Code.
You should use a combination of JWT token + activation code that you store in the database.
Generate an activation code.
Generate JWT token.
const token = await this.jwtService.sign(
{
usetId: userId
},
{ expiresIn: "1d" }
);
Save the activation code and the JWT token to the database.
Send an E-mail or SMS with an activation code to the user. + include URL where user can insert the activation code.
URL can look something like this:
https://www.mywebsite.com/api/activation?token=eyJhbGciOiJIfasdfas
(use query parameter for JWT token =>> ?token=JWT_token
Verify JWT token from URL (value from query parameter "token") and validate if Token is not expired.
Control if user input matches an Activation code stored in your database.
Activated
I used firebase authentication to secure my ASP.NET CORE api.
I actually store the users in the database my API uses. Note that in my database the google identifiers are the uids generated by the firebase authentication and that the classic identifiers (login + password) are generated in my API.
When the user connects with Google, the token is created in the client(Angular) so I send it to my API (of course I don't store it), I just check if the token is valid and if the id which is contained in the token corresponds to the identifier of one of the users which is stored in my database.
In my client, for google authentification:
async GoogleAuth() {
try {
return new Promise((resolve, reject) => {
signInWithPopup(this.auth, this.provider).then(() => {
this.auth.onAuthStateChanged((user) => {
if (user) {
user
.getIdToken()
.then((idToken) => {
this.sendTokenUserGoogleToAPI(user, idToken)
.then((data: any) => {
localStorage.setItem('token', data.token);
resolve(data);
})
.catch((error) => {
console.log("googleAuth : " + error)
reject(error);
});
})
.catch(() => { });
}
});
});
});
} catch (e) { }
}
For the classic connection (login + password), the data is sent directly to my API and I create a personalized token with the user ID in my backend and I send the token to the client who generates a personalized token to from the token.
In my API, for classic authentication :
[HttpPost]
[ActionName("signin")]
public async Task<ActionResult> SignIn([FromBody] UserLoginViewModel userModel)
{
var user = await _context.Users.FirstOrDefaultAsync(u =>
u.Login == userModel.Login && u.Password == userModel.Password);
if (user == null)
{
_logger.LogWarning("Connection attempt failed.");
return NotFound(new { message = "User or password invalid." });
}
if (user.IsLocked)
{
return new ObjectResult(new { message = "Your account has been blocked." }) { StatusCode = 403 };
}
var token = await FirebaseAuth.DefaultInstance.CreateCustomTokenAsync(user.UserId);
var login = user.Login;
return Ok(new
{
login,
token
});
}
In my client, when I receive the token from my API
signInWithCustomToken(getAuth(),token)
.then((userCredential) => {
const user = userCredential.user;
console.log(token)
user!.getIdToken(true).then((idToken) => {
localStorage.setItem('token', idToken)
}).catch((error) => {
console.log(error)
})
})
So I'm guessing I shouldn't generate the token in my backend and only return the id to generate the token in the client? I regenerate the token in my client so that the user can access the chat
With my client, is it better that I get the token by querying firebase each time or is it better that I store this token locally to be able to use it in my requests?
For the moment, I store it locally but I think that it can be problematic if the token changes or if an attacker modifies his token because I verify thanks to firebase that the user is connected, if the local token changes, firebase will always say that the user is logged in but in my api the token will not be valid.
The ID token you get from Firebase Authentication is an exp property/claim that shows you until when it's valid. Firebase's own SDKs refresh the token about 5 minutes before it expires, so your code should probably do the same. In fact, if you listen for when the ID token changes (Android web), you don't have to force a refresh yourself and can just piggyback on the work the SDK already does for you.
I have a problem with the function(I suspect) in which I add custom claims. Here is the code:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.addAdminRole = functions.https.onCall((data)=> {
//get user and add custom claim (admin)
return admin.auth().getUserByEmail(data.email).then(user => {
return admin.auth().setCustomUserClaims(user.uid, {
admin: true
});
}).then( () => {
return {
message: `Success! ${data.email} has been made an admin`,
}
})
})
I call the function using the following code(I use Redux and React):
let addAdminRole = window.firebaseFunctions.httpsCallable('addAdminRole');
addAdminRole({email:employee.email}).then(res => {
console.log(res)
})
I get the expected message({message: Success! ${data.email} has been made an admin}), but the claim isn't added.
I make a separate Firebase Auth REST API via axios for authentication, but the claim 'admin' isn't there.
I have a Spark(free) billing plan and when checking the logs from firebase functions I see 'Billing account not configured. External network is not accessible and quotas are severely limited. Configure billing account to remove these restrictions' when addAdminRole is executed.
From what I read this is a message that you always get on the free plan and there shouldn't be a problem when accessing internal(google) info.
Here is the code for the axios Request:
axios({
method:'post',
url:urlAuth,
data:{
email:employee.email,
password:employee.password,
returnSecureToken: true
}
}).then(res => {
delete employee.password;
console.log(res)
const expirationDate = new Date().getTime() + res.data.expiresIn * 1000;
localStorage.setItem('token',res.data.idToken);
localStorage.setItem('expirationDate',expirationDate);
localStorage.setItem('userId', res.data.localId);
localStorage.setItem('admin', res.data.admin)
dispatch(checkAuthTimeout(res.data.expiresIn));
if(logIn){
dispatch(endAuth(res.data.idToken,res.data.localId,res.data.admin));
}else{
employee.userId = res.data.localId;
dispatch(addEmplRegister(employee,res.data.idToken,admin));
}
}).catch(err => {
dispatch(errorAuth(err.message))
})
FOUND OUT THE ISSUE, the information about claims isn't transmitted when using REST API authentication
Setting a custom claim doesn't take effect immediately for users with existing JWT tokens. Those users will have to either:
Sign out and back in again,
Force refresh their token, or
Wait up to one hour for that token to automatically refresh by the Fireabse Auth SDK.
On then will their new token show the custom claim.
We are currently implemented ADAL.js using Angular, but unfortunately the token does not renew automatically, the code is the following to handle the logic to renew the token
We added some logic to check if the token is expired and renew it automatically, but this is not working for me. The application kick you out to white page, and it is required to refresh the browser in order sign in again.
#Injectable({
providedIn: 'root',
})
export class AuthenticationService {
checkAuthInterval$: Observable<number>;
constructor(
private jwtHelperService: JwtHelperService,
private adal: MsAdalAngular6Service
) {
this.checkAuthInterval$ = interval(6000).pipe(
tap(() => {
this.updateSession();
})
);
this.checkAuthInterval$.subscribe();
}
public isAuthorized(allowedRoles: string[]): boolean {
if (allowedRoles == null || allowedRoles.length === 0) {
return true;
}
const token = localStorage.getItem(
authenticationSettings.authenticationToken
);
const status = this.jwtHelperService.isTokenExpired(token);
const decodeToken = this.jwtHelperService.decodeToken(token);
if (!decodeToken) {
console.log('Invalid token');
return false;
}
decodeToken.roles = decodeToken.roles || [];
const allow = allowedRoles.some(r => decodeToken.roles.includes(r));
return allow;
}
get profileInfo(): User {
return this.adal.userInfo;
}
get expirationDate() {
const expiration = this.profileInfo.profile.exp * 1000;
return moment(expiration);
}
get issueDate() {
const expiration = this.profileInfo.profile.iat * 1000;
return moment(expiration);
}
get hasExpired(): boolean {
const today = moment();
return this.expirationDate.isSameOrBefore(today);
}
get shouldRenew(): boolean {
const today = moment().subtract(5, 'minutes');
return this.expirationDate.isSameOrBefore(today);
}
get isAuthenticated() {
const authenticated = this.adal.isAuthenticated;
return authenticated;
}
updateSession() {
if (this.hasExpired) {
this.adal.login();
} else if (this.shouldRenew) {
this.adal.RenewToken(environment.ApiBaseUrl);
}
}
}
Generally speaking, we don't recommend trying to renew the token yourself, as you should let the library handle that as needed. Our recommendation for acquiring tokens is as follows:
Check if the user has signed in, and if not, send them to the login screen: https://github.com/AzureAD/azure-activedirectory-library-for-js#2-login-the-user
When your app is in the process of making a network request to a resource (e.g. Microsoft Graph) that requires an access token, call acquireToken with the scopes needed for that request.
If there is already a valid token in the cache, that token should be returned to you.
If there is not a valid token in the cache (e.g. an expired token) and your user still has an active session with AAD, the library will make a network request to acquire a new token, and return that new token to you.
If there is not a valid token in the cache, and your user does not have an active session with AAD or there is some other scenario that requires interaction (e.g. consenting to new scopes), the library will return an error and you will need to invoke one of the interactive methods (acquireTokenPopup or acquireTokenRedirect) to resolve those issues, and then the new token will be returned to your application: https://github.com/AzureAD/azure-activedirectory-library-for-js#3-get-an-access-token
Automatically trying to renew the token yourself may cause other issues, which is why we don't recommend it. Instead, call acquireToken lazily right before you need to the token, and then have error handling in place (as described above) to invoke interactive methods if needed.
Have you tried the process described above, and if so, what were the specific issues/errors that you encountered? Are there errors in the browser console or the network tab?
Suppose I have API for login, log out. I am using Token Authentication.
I have react component name Login. I can call API and I am getting token. But, I am not understanding how to save the token in a browser until log out or automatically destroy token after a moment.
You can create a storage module and check for available storage in client's browser.
import LocalStorage from "./localstorage"
import Cookie from "./cookie"
// Function to check availability of `localStorage`
function storageAvailable() {
try {
var storage = window["localStorage"],
x = '__storage_test__';
storage.setItem(x, x);
storage.removeItem(x);
return true;
}
catch(e) {
return false;
}
}
export default function Storage() {
return storageAvailable() ? LocalStorage : Cookie
}
Using above module:
function login(redirect = '/home') {
// set your session data
storage.set({email, id})
// redirection
window.location.href = redirect
}
function logout(redirect = "/") {
storage.clear()
// redirection
window.location.href = redirect
}
const Session = {
login,
logout
}
export default Session
Now you can simply use your Session module for login and logout as Session.login() and Session.logut() respectively.
How to use cookie:
How do I create and read a value from cookie?
How to use localStorage: Storing Objects in HTML5 localStorage
You can use universal-cookie package to set it in the cookie,
const cookies = new Cookies();
cookies.set('token', this.token, { path: '/' });
Don't forget to import(or require) Cookies from universal-cookie.
You can retrieve it back using :
cookies.get('token');
Refer to https://github.com/reactivestack/cookies/tree/master/packages/universal-cookie
You can save it in local storage as well :
localStorage.setItem('token', this.token);
But saving in the cookie would be a better idea, refer to :
Local Storage vs Cookies