Token Refresh in Authentication plays a crucial role in the OAuth Token Refresh model, which is one of the most popular and reliable mechanisms for addressing authentication/authorization in platforms and API. This article will delve into the workings of this model, illustrating how it substantially enhances security by protecting your platforms from breaches and exploitation.
First, we will briefly go through the basics of token-based security for APIs and platforms. Then we will explain what a refresh token is in detail and review the differences between an access token and a refresh token. After that, we will explain why refresh tokens are crucial to ensure token-based security protection. Finally, we will put the knowledge to good use by implementing an example of a refresh token mechanism in Node.js that you can use and test on your browser.
Alright, let’s get into it.
The Basics of Token-Based Security
The basis of token-based security is founded on the principle of bearer authentication. This implies that any entity is granted access to a resource by showing ownership of a “Token” or key an authority provided during authorization.
This means that as long as you have a token identifying you as an authorized entity and the token is valid and has not expired, you can consume the resource as much as you want.
In essence, tokens are data elements that hold the minimum amount of information to enable the process of determining the identity of a user and authorizing their actions.
Standard identity protocols use token-based mechanisms like OAuth 2.0 to secure access to resources and applications. OAuth 2.0 is one of the most prevalent authorization frameworks available, and it uses a combination of Access Tokens and Refresh Tokens.
What is an Access Token?
In an OAuth 2.0 compliant server, the server issues an access token at the moment of authentication. As said previously, this token allows client applications to make secure calls to the server API by signaling that it has acquired authorization from the user to perform specific tasks or access resources on behalf of the user.
Tokens have a lifecycle, and for access tokens, their lifespan is usually short, between 30 minutes to a day, depending on the server’s security settings. This security measure ensures that compromised tokens only provide access for a short time window.
So, does that mean the user must provide their credentials every time the token expires? Well, no, as long as you are implementing refresh tokens.
What is Token Refresh in Authentication?
As the official OAuth 2.0 specification states, “An OAuth Refresh Token is a string that the OAuth client can use to get a new access token without the user’s interaction.”
What this means is that a refresh token is a means to renew or regain authentication status from the server without the need for credentials. This is important because you would want to reduce the friction of authentication requests that reach the user for credentials as it creates a bad user experience and hinders the overall flow of interaction.
What is the difference between an access token and a refresh token?
The most crucial difference between access tokens and refresh tokens is their purpose. Even though they are very similar in composition, they are used for very different ends.
An access token acts as a credential artifact or certificate to grant access to protected resources. As a result, malicious users could compromise a system and misappropriate access tokens. They could then use them to access protected resources.
However, a refresh token does not grant access to any resource other than the mechanism to renew or generate new access tokens.
As stated on the OAuth website, “A refresh token must not allow the client to gain any access beyond the scope of the original grant. The refresh token exists to enable authorization servers to use short lifetimes for access tokens without needing to involve the user when the token expires.”
Additionally, refresh tokens have a much longer lifespan than access tokens due to their function as the means to renew these tokens.
What happens when the refresh token expires?
But what happens when a refresh token expires then? Well, simple. The user is asked to provide credentials for authentication.
From a security perspective, the primary purpose of a refresh token is to segregate the means of authentication from authorization and prevent the unnecessary exposure of the credentials that grant authentication while reducing the need to nag the user for credentials. It achieves this by serving as a sort of private key that is only exposed when needed (when the access token has expired) and while staying valid for a period that the user will not feel inconvenient to re-authenticate.
Why are Refresh Tokens Important?
One of the main reasons tokens are such a robust authentication/authorization mechanism is their capacity to be expired.
However, without a refresh token mechanism in place, the token model doesn’t hold up.
What if, for example, a bad actor misappropriates the token? They can essentially impersonate you and gain access without any authentication required, right? That would not be good.
Since the access token has a short expiration time, in the unlikely event that an attacker gains access to a valid token, without a refresh token, an attacker only has a small window to use them.
Additionally, refresh tokens are less likely to be stolen since they are not exposed to exploitation nearly as much as access tokens.
Moreover, in case of a breach, the server can invalidate refresh tokens and close the window for attacks.
Refresh Tokens Implementation
Now that we have a solid understanding of the basics of token-based authentication. Let’s see it in action.
For this, you will need to create a simple Node.js app.
Create a folder named “TokenTest” and add a Javascript file called “index.js” to it.
Add this code to the “index.js” file.
const express = require("express")
const bodyParser = require("body-parser")
const cookieParser = require("cookie-parser")
const app = express()
app.use(bodyParser.json())
app.use(cookieParser())
app.listen(8000)
This code is your Node.js server containing the code required to start a server and accept requests.
Then go to the terminal and run this command.
$ npm init
This command will take you through creating a “package.json” file for your node dependencies. Press Enter on all the prompts if you don’t know what they do.
If you don’t have npm or Node installed, you can find it here.
Setting up Node
Input the following commands to install the dependencies required in the “index.js” file.
$ npm install express
$ npm install body-parser
$ npm install cookie-parser
$ npm install jsonwebtoken
Now, create a second Javascript file called “handlers.js” and add the following code to this file.
const jwt = require("jsonwebtoken")
const jwtKey = "SECRET"
const jwtExpirySeconds = 600
const users = {
user1: "iamsecret1",
user2: "iamsecret2",
}
Notice that there’s a variable holding a key called “jwtKey.” Node will use this key to build and encrypt tokens, so you should keep this key in a safe place. For this example, however, that is not necessary.
Additionally, as you can see, the account credentials are on a static variable since this example does not require data storage.
Token Refresh in Authentication
In order to manage the authentication mechanism, you need to add these three endpoints: “login,” “secret,” and “refresh.”
Add the following code to the “handlers.js” file.
const login = (req, res) => {
const { username, password } = req.body
if (!username || !password || users[username] !== password) {
return res.status(401).end()
}
const token = jwt.sign({ username }, jwtKey, {
algorithm: "HS256",
expiresIn: jwtExpirySeconds,
})
console.log("token:", token)
res.cookie("token", token, { maxAge: jwtExpirySeconds * 1000 })
res.end()
}
const secret = (req, res) => {
const token = req.cookies.token
if (!token) {
return res.status(401).end()
}
var payload = null;
try {
payload = jwt.verify(token, jwtKey)
} catch (e) {
if (e instanceof jwt.JsonWebTokenError) {
return res.status(401).end()
}
return res.status(400).end()
}
res.send(`Welcome ${payload.username}!`)
}
const refresh = (req, res) => {
const token = req.cookies.token
if (!token) {
return res.status(401).end()
}
var payload = null;
try {
payload = jwt.verify(token, jwtKey)
} catch (e) {
if (e instanceof jwt.JsonWebTokenError) {
return res.status(401).end()
}
return res.status(400).end()
}
const nowUnixSeconds = Math.round(Number(new Date()) / 1000)
if (payload.exp - nowUnixSeconds > 30) {
return res.status(400).end()
}
const newToken = jwt.sign({ username: payload.username }, jwtKey, {
algorithm: "HS256",
expiresIn: jwtExpirySeconds,
})
res.cookie("token", newToken, { maxAge: jwtExpirySeconds * 1000 })
res.end()
}
module.exports = {
login,
secret,
refresh,
}
The “login” endpoint is where credential validation happens. This is the initial stage of the authentication flow.
It works by accepting a basic login request and confirming the validity of the credentials. It then creates a token using the username and the secret key. Lastly, it saves the token on the session cookies.
The “secret” endpoint represents our consumable resource and checks for the token on the session cookies. This serves as both an authentication and authorization control for the API.
Finally, the “refresh” endpoint provides the mechanism to refresh the session token and keep the session alive.
Running the Code
Change the “index.js” to the following.
const express = require("express")
const bodyParser = require("body-parser")
const cookieParser = require("cookie-parser")
const app = express()
app.use(bodyParser.json())
app.use(cookieParser())
const { login, secret, refresh } = require("./handlers")
app.post("/login", login)
app.get("/secret", secret)
app.post("/refresh", refresh)
app.listen(8000)
Great. All the elements are in position.
Now run the code with this command.
$ node ./index.js
Token Refresh Test
To test your app, all you need is a tool to send requests to the server. Something like Postman will do the job.
To use it, just create a POST request to the URL (http://localhost:8000/login) and add the following payload.
{"username":"user1","password":"password1"}
These are your credentials.
Now click send and check the application log.
To access the protected resource, simply send a GET request to the URL (http://localhost:8000/secret).
Lastly, call the refresh endpoint with a POST to the URL (http://localhost:8000/refresh) to refresh your access token.
What’s Next?
Keeping tabs on all the vulnerabilities and hacks that pose a threat to our systems can be a daunting task even for the most experienced security engineers.
Staying updated and relevant in this day and age has never been more challenging, and nowhere is this more true than in the world of systems security and authorization.
When it comes to building a robust and secure authentication/authorization mechanism for our clients, resilience and reliability are always at the top of the priorities. However, this can come at the cost of convenience for the end user. This situation can be a non-starter for your platform depending on your target user base, the authentication dynamic, and the data’s sensitivity.
You can also read about how to use WebSockets in iOS with SocketIO, covering fundamentals, implementation, pros and cons, and best practices for real-time applications here.
Leave a Reply