JWT Silent Refreshing based on Redis - MERN Stack - node.js

I've been struggling so long with silent refreshing to make it the most transparent to client.
I've implemented so many strategies so far and still I have no clue how solve it fully.
When I perform silent refreshing I put my recent generated refresh token in redis db for further access token generation so as to it carries out refreshing for both everytime when there is a need to refresh.
Due to React Strict Mode in dev mode, request is being made twice so first time it can be pulled out from db and showed normally but consecutive request acts like value in db doesn't exist and it spits null value.
My middleware is written in express js, I put my code beneath:
const authRefresh = async (req, res) => {
try {
const refreshToken = await req.cookies['refresh-token'];
const accessToken = await req.cookies['access-token'];
if (!refreshToken) {
return res.status(401).json({ status: 'error', message: "Refresh token not found" })
}
if (!accessToken || accessToken.includes('mockup'))
jwt.verify(refreshToken, process.env.JWT_REFRESH, async (err, userProperties) => {
if (err) {
return res.status(401).json(err)
}
const redisValue = await redisClient.get(refreshToken);
if (redisValue) {
const tokens = await generateToken(userProperties)
//setting tokens in httpOnly cookies
res.cookie('access-token', tokens.accessToken,
cookiesTime.access);
res.cookie('refresh-token', tokens.refreshToken,
cookiesTime.refresh);
await redisClient.del(redisValue);
await redisClient.set(tokens.refreshToken, tokens.refreshToken);
await redisClient.expire(tokens.refreshToken, time_refresh);
return res.status(200).json('Tokens set again')
}
else {
return res.status(401).json('No refresh token in db');
}
})
else return res.status(202).json('No need to refresh an access token')
} catch (err) {
return res.status(401).json({ status: 'error', message: err })
}
}
PS. I want to mention I've tried rearrange redis methods from asynchronous to synchronous and backwards. It seems the same for my action.

Related

CouchDB (nano) randomly returns `You are not allowed to access this db.`

I've integrated a user authentication and it already works very well. (Authenticates and returns a JWT in cookie.)
But for some reasons AFTER I logged in with my user and I want to establish a connection to the database I get a 403. The request itself isnt even protected. No verification if user is logged in or anything. Its a public request.
The funny part is, if I restart the node express application the exact same request goes through.
So it seems that anything within the process.
My login function:
const login = async (username, password) => {
try {
const response = await connection.auth(username, password)
if (!response.ok) {
return [401, null]
}
const token = jwt.sign({ sub: username }, secret, { expiresIn: '1h' })
return [null, token]
} catch (error) {
return [error, null]
}
}
This one gets called right after and I run into the catch block
try {
const conn = await nano('http://admin:password#127.0.0.1:5984')
const db = await conn.use('test')
[…]
} catch (error) {
// I LAND HERE
console.error(error)
return [error, null]
}
}
Okay I figured it out myself.
Looks like 403 was sent because it overrides your current session. What I did was basically authenticate again right after "login" was successful but this time I used admin and password again. Kinda ugly but it works.
const response = await connection.auth(username, password)
if (!response.ok) {
return [401, null]
}
await connection.auth('admin', 'password') // < server credentials

MSAL Node / Electron - PublicClientApplication.acquireTokenByCode() accessing Tokens despite clearing accounts

I'm trying to clear all access Tokens from Main.Ts in an Electron App without success.
On the Node side of the App, I have a login function that implements the Auth Code Flow with PKCE - which works fine:
let login = async () => {
try {
const authResult = await getTokenInteractive(authCodeUrlParams);
mainWindow.webContents.send(IpcMessages.XXX-XXXX, authResult);
} catch (error) {
console.log(error);
}
};
let getTokenInteractive = async (
tokenRequest: AuthorizationUrlRequest
): Promise\<AuthenticationResult\> =\> {
try {
...
// Generate PKCE Challenge and Verifier before request
const cryptoProvider = new CryptoProvider();
const { challenge, verifier } = await cryptoProvider.generatePkceCodes();
...
// Get Auth Code URL
const authCodeUrl = await clientApplication.getAuthCodeUrl(
localAuthCodeUrlParams
);
const authCode = await listenForAuthCode(authCodeUrl, authWindow);
...
// This works fine
const authResult: AuthenticationResult =
await clientApplication.acquireTokenByCode({
...authCodeRequest,
code: authCode,
codeVerifier: verifier,
});
return authResult;
} catch (error) {
throw error;
}
};
The logout function also clears the accounts:
const logout = async () =\> {
try {
console.log("LOGOUT RECEIVED");
const accounts: AccountInfo\[\] = await clientApplication
.getTokenCache()
.getAllAccounts();
if (accounts && accounts.length \> 0) console.log(accounts);
await clientApplication.getTokenCache().removeAccount(accounts\[0\]);
// Test to see if accounts are cleared returns '[]'
const checkAccounts: AccountInfo[] = await clientApplication
.getTokenCache()
.getAllAccounts();
console.log(checkAccounts);
mainWindow.webContents.send(IpcMessages.USER_LOGGED_OUT);
} catch (err) {
console.log("Error in main.logout ", err);
}
};
Unfortunately, acquireTokenByCode still accesses the Token (?? How) when the user is redirected to a login page.
ps I've also checked local storage, Cookies etc in the browser and they are never populated (ie always remain empty).
I've seen numerous answers suggesting using a logout URL:
https://learn.microsoft.com/en-us/answers/questions/263335/msal-sign-out-does-not-appear-to-clear-cache.html
https://learn.microsoft.com/en-us/answers/questions/994601/logout-does-not-clear-session.html
However, these solutions down't work with Azure B2C. Also, redirecting using the logout endpoint using Electron.shell(openExternal([url])) also does nothing.
It's unbelievable that MS hasn't implemented a simple logout solution.
Question: Does anyone know how to stop tokens from being acquired WITHOUT having to open a native browser window or populate the Browser Window with a "Successfully signed out. You can now close this window" prompt ?
Thanks in advance.

Destroy JsonWebToken on logout request in Node.js

I want to destroy the JWT whenever user sends the logout request to the app.
First of all, I am not storing JWT in the database so I can not delete that and I am also not using cookies or sessions. Is JWT stored on the client side? If so how can I destroy the JWT and invalidate the user's requests after logging out of the app.
The token middleware:
module.exports = middlewares = {
authenticateToken: async (req, res, next) => {
try {
if (!req.headers["x-access-token"]) {
return res.status(401).json({
error: "Key x-access-token not found",
});
}
if (req.headers["x-access-token"] === "") {
return res.status(401).json({
error: "Token not found",
});
}
const token = req.headers["x-access-token"];
const data = jwt.verify(token, keys.JWToken);
if (!data) return res.status(401).json({ error: "Invalid token" });
req.data = data;
next();
} catch (err) {
return res.status(400).json({ error: err.message });
}
},
};
Here's how I generate token on the registration and login requests:
const payload = { id: new_user._id };
const JWToken = jwt.sign(payload, keys.JWToken, { expiresIn: 31556926 });
The code provided is from the server, hence I don't know how its being saved on the client side, usually it is done using localhost, cookie or session. But you have mentioned that you are not using cookie or session, hence there is a chance that you are using local storage to store the jwt token. You can check your local storage on chrome by going to developer options -> Application -> Local Storage. You may find your token by how you named it, you can access it and delete by localStorage.removeItem("name of your token");

Delete User and Logout that user from all Devices

I wanted to implement a feature in my app. Where an Admin can delete the user. So basically the delete is working fine but somehow i cannot logout the logged in user. Let me explain it more briefly, Suppose there is a User A which is currently using my app and the admin decided to remove that user from the app so they can't no longer access the features of the app. To remove the user i can call an API and delete that user but if i completely delete the user it loses all the access to the API's call coz user with the certain ID isn't available anymore and the app breaks coz the API call will fail for that deleted User. So I was wondering is there anyway to logout the user after admin deletes it.
The Frontend is on ReactJs and Backend is on NodeJs. And i am using JWT for authentication. Any help will be appreciated and if this question isn't clear enough please let me know so i can explain it more.
In backend in every protected route you should verify the token and token should contain user id or email using that you will verify the token. After deleting the user throw error with no user found and in frontend make sure if there are the error no user found then it will delete the JWT token.
What comes into my mind is to put a middleware between your requests and server. By doing so, instead of trying to log out from all devices, we will not allow any action if user does not exist; in this very example, we will prevent the user to delete a place and toast a message on the front end. I will share an example of that, but you need to tweak the code according to your needs.
Http Error Model
class HttpError extends Error {
constructor(message, errorCode) {
super(message);
this.code = errorCode;
}
}
module.exports = HttpError;
Middleware
const HttpError = require('../models/http-error');
module.exports = (req, res, next) => {
try {
// Check user if exists
User.findById(req.userData.userId).exec(function (error, user) {
if (error) {
throw new Error('Authentication failed!');
}
else {
return next();
}
});
}
catch (error) {
return next(new HttpError('Authentication failed!', 403));
}
};
Route
const express = require('express');
const router = express.Router();
const checkAuth = require('../middleware/check-auth');
router.use(checkAuth);
// Put after any routes that you want the user to be logged in
router.delete('/:placeId', placesControllers.deletePlace); //e.x.
...
module.exports = router;
E.x. controller (with MongoDB)
const deletePlace = async (req, res, next) => {
const placeId = req.params.placeId;
let foundPlace;
try {
foundPlace = await Place.findById(placeId).populate('userId').exec();
}
catch (error) {
return next(new HttpError('Could not find the place, please try again', 500));
}
// Delete place
res.status(200).json({message: 'Deleted place'});
};
FRONT END PART
import toastr from 'toastr';
....
try {
const response = await fetch(url, {method, body, headers});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message);
}
}
catch(error) {
// handle the error, user not found
console.log(error.message);
toastr.error(error.message, 'Error', {
closeButton: true,
positionClass: 'toast-top-right',
timeOut: 2000,
extendedTimeOut: 1,
});
}

Best way to handle multiple functions/scenarios inside an express route?

I have many endpoints in my express app that have many conditions. I want to find what is the best design pattern for them without repeating myself so much.
This is one of my simpler routes:
router.post('/reset/:token',
asyncMiddleware(async(req, res, next) => { await reset(req, res, next, pino); })
);
Inside reset()I need to check a couple of things, such as:
If all the required body params are there
If the email from the decrypted token matches the one from the database
If the password was saved successfully.
I would like to check those conditions that without having a huge function, but I don't know what is the best way to do so.
Entire Route Code
export async function reset(req, res, next) {
const email = req.body.email;
if (!email) return res.status(400).json(Error.paramsMissing('email'));
const user = await userAssociatedWithEmail(req.body.email);
if (!user) {
return res.status(501).json(Error.noActiveUserAssociatedWithEmail);
}
// Generate token
const token = await jwt.sign({ email: user.email, id: user.id }, 'shhhhh');
const emailSent = await sendForgotEmail(token, user);
if (!emailSent) return res.status(500).json(Error.emailNotSent);
else return res.json({ status: 'success', message: 'Email sent successfully.' });
}
What I would like to do
Final Result I'd like to have
export async function reset(req, res, next) {
const email = req.body.email;
if (!email) return res.status(400).json(Error.paramsMissing('email'));
// If error inside userAssociatedWithEmail, I'd like to stop execution and
// return res.status(501).json(Error.noActiveUserAssociatedWithEmail) from inside
// that function, without having to add an if condition below as exists in the
// original code above
const user = await userAssociatedWithEmail(req.body.email);
const token = await jwt.sign({ email: user.email, id: user.id }, 'shhhhh');
// Again I'd like to return res.status(500).json(Error.emailNotSent)
// from inside sendForgotEmail IF there is an error
const emailSent = await sendForgotEmail(token, user);
// If everything is successful, finally I'd return this
return res.json({ status: 'success', message: 'Email sent successfully.' });
}
Explanation of the result in word:
I'd like to be able to handle the conditions and scenarios without having to handle it in the main reset function if that's possible (aka, without having to store a response in a variable, check the variable and return in the main function in the case of error).
So for example, instead of:
const allParamsAreValid = validParams(token, email, new_password, res);
if (!allParamsAreValid) return;
I'd like to do something like:
validateParams(token, email, new_password, res);
And then inside validateParams() if a param is missing, I'd force exit the program besides also setting the response with res.json({}).
Is that possible?
You can make all your asynchronous functions that return promises reject their promise with the status and value you want sent. Then, you can handle that rejected promise in one place:
export async function reset(req, res, next) {
try {
const email = req.body.email;
if (!email) return res.status(400).json(Error.paramsMissing('email'));
// If error inside userAssociatedWithEmail, I'd like to stop execution and
// return res.status(501).json(Error.noActiveUserAssociatedWithEmail) from inside
// that function, without having to add an if condition below as exists in the
// original code above
const user = await userAssociatedWithEmail(req.body.email);
const token = await jwt.sign({ email: user.email, id: user.id }, 'shhhhh');
// Again I'd like to return res.status(500).json(Error.emailNotSent)
// from inside sendForgotEmail IF there is an error
const emailSent = await sendForgotEmail(token, user);
// If everything is successful, finally I'd return this
res.json({ status: 'success', message: 'Email sent successfully.' });
} catch(e) {
res.status(e.status || 500).json(e.errData)
}
}
And, then all of your asynchronous functions would reject if they have an error condition and set both e.status and e.errData on the rejected reason. That would allow you to have one common error handler and let the async function collect any rejected promise into your try/catch for you. This is meant to be the clean way you handle rejections in a series of await calls where you want the whole function to finish.
Then, you also need to make sure your asyncMiddleware() function is NOT also sending a response (can't really tell what its purpose is). You don't show that code, so I can't see what it's doing.
You don't show any code that uses validateParams(), but if it was synchronous, then it could just throw an exception with the right fields set on it and the try/catch would also catch it just like it would catch the async rejections.
For example:
function validateParams(token, email, new_password) {
let err = new Error();
err.errData = {status: 'error'};
if (!token) {
err.errData.message = 'invalid token';
throw err;
}
if (!email) {
err.errData = Error.paramsMissing('email');
throw err;
}
if (!new_password) {
err.errData.message = 'invalid new password');
throw err;
}
}
If you wanted to, you could also send an error response in validateParams(), but I think it's cleaner not to because they you can collect all errors including all your await asynchronous calls in one try/catch in the route handler and frankly, it's a lot more readable and understandable code not to send a response in some function calls, but not in others. I try to keep all my responses both error and success sent at the same level. Then, it's really easy to keep track of and to avoid accidentally trying to send multiple responses.
Then, in your route handler, you'd just call validateParams(...) just like that. If it throws, your try/catch would catch it and send the appropriate error. If no error, then execution would just continue.
Since you are passing res object to the validateParams method, you can do something like this,
async function validateParams(token, email, new_password, res) {
if (token && emal && new_password && res) {
// all values are available
// perform your desired operation
} else {
// exit from the method and pass info to the client
return res.json({ message: 'Invalid parameter' });
}
}
In this case, all you have to do is invoking the validateParams.
await validateParams(token, email, new_password, res);
If there is a missing parameter, the server passes the control to the client immediately. Otherwise, you can perform your operation there.

Resources