How to handle token authentication in reactjs? - javascript

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

Related

Cookies not saved on mobile

I'm using next middleware to do protected routes and I'm using cookies to do the checking since you can use cookies on the server-side.
When a user logged in, I save the info on local storage(for all my frontend auth checks) and a cookie set to true if logged in(for server-side checking).
The issue is that when im on mobile, and a user logs in, the info is saved successfully. But if the user closes the browser(like safari) then opens it again and goes to the site, the user is still logged in but the Cookie doesnt seem to work(or is no longer there) but the localstorage still is there. Then this causes a case where the user is logged in but they cant access the logged in(protected) routes.
The following is my login in function
let loginUser = async (e) => {
e.preventDefault();
let response = await fetch(URL, {
method:'POST',
headers:{
'Content-Type': 'application/json'
},
body:JSON.stringify({'email':e.target.email.value, 'password':e.target.password.value})
})
if(response.ok){
let data = await response.json();
setAuthTokens(data);
localStorage.setItem("authTokens", JSON.stringify(data));
Cookies.set("auth", "true");
setUser(true);
setFailed(false);
router.push("/dashboard");
} else {
setFailed(true)
}
}
The following is my middleware
export default function middleware(req, event) {
let verify = req.cookies['auth']
const url = req.url
if (url.includes("/login") || url.includes("/signup")) {
if (verify) {
return NextResponse.redirect("some_url/dashboard");
}
}
if (url == "some_url" && verify) {
return NextResponse.redirect("some_url/dashboard");
}
if(url.includes("/dashboard")){
if(!verify){
return NextResponse.redirect("some_url/login");
}
}
}
This code may cause some security issues so I suggest you to set cookie from server also have an API for logout that clears cookie.
BTW if you don't set expire date in your cookie safari mobile will remove it.
You can do this by passing options to third argument of Cookies.set() .
It would be something like this:
Cookies.set('auth', 'true', { expires: 7 }); //expires 7 days from now

Next-auth.js - How do I redirect from getServerSideProps with a callback url?

I'm using the NextAuth library for authentication in a Next.js app and I've come across a scenario which hasn't been addressed in the docs. I'm trying to add a 'route guard' if you can call it that by checking if session is not null in getServerSideProps and redirecting to the built in signin page. Although that works, I'm always redirected to the home page after signing in. I tried to add the callbackUrl query parameter as you can see below but it still doesn't work as expected. How do I achieve this? Thanks in advance.
blog.js
export async function getServerSideProps(context) {
const session = await getSession(context)
if (!session) {
return {
redirect: {
destination: `/api/auth/signin?callbackUrl=${encodeURIComponent(
'http:localhost:3000/blog'
)}`,
permanent: false
}
}
}
return {
props: {
data: 'Blog data',
session
}
}
}
Now that your login page has a query string:
You need to pull the callbackUrl from url (I would suggest giving it a good default such as "/dashboard")
You need to pass a callbackURL to your signIn function (that I assume you are calling instead of a post method) from your onSubmit function.
const { query:{ callbackUrl } } = useRouter();
signIn("your-provider-name", {
...logInFormValues, // such as email and password
callbackUrl, // from the useRouter hook
});

JWT Token and how to use them after login?

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.

Token does not renew automatically Angular and ADAL.js

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?

Meteor accounts-google Token Expires

I have the Accounts-UI config setup to store an offline token for google thusly:
if (Meteor.isClient) {
Accounts.ui.config({
requestOfflineToken: { google: true },
forceApprovalPrompt: { google: true },
requestPermissions: { google: ["https://mail.google.com/"] }
});
}
However, tokens seem to expire. I assume I need to somehow use the refreshToken. I'm not sure how though with meteor. Any help would be lovely. Thanks!
I recommend using Google API Node JS client to refresh your access tokens.
https://github.com/google/google-api-nodejs-client/
It's available as a server-side NPM package, so you might want to use this package to be able to npmRequire it in your Meteor app.
Use this packages.json config to load the latest googleapis package :
{
"googleapis": "2.1.5"
}
Then in your Meteor server code you'll be able to refresh the access tokens like this :
ES2015
const GoogleApis = Meteor.npmRequire('googleapis');
function getAccessToken(user) {
const googleService = user.services.google;
// is token still valid for the next minute ?
if (googleService.expiresAt < Date.now() + 60 * 1000) {
// then just return the currently stored token
return {
access_token: googleService.accessToken,
token_type: 'Bearer',
id_token: googleService.idToken,
expiry_date: googleService.expiresAt,
refresh_token: googleService.refreshToken,
};
}
// fetch google service configuration
const googleServiceConfig = Accounts.loginServiceConfiguration.findOne({
service: 'google',
});
// declare an Oauth2 client
const oauth2Client = new GoogleApis.auth.OAuth2(googleServiceConfig.clientId, googleServiceConfig.secret);
// set the Oauth2 client credentials from the user refresh token
oauth2Client.setCredentials({
refresh_token: user.services.google.refreshToken,
});
// declare a synchronous version of the oauth2Client method refreshing access tokens
const refreshAccessTokenSync = Meteor.wrapAsync(oauth2Client.refreshAccessToken, oauth2Client);
// refresh tokens
const tokens = refreshAccessTokenSync();
// update the user document with the fresh token
Meteor.users.update(user._id, {
$set: {
'services.google.accessToken': tokens.access_token,
'services.google.idToken': tokens.id_token,
'services.google.expiresAt': tokens.expiry_date,
'services.google.refreshToken': tokens.refresh_token,
},
});
//
return tokens;
}
Here is a full example of how to refresh your access tokens before using a google service.
function listMeteorChannel() {
// fetch a user you want to act on behalf who authorized offline access
const user = Meteor.users.findOne({
'services.google.refreshToken': {
$exists: true,
},
});
if (!user) {
return;
}
const googleServiceConfig = Accounts.loginServiceConfiguration.findOne({
service: 'google',
});
// declare oauth2 client and set credentials
const oauth2Client = new GoogleApis.auth.OAuth2(googleServiceConfig.clientId, googleServiceConfig.secret);
// get user access token
const tokens = getAccessToken(user);
oauth2Client.setCredentials(tokens);
// obtain the youtube service at version 3 and perform authentication at service level
const youtube = GoogleApis.youtube({
version: 'v3',
auth: oauth2Client,
});
// declare a synchronous version of youtube.channels.list
const youtubeChannelsListSync = Meteor.wrapAsync(youtube.channels.list, youtube.channels);
// fetch an info snippet from the Meteor official YouTube channel
const result = youtubeChannelsListSync({
part: 'snippet',
// Meteor channel ID
id: 'UC3fBiJrFFMhKlsWM46AsAYw',
});
result.items.forEach((item) => {
// display the channel title, which should be 'Meteor'
console.log(item.snippet.title);
});
}
Meteor.startup(listMeteorChannel);

Categories

Resources