bojagi
Published in

bojagi

using refresh tokens in node to stay authenticated

— — This article was originally published under https://blog.hyphe.me on Tue, 13 Oct 2015 — —

In this article we will take a look at how to use refresh tokens in node.js for permanent user sessions. It covers a lot of ground. When we’re finished, we will be able to:

  • authenticate via passport and it’s strategies
  • authenticate via access tokens (for api calls)
  • regenerate access tokens with refresh tokens to be able to stay logged in
  • handle multiple user sessions (a user may be logged in on different devices)
  • reject single sessions

This article is part two of two related posts:

A fully configured example can be found on bitbucket.

Authentication route

Flow of the route

To be able to handle all the new requirements, we need to add some things to our previous /auth route. Besides serializing an user and generating the access token, we need to create a specific user session and a refresh token, with which this session can be kept alive.

The first two steps (authenticate and serializeUser) as well as generating the access token are pretty much the same of the related post before and we don’t need to change much.

New is serializing a client and the generation of refresh tokens.

Serializing clients

To handle multiple user sessions, we need an identifier for each logged in client. After authentication of the user, we create a new client on each (permanent) login.

function serializeClient(req, res, next) {  
if (req.query.permanent === ‘true’) {
db.client.updateOrCreate({
user: req.user
}, function(err, client) {
if (err) {
return next(err);
}
// we store information needed in req.user
req.user.clientId = client.id;
next();
});
} else {
next();
}
}

After the client is created, we store it’s id in our req.user object because we need it in our access-token to identify the specific user session.

Creating and store refresh tokens

Refresh tokens are quite different to access tokens. In fact, they are similar to classic session cookies as they are stored in a database and don’t contain any information in itself.

function generateRefreshToken(req, res, next) {  
if (req.query.permanent === ‘true’) {
req.token.refreshToken = req.user.clientId.toString() + ‘.’ + crypto.randomBytes(
40).toString(‘hex’);
db.client.storeToken({
id: req.user.clientId,
refreshToken: req.token.refreshToken
}, next);
} else {
next();
}
}

To create our refresh tokens (they are basically a long random string), we use the crypto module of node. To be sure, the token is unique, we add the previous created client-id to the token.
After that we store the token in the client database and in req.token to send it back to the user when the whole process is finished.

That’s pretty much it. With these little additions, we have multiple user sessions enabled as well as a way of refreshing access tokens.

Let’s give it a try by starting the bitbucket example and running the following request:

curl -X POST -H ‘Content-Type: application/json’ -d ‘{ “username”: “devils name”, “password”: “666” }’ localhost:1337/auth?permanent=true

{
“token”: {
“accessToken”: “classic.access.token”,
“refreshToken”: “0.someRandomString”
},
“user”: {
“id”: 666,
“clientId”: 0
}
}

As we can see, a client and a refresh token are created. We can make protected api calls with the accessToken.

Note: We implemented serializeClient and generateRefreshToken in a way, that a session can be either permanent (like in this example) or not. If you don’t set the permanent=true flag in the url, no client or refresh token will be created and the session ends once the access token turns invalid.

Refresh an access token

Once an access token is no longer valid, we need to create a new one by using the refresh token. That’s quite easy. We use the refresh token to authenticate the user-(session).

function validateRefreshToken(req, res, next) {  
db.client.findUserOfToken(req.body, function(err, user) {
if (err) {
return next(err);
}
req.user = user;
next();
});
}
// express
app.post(‘/token’, validateRefreshToken, generateAccessToken,
function(req, res) {
res.status(201).json({
token: req.token
});
});

To do this, we need to make a database call and search for the given refresh token. If we find it, the user is authenticated and we can read out the user and client id and store it once again in req.user.

With this information, we can generate a new access token and send it back to the user.

curl -X POST -H ‘Content-Type: application/json’ -d ‘{ “refreshToken”: “0.someRandomString”}’ localhost:1337/token

{
“token”: {
“accessToken”: “new.access.token”
}
}

Reject a refresh token

To end a session, we need to reject a token. In this example, we just delete the client entry which holds the token.

app.post(‘/token/reject’, function(req, res, next) {  
db.client.rejectToken(req.body, next);
}, function(req, res) {
res.status(204).end();
});

curl -X POST -H ‘Content-Type: application/json’ -d ‘{ “refreshToken”: “0.someRandomString”}’ localhost:1337/token/reject

=> 204 — no content

If we try to refresh an access token now, we get an not found error:

curl -X POST -H ‘Content-Type: application/json’ -d ‘{ “refreshToken”: “0.someRandomString”}’ localhost:1337/token

=> 500 Error: not found

Note: I was to lazy to implement a proper error handling for this example, so the error responses are a bit ugly (and are always 500).

Conclusion

Token based authentication isn’t that difficult!

We are able to protect our api with access tokens. Permanent sessions are achieved by implementing refresh tokens. These tokens are stored in a database and can be rejected to end the session.
By assigning them to specific client ids we are able to provide multiple user sessions. These clients come in handy when speaking about notifications later on.

As always, feel free to leave suggestions in the comment section below.

The original discussion can be found on disqus
https://disqus.com/home/discussion/hypheblog/using_refresh_tokens_to_stay_authenticated_in_node_24/

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store