JWT Refresh Tokens
Thoughts and notes on implementing JWT Auth tokens with refresh tokens
Basic Implementation
- On login (calling to
/login), generate a JWT auth token, as well as a refresh token (which could just be a UUID, doesn’t need to be JWT-based)- The JWT token should have a short expiration date (e.g., 15mins)
- The refresh token can have a longer life time; depends on implementation, but maybe something like 10hrs?
- The table associating a token with a user should keep track of a couple of things:
created_date: When it was created (and potentially when it expires, if you’d like; that, or calculate that in business logic in the code based on time of creation)active: Is the token active or not? This will allow for immediate blacklisting of potentially-hijacked tokens
- Store the refresh token in a DB table associated with a specific user, and return both to the client
- The client will utilize the JWT auth token as the portion of the
Authorizationheader- E.g.,
Authorization: Bearer: AUTH_JWT_TOKEN_HERE
- E.g.,
- Refresh the auth token by calling to a route like
/refresh-tokenpassing the refresh token in the body (unless it’s originally stored as anHttpCookie, in which case it’s automatically sent back up with the call). This route returns a newly-created JWT with updated expiration & a refreshed refresh token (which should be stored in the DB).- We’ll validate the refresh token and that it’s still active / not expired
Securely Storing Tokens
Seems like there are different opinions on the most secure way to store the tokens in the client. Here are some prevailing thoughts:
- Don’t persist the auth token at all outside of the browser / client memory
- Store the refresh token in an
HttpOnlycookie- I’m not sure how I feel about this, as I’m trying to avoid cookies in general.
- This does make things easier as the client doesn’t need to worry at all about persisting the refresh token separately, or about explicitly passing the refresh token up to the
/refresh-tokenroute
Cookies or No?
According to a couple of places, a “secure” way of storing the refresh token is in an HttpOnly cookie. This will work just fine for browser-based applications; however, will it work on native (or React Native) applications?
- According to one Reddit post (see Further Reading section below), it looks like storing the refresh token in an
HttpOnlycookie won’t work. It’ll be lost on each app close, which is not ideal. - Yet, according to a GitHub thread (also linked below) it appears that you can in fact retrieve an
HttpOnlycookie via native code, which you can bridge into React Native.
Given that this is the case, and the security benefits of the HttpOnly cookie (not to mention the ease of not having to manage storing another token), I’m leaning towards implementing the refresh token storage via cookies.
How to Set an HttpOnly Cookie in Express (Node)
If I’m going to pursue using this, here’s some example code for how to do it using Express:
Setting Cookie in response:
NOTE: This requires the use of
cookie-parser, an additional middleware package.
const cookieConfig = {
httpOnly: true, // to disable accessing cookie via client side js
//secure: true, // to force https (if you use it)
maxAge: 1000000000, // ttl in ms (remove this option and cookie will die when browser is closed)
signed: true, // if you use the secret with cookieParser
};
res.cookie("test", "some value", cookieConfig);
res.send("set cookie");
Retrieving Cookie in Request:
const signedCookies = req.signedCookies; // get signed cookies
console.log("signed-cookies:", signedCookies);
const cookies = req.cookies; // get not signed cookies
console.log("not-signed-cookies:", cookies);
// or access directly to one cookie by its name :
const myTestCookie = req.signedCookies.test;
console.log("our test signed cookie:", myTestCookie);
Setting Expiration Dates on JWT
JWTs have their expiration by setting the exp claim on the payload. This claim specifically is set as the seconds since the Unix epoch of the date in which the token expires. E.g.:
// Sets the token to expire in 1hr
jwt.sign(
{
exp: Math.floor(Date.now() / 1000) + 60 * 60,
data: "foobar",
},
"secret",
);
However, using the Node package node-jsonwebtoken, you can set it even easier with their options argument in the sign() function:
jwt.sign(
{
data: "foobar",
},
"secret",
{ expiresIn: 60 * 60 },
);
//or even better:
jwt.sign(
{
data: "foobar",
},
"secret",
{ expiresIn: "1h" },
);
Managing “Remember Me” Sessions
TODO: Add more here.
Further Reading
- Hasura’s Explanation
- Reddit Thread on Storing Cookies in React Native Apps
- GitHub Post Outlining That Retrieving HttpOnly Cookies is Possible in Native Code
- Setting Cookies in Node
- Token Expiration