Reputation: 155
For example, when I log in I create an access token with payload { 'userId': 1, '2fa': false}
(/login
route), then I do another route '/login/auth' that checks for example if a one time password is correct, if it is I created another jwt but this time with 2fa set to true. Then proceeding routes will check whether 2fa is true to run. If not, it will error. Is this a proper way to do this or hacky?
// middleware
const auth = (req, res, next) => {
const token = req.header('access-token');
if (!token) return res.status(401).json('Not Authorized');
try {
const payload = jwt.verify(token, 'secret1');
req.payload = payload
next();
} catch (ex) {
res.status(400).json('Invalid.');
}
};
// routes
router.post(
'/login',
async (req, res) => {
try {
/* login database stuff goes here if successful creates access token
*/
const token = jwt.sign(
{ userId, twoFactorAuthenticated: false },
'secret1',
);
res.status(200).json(token);
} catch (e) {
console.log(e);
return res.status(500).json(e);
}
},
);
router.post(
'/login/auth2',
auth,
async (req, res) => {
try {
/* verifying two factor auth logic goes here
* if succesful approves the 2fa
*/
const token = jwt.sign(
{ userId, twoFactorAuthenticated: true },
'secret1',
);
res.status(200).json(token);
} catch (e) {
console.log(e);
return res.status(500).json(e);
}
},
);
router.post(
'/some-route',
auth,
async (req, res) => {
try {
if(!req.payload.twoFactorAuthenticated)
return res.status(400).json('user has not completed second factor auth')
} catch (e) {
console.log(e);
return res.status(500).json(e);
}
},
);
My use case doesn't really require login authorization but for something different but same concept applies.
Upvotes: 1
Views: 1184
Reputation: 2676
The process you are suggesting will work, but it can be improved.
Set JWT claims
Since the payload of JWT is not encrypted, it is only meant for storing non sensitive that is usefull in the verifing process. Although the payload is not encrypted, it is signed and cannot be changed without knowing the secret or private key.
There are a number of standard claims which might be useful, besides your own custom twoFactorAuthenticated
claim. You always should set an expiration time (exp) on your tokens. For the userId
you may use the subject claim (sub).
Use middleware for authentication of routes
Once the two factor authentication is done, you want to check the JWT on all protected routes. Now, you have put the check in the ‘some-route’ controller itself, but it is preferable to define middleware for this if you have more then one protected route.
Use a http only cookie to store JWT with a short life time
In your solution you send the token to your client and let the client add it to the authentication header. This probably means that the token will be stored in local storage. It may be more secure to use a secure http cookie for this, with a short life time, like 10 minutes or so. This means that if the token is compromised, your API is vulnerable for a maximum of 10 minutes. For a user, having to authenticate every 10 minutes is a horrible UX. So that’s why you may want to implement refresh tokens.
Use refresh token and use it only once
In your example, after the twofactor authentication is verified, you send a token back with the twoFactorAuthenticated
set to true
. As suggested before, you can send this in a secure cookie with a short life time instead. At the same time, you generate a refresh token with a much longer expire time, e.g. 4 hours. When the cookie expires after 10 minutes, the client can use the refresh token once to get a new cookie and a new refresh token.
Important:
This requires some implementation on client and server side, but I think it makes your authentication process much more solid.
Upvotes: 7