Handle expired jsonwebtoken - NodeJS - node.js

So I have my app working properly regarding JWT. I can log in with a certain user, log out, perform operations with such user, etc.
However, I'm not sure how to handle the expiration of the token. I am using Angular and the localStorage in order to store the token.
Is there a way of removing the localStorage with Angular if the token is expired?
I have the following code:
io.on('connection', async (socket) => {
let token = socket.handshake.auth.token;
if (token) {
try {
let userTkn = jwt.verify(token, config.secret);
//A bunch of code that does stuff
} catch (err) {
//Expiration and other errors handling
}

Related

Refresh token how to handle POST routing

I am new to JWT and tokens for user verification and login. I used the following extensions for Node JS (NPM)
var jwt = require('jsonwebtoken');
const cookieParser = require('cookie-parser')
require('dotenv').config();
// Express ..
I already have a login that checks in MongoDB (Node JS as server) the user, checks email and password and then sets a cookie with access token and refresh token.
My login code is like
//create the access token with the shorter lifespan
let accessToken = jwt.sign(payload, process.env.ACCESS_TOKEN_SECRET, {
algorithm: "HS256",
expiresIn: process.env.ACCESS_TOKEN_LIFE
})
//create the refresh token with the longer lifespan
let refreshToken = jwt.sign(payload, process.env.REFRESH_TOKEN_SECRET, {
algorithm: "HS256",
expiresIn: process.env.REFRESH_TOKEN_LIFE
})
//send the access token to the client inside a cookie
res.cookie("_login", accessToken, {secure: true, httpOnly: true})
res.send()
and here is the part for refresh token post
exports.refresh = function (req, res, next){
console.log("Test");
let accessToken = req.cookies._login
if (!accessToken){
return res.status(403).send()
}
let payload
try{
payload = jwt.verify(accessToken, process.env.ACCESS_TOKEN_SECRET)
}
catch(e){
return res.status(401).send()
}
//retrieve the refresh token from the users array
let refreshToken = payload.email.refreshToken
//verify the refresh token
try{
jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET)
}
catch(e){
return res.status(401).send()
}
let newToken = jwt.sign(payload, process.env.ACCESS_TOKEN_SECRET,
{
algorithm: "HS256",
expiresIn: process.env.ACCESS_TOKEN_LIFE
})
res.cookie("_login", newToken, {secure: true, httpOnly: true})
res.send()
My question now is, since I see in so many tutorial and guides that they also work with refresh token via POST, how do I process that with the user?
Would the client send an AJAX or post to middleware for check on access token
-> If Access token is expired
--> code automatically takes refresh token and issues a new access token and gives an OK?
or the client sends to middleware where access token is checked
-> Access token is expired (result to the user)
-> client make now post request to /refresh-token result = new access and refresh token
-> And again post request to original middleware with new post request?
What is the procedure here, I can't find any workaround how this is handled.
Keep in mind that my answer is based on my experience. Feel free for anyone to edit if I happen to make a mistake in my ways.
So, in order to handle refresh token, I use this way:
When a user successfully logs in, the JWT (containing user's auth) and the refresh token (containing the user's refresh token) will be placed in the user's cookies (same like you).
The user will do his/her things inside your web application, until he/she closes the browser without signing out.
Remember that JWT always have expiration date - this expiration date will be kept in mind.
In every request, you're going to send that specific JWT (that contains the user's auth) and the refresh token to a middleware where you are going to make a POST request. If the JWT has expired, pick the refresh token and call your /refresh-token-result to get a new token. Else, just don't do anything with the refresh token and proceed with your request.
Ensure that your /refresh-token-result accepts a request token. The endpoint will check for its validity and will return a new access token.
If the refresh token had expired, log out the user. This is for security reasons and this is important!
Oh, and when a user logs out, ensure that both of your user's token and your user's refresh token is revoked properly, usually by changing the cookie value and the expiresIn attribute. For me, I usually change both cookie values to loggedOut and I'll set the expiresIn to five seconds.
Alternatively, if you're using React (an additional answer), you can do it like this:
If a user accesses your website, and the JWT expiration date is close to expiry, you can simply use a useEffect() hook in order to renew your access token.
TL;DR: Your second way is already good.
EDIT: Sample pseudocode to help you. Don't copy paste this right away, it most likely wouldn't work, but it should give you the general idea of how things work.
// middleware.js
const { token, refreshToken } = req.cookies;
// 1. If the token has not expired, call 'next()'
// assume 'isExpired' returns boolean: true or false depending on the state of your token.
if (!token.isExpired()) {
return next();
}
// 2. If the token has expired AND the refreshToken has not expired, issue a new token, THEN call 'next()'
if (token.isExpired() && !refreshToken.isExpired()) {
await issueToken();
return next();
}
// 3. Else, logout the user. I'll keep this one short.
await logoutUser();
res.status(401).json({
status: 'fail',
message: 'Your access has expired! Please log in again!',
});
And this is your controller.
// controller.js
const getAllComments = async (req, res, next) => {
const comments = await Comment.find();
res.status(200).json({
status: 'success',
data: comments,
});
}
And, this is what your route should look like.
// this import might be unresolved - keep in mind!
const middleware = require('./middleware');
const getAllComments = require('./controllers');
router.get('/api/v1/comments/', middleware,
checkToken, getAllComments); // assume checkToken is your function to check for a token's validity.
Keep in mind I did not include error handling to keep this example short.

Node.js Express Spotify API save in session

Question appeared while integrating Spotify API into Nodejs Express web application using spotify-web-api-node. How multiple simultaneous user requests should be handled? After passing the authentication step, user receives access_token, which is different for each user. Each request can have a session, for example using express-session since access_token is unique for each authenticated user. The weird thing is that I can't find an example with proper session usage in the description and samples https://www.npmjs.com/package/spotify-web-api-node where spotify-web-api-node is used. How is that possible to use global variable without session? Would it make full mess among separate user requests or I'm missing something? I guess that the access_token would be always replaced with latest authenticated user. Another usage example is here https://github.com/thelinmichael/spotify-web-api-node, though it also suggests to use one global instance.
the solution is to store the access_token and refresh_token after successful authentication in the session storage, than before calling Spotify API endpoints set both tokens for the current user from the present session:
saving tokens in the session after successful authentication:
app.get('/login', (req,res) => {
var scopes = [ ... ]
var authUrl = spotifyApi.createAuthorizeURL(scopes)
res.redirect(authUrl+"&show_dialog=true")
})
app.get('/callback', async (req, res) => {
const { code } = req.query
try {
var data = await spotifyApi.authorizationCodeGrant(code)
const { access_token, refresh_token } = data.body
spotifyApi.setAccessToken(access_token)
spotifyApi.setRefreshToken(refresh_token)
req.session.spotifyAccount = { access_token, refresh_token }
res.redirect('...')
} catch(err) {
res.send(`error ${err}`)
}
});
app.get('/userinfo', async (req,res) => {
try {
spotifyApi.setAccessToken(req.session.spotifyAccount["access_token"])
spotifyApi.setRefreshToken(req.session.spotifyAccount["refresh_token"])
var result = await spotifyApi.getMe()
console.log(result.body);
res.status(200).send(result.body)
} catch (err) {
res.status(400).send(err)
}
});
since access_token is only identification key which identifies any API request, that ensures that API endpoints are called for the current user. This technique prevents mess and confusion, so that each user can see and manipulate his data only.

How can you verify if a JWT is still valid?

I want to make a call every X amount of minutes from the client side to see if the JWT is still valid. I'm not sure how to do this in nodeJS. If I'm already authorized, how can i check if I'm still authorized.
An elegant solution to handle token expiration is when you set the token(in LocalStorage or store(redux), or both) is also to have an Async function that runs exactly when the token expires. Something like this:
const logUserOut = token =>{
setTimeout(()=> MyLogoutFunction(), token.expiresIn)
}
This way you make sure that the user won't be logged when the token is no longer valid.
You can have your client side decode the JWT and check an expiry field and compare it with system time.
eg.
isExpired: (token) => {
if (token && jwt.decode(token)) {
const expiry = jwt.decode(token).exp;
const now = new Date();
return now.getTime() > expiry * 1000;
}
return false;
you can use npm install jsonwebtoken or some other npm package on the client side to do this
Create and endpoint that verifies the token is valid. You can use the the jsonwebtoken package.
import jwt from 'jsonwebtoken';
const verifyToken = (req, res) => {
const token = req.headers.authorization;
jwt.verify(token, SECRET_KEY, (err, decoded) => {
if (err) {
return res.status(401).send();
}
// can do something with the decoded data
})
}
router.post('/verify-token', verifyToken);
I found a better option with promise to check if my token is valid
jwt.verify(token,key,(err,result)=>{
if(err){
if(err.name == "TokenExpiredError"){
console.log("Expired") //This case is when token expired
}
else{
console.log(err.name) //Any other case
}
}
else{
//Here code for your promise using 'result' when token is Valid
}
})
EDIT: this code is OK if you don't use the JWT for security reason, only if you use it to public stuff.
since this request is show to user if it valid or not, and if not why.
I'm using it only to understand if object is still available for other reason. (and it's OK to be public)

How to show different page if user is logged in via firebase

I have a slight problem, and it seems to be an easy one, but I cannot seem to wrap my head around what to do.
I have an express app, that uses Firebase to store data. I am able to login, register and log out trough a client side script, but my problem is: How do I check via express if a user is logged in, to be able to send a different page to the logged in users?
This is my code so far:
var firebase = require('firebase');
// Initialize Firebase
var config = {
serviceAccount: "./Chat Application-ad4eaaee3fcc.json",
databaseURL: "MY_DATABASE_URL"
};
firebase.initializeApp(config);
and then I want to show a special page for logged in users, and this is what I have tried:
router.get("/special-page", function(req, res, next) {
var user = firebase.auth().currentUser;
console.log(user); // this variable gets undefined
if(user) {
res.render("special-page");
} else {
res.redirect("/");
}
});
I know this might seem like an easy question, but any help would be much appreciated!
Thanks in advance.
The user side, and server side, are completely different execution areas. Hence, as you probably guessed, calling firebase.auth().currentUser on the server cannot work if the authentication occurred on the client.
The server process just does not have this information, unless the client tells him.
You could just have a request header telling "i am logged as XXX", but it would not be secure, because the server would not be able to verify that information, and a malicious user could pretend to be another one.
The only solution to this, in your use case, is to provide the Firebase token to the server, and then the server needs to verify this token against firebase server, and only then it will be 100% sure about the client authentication.
I needed that in my React app for Server Side Rendering, here is how I did it.
Upon user authentication, set a cookie that contains the firebase token
Unset the cookie when the users logs out
In the server, read the cookie to authenticate client user at each request
Code in the client :
const setAppCookie = () => firebase.auth().currentUser &&
firebase.auth().currentUser.getToken().then(token => {
cookies.set('token', token, {
domain: window.location.hostname,
expire: 1 / 24, // One hour
path: '/',
secure: true // If served over HTTPS
});
});
const unsetAppCookie = () =>
cookies.remove('token', {
domain: window.location.hostname,
path: '/',
});
// triggered by firebase auth changes, this is where you deal
// with your users authentication in your app
fbAuth.onAuthStateChanged(user => {
if (!user) {
// user is logged out
return;
}
// user is logged in
setAppCookie();
// Reset cookie before hour expires
// (firebase tokens are short lived, say the docs)
setInterval(setAppCookie, 3500);
});
[...]
// In the logout code
unsetAppCookie();
Code in the server:
// Before serving express app, enable cookie parsing
app.use(cookieParser());
// In the code dealing with your requests
const { token } = req.cookies;
if (!token) {
// renderWithoutUser();
}
//
// If user found in cookie, verify the token and render with logged in store
//
console.log('Verifying token', token);
firebase.auth().verifyIdToken(token)
.then(decodedToken => {
const uid = decodedToken.sub;
console.log('User is authenticated for this request', uid);
// renderWithUser();
})
.catch(err => {
console.error('WARNING token invalid or user not found', err);
// renderWithoutUser();
});

Save Token in local Storage using node

I'm using JWT ("jsonwebtoken": "^5.4.0") with express 4 and jade.
I'm able to create the right Token, but How can i Pass this token in each call?
Where I have to store this token ? in headers or in localStorage?
For now I'm using CURL with Postman, and Set token in header in
x-access-token
Have I Do create a middleware that retrieve a token from Database and use this in each call?
thanks
You do not need to save and check the token from the database. This token such a mechanism can be decoded with only your-server, and if it was done that the token is valid. The code that you want to do should look like.
var cookieParser = require('cookie-parser')
app.use(cookieParser())
app.get('/login', function(req, res, next) {
var user = {name:'test'}; //!! find the user and check user from db then
var token = jwt.sign(user, 'secret', {
expiresInMinutes: 1440
});
res.cookie('auth',token);
res.send('ok');
});
app.use(function(req, res, next) {
var token = req.cookies.auth;
// decode token
if (token) {
jwt.verify(token, 'secret', function(err, token_data) {
if (err) {
return res.status(403).send('Error');
} else {
req.user_data = token_data;
next();
}
});
} else {
return res.status(403).send('No token');
}
});
Here you can find very nice article : https://scotch.io/tutorials/authenticate-a-node-js-api-with-json-web-tokens
I would recommend checking this out if you want local storage: https://www.npmjs.com/package/node-localstorage
But, with that said, you guys and girls wouldn't believe how long it took me to find res.cookie('auth' token) from the above answer. I scoured Google for hours, Passport docs, Express docs, GraphQL and authentication/authorization docs in an effort to find out how to get the token to the API in a stateless manner.
I already built JWT token security and secured my GraphQL resolvers with it, but then, I opted to use EJS along with graphql-request (approx same as Apollo Client), so I needed to find a way to pass the token to my middleware without using a server side session.
Storing a JWT token in cookies is fine especially if you take extra precautions such as signing the cookie, and I recall there are also options you can include that keep the cookie secure, so that other sites cannot see it if the "browser" allows access to cookies. If a cookie is signed with your server secret, the data inside the cookie simply cannot be altered and still be valid. The risk is always still someone leaking their token/cookie, and if that bothers you, do research into refresh tokens. However, API tokens are generally and should be kept tightly secret and safe. Your biggest annoyance will more likely be the requirement to maintain a blacklist of JWTs that expire a year from now if you set expiry to 1y.
I am just including my findings here because this question is actually a rare resource it seems...
Here is my Express middleware for authentication:
// AUTHENTICATION
app.use(async (req) => {
try {
const token = req.headers.authorization || req.cookies.auth
const { person } = await jwt.verify(token, SECRET)
req.person = person
return req.next()
} catch (e) {
return req.next()
}
})
You can see I am setting the token from the header with cookie as fallback. This supports my needs fine and allows me to use really any client with stateless security.
My logged in user is available as req.person in my views and GraphQL resolvers. If req.person is not set, the user is treated as not-logged-in.
I am using return req.next() which is important to note because calling next() without parameters is treated as "clean go-to next middleware and/or proceed to process request". If you include any string or object parameter, it will throw an error that can bubble down to your error handling middleware. You can try it yourself. Put return next('You are not authenticated.') in the catch block and you will see it halt the request before your route.
I use return next() because I handle authorization in the routes and in my resolvers. It allows more flexibility such as facilitating register and login mutations to be accessed by non-authenticated users.
Here is my GraphQL endpoint (I am using Apollo Server):
app.use('/graphql', bodyParser.json(), graphqlExpress((req) => {
const context = {
person: req.person
}
return {
schema,
context,
rootValue: null
}
}))
In my GraphQL resolvers, the third parameter of every query has context.person populated with req.person which comes from the above Authentication middleware.
That is really all a person needs to know.
Here is how I am using the NPM package called graphql-request:
https://www.npmjs.com/package/graphql-request
app.get('/allpeople', async (req, res) => {
try {
const client = new GraphQLClient(GRAPHQL_ENDPOINT, {
headers: { Authorization: req.headers.authorization || req.cookies.auth }
})
const query = `query allPeople($serialNumber: String!) {
allPeople(serialNumber: $serialNumber) {
id
created
status
email
}
}`
const variables = {
serialNumber: req.person
}
const response = await client.request(query, variables)
res.render('allpeople/list', { people: response.allPeople })
} catch (e) {
throw [`allPeople`, `${JSON.stringify(error, null, 2)}`]
}
})
I include this code because there are no "more advanced" example usages of graphql-request, and I like it so far. It is very concise and could easily be swapped out for Apollo Client if you venture into React.js. My examples here are also very relevant for anyone researching createNetworkInterface and new ApolloClient().

Resources