Refresh Token in React / JavaScript

You might have probably wondered what this is. You might have heard about it and be like :

But have you ever implemented it? Okay no, yes whatever , let’s get to it.
Token? What is that
A token is like an event wristband. After the security guard scans and verifies your ticket, they give you a wristband. This wristband is your way of saying “yeah I bought this ticket and I can roam freely in the event.”
We can call that an access token. Now some events are like three days and you only bought for a day.
Instead of security escorting you out of the event at the end of the day, they can say you need to redeem your ticket or get a new one something of the sorts.
I think I have described three things here :
[Access Token] — the wristband given when you enter the event.
[Expiry Time ]— the event pass expiring after a day
[Refresh Token ]— redeeming your ticket , getting a new one.
What is (Access, Refresh) Token in technical terms ?
- When we login to a system using O.Auth , we can be give an access token, expiry time and refresh token.
- The access token is what enables us to make API requests but only for a time (expiry time). Now you may notice that after some time in some applications we are usually told to log in again and perform the action we want.
- This might be because that our current access token has expired and to get a new one we must log in again. (They have not implemented refresh token).
- This has been prevalent but what if I don’t want my user to log out but just refresh the token for them in the background.
This is where what we will do today will come in.
Why use Refresh Token?
- For security? I believe we have sensitive data that we don’t want lying around from morning to morning. The token we use to make requests has to expire.
- Better User Experience(UX). I don’t think it will be a good feature to have your users log out and log in each and every time cause their access token has expired? Nope. Trust they will leave your application . It’s boring , keep up.
Token Format
This is how it looks , I believe we might have seen something like this.
Access token base format after login
{
"access_token":"eyJz93a...k4laUWw",
"token_type":"Bearer",
"expires_in":86400 // this is time it expires , its usually in milliseconds.
//depends on your backend guy
}
How can we implement this
There are various ways to implement this but what we will try to use today is async/await
In layman terms.
- We have logged in and have our access token.
- After some time , our token expires and we get unauthorized response since its invalid when making a request.
- Instead of logging out to get a new one , our function calls the refresh token api to generate a new one.
- We get the new one and now continue with our initial request.
- The user won’t know that their token expired and being refreshed but might notice/realize this execution is taking some time.
So basically a refresh token does this :(please please please click on the link )
What is async/await?
An async/await is used in asynchronous programming.
Asynchronous programming is a way to handle tasks that may take some time to complete, such as fetching data from a server or reading a file, without blocking the execution of the rest of the code.
Let’s start with real time example:
We have a daily routine(function), lets call it GetOutOfTheHse();
function GetOutOfTheHse(){
WakeUp();
Shower();
WearClothes();
EatBreakfast();
BrushTeeth();
WearShoes();
GetOut();//final function
}
So if we run this function , it will do so from start to end without caring. But what if the EatBreakfast() runs before you even finish WearClothes() and imagine that also happened before you finished Shower(); How would you look lmao.
We introduce asynchronous because each function here takes time and needs to end , before we start the next one. To do this we introduce await.
So our new function becomes like this:
async function GetOutOfTheHse(){
await WakeUp();
await Shower();
await WearClothes();
await EatBreakfast();
await BrushTeeth();
await WearShoes();
GetOut();//final function, out of the house literally.
}
This will make sure we have to do all the things before we GetOut().
- But there’s a catch. lol. We can expect some unexpected things,like not finding the house keys, jacket, your shoelaces or what not .Whatever that might make you not GetOut().
We need to anticipate and know how to fix this.
- To fix this we include try/catch block.
Lmao I’m now getting this trust me. We however need to know what we can expect and how to handle it.
Our new async fn with try/catch block:
async function GetOutOfTheHouse() {
try {
await WakeUp();
await Shower();
await WearClothes();
await EatBreakfast();
await BrushTeeth();
await WearShoes();
} catch (error) {
throw new Error(
"Oh no, I can't find the shoelaces for WearShoes(). " +
"We can, however, try and fix this by wearing the other shoes."
);
}
GetOut(); // final function, out of the house literally.
}
So what we are saying here is yea run the function, and we can actually fix this error we are getting cause we anticipated it yea? Otherwise we would have gone out with shoelace-less shoes.
Refresh Token with Async/Await .
I will use both technical and the event terms to explain this part.
- Login (Enter Event)
Let’s say you have successfully logged in
export const handleLoginFn = ()=>{
// Prepare the request data
const formData = new FormData();
formData.append("account_id", your_unique_identification);
formData.append("client_secret", your_ticket);
// Authenticate user and return access and refresh tokens
fetch("/login", {
method: "POST",
body: formData,
})
.then(response => {
// Successful login - your ticket is valid!
// Now, let's generate your access token, refresh token, and set an expiry time
// Generate your access token
if (response.status === 200) {
return response.json();
} else {
throw new Error("Authentication failed");
}
})
.then(data => {
if (
data.access_token &&
data.access_token !== null &&
data.access_token !== ""
) {
const accessToken = data.access_token;
const expiryTime = data.expires_in;
const refreshToken = data.refresh_token;
// Set tokens and other information in localStorage
localStorage.setItem("bearer_token", accessToken);
localStorage.setItem("expiry_time", expiryTime);
localStorage.setItem("refresh_token", refreshToken);
localStorage.setItem("account_id", your_unique_identification);
console.log("Done setting, enjoy your event"!);
// Perform additional actions (e.g., navigate to another page)
toast.success("Authentication successful!");
setLogin(true);
}
})
.catch(error => {
// Oops, this ticket is invalid - you're told to stand aside
console.error("Authentication error", error);
// Handle authentication error, if needed
localStorage.setItem("bearer_token", "");
});
}
Before we continue, lets define our refresh token function. This is so that when our time ends , we are not escorted out but this guy comes in and says chill let me sort you.
export const refreshTokenAPI = (
your_unique_identification,
refreshToken,
handleLoginFn
) => {
console.log("refresh token hit");
// console.log(handleLoginFn)
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
const urlencoded = new URLSearchParams();
urlencoded.append("grant_type", "refresh_token");
urlencoded.append("account_id", your_unique_identification);
urlencoded.append("refresh_token", refreshToken);
const requestOptions = {
method: "POST",
headers: myHeaders,
body: urlencoded,
redirect: "follow",
};
// console.log("raw:", urlencoded);
fetch("/auth/token/", requestOptions)
.then((response) => {
const status = response.status;
return response.json().then((result) => {
return { status, result };
});
})
.then(({ result, status }) => {
//yay! we have refreshed your ticket successfully,
//lets save the new values again for next time
if (status === 200) {
localStorage.setItem("bearer_token", result.access_token);
localStorage.setItem("refresh_token", result.refresh_token);
localStorage.setItem("expiry_time", result.expires_in);
console.log("Token refreshed successfully.");
} else if (status === 401) {
//oops your refresh token did not work
// you need to get out for real and pay from the gate
//logging out
handleLoginFn();
console.error("Token refresh inside:", result);
}else {
//honestly we cannot help you
// YOU NEED TO LEAVE!
handleLoginFn();
}
})
.catch((error) => {
//YOU GOT HERE ILLEGALLY MAN , OUT
//THIS AINT YOUR TICKET FR
console.error("Token refresh failed:", error);
handleLoginFn(); // Navigate to login
});
};
So we have defined what will happen when our time runs out.
2. Refresh Token implementation.
So we are having fun backstage , special access and lets say we were on line to meet our fave artists and when they scanned your wristband it says error , not authorized.
Oops. Since we took care of this , let’s async this
import { refreshTokenAPI } from "./refreshToken";
export const MeetArtist = async (
acccessToken,
account_id,
refreshToken,
handleLoginFn,
setNewRefreshedToken
) => {
if (!acccessToken || acccessToken === "") {
//function is done
//we cannot do anything without the access token
return;
}
//else we try to exec our function meet_artist
try {
const myHeaders = new Headers();
myHeaders.append("Authorization", `Bearer ${acccessToken}`);
const requestOptions = {
method: "GET",
headers: myHeaders,
redirect: "follow",
};
//we run our first function here
//we then wait for the response from the fetch api
const originalResponse = await fetch(`/meet_artist/?account_id=${account_id}`, requestOptions);
//------------------------------------------
//we have anticipated the error we will get and said to run the refresh token
//we say if we get the unauthorized status
//we run the refresh token api we defined and tell it to wait
if (originalResponse.status === 401) {
await refreshTokenAPI(account_id, refreshToken, handleLoginFn);
//then we say the new response is the same api with the new token
const refreshedResponse = await fetch(`/meet_artist/?account_id=${account_id}`, requestOptions);
if (refreshedResponse.status === 200) {
const resultFromNewToken = await refreshedResponse.json();
setNewRefreshedToken(resultFromNewToken);
} else {
throw new Error("An error occurred after token refresh.");
}
///-------------------------------
//this is what happens if we just got a success the first time
//meaning our token did not expire
} else if (originalResponse.status === 200) {
const originalResult = await response.json();
setNewRefreshedToken(originalResult);
} else {
//if we have another error ,
//we can choose to logOut and log in again cause now what can we do
throw new Error("An error occurred.");
handeLoginFn();
}
} catch (error) {
Alert("An error occurred. Please try again.");
console.error("Error:", error);
}
};
- So inside this function, we run the function and said is perchance the time expires on us (get status code: 401 ), we run the refresh token function.
- After getting the new token, we run the same function with the new token. This has helped us not to log out and log in again.
- We have also said if it didn’t expire, then we just use the response to whatever we got.
Yeah, I don’t know how to end this. But this is one of the ways to implement a refresh token. But at least we get the gist of it. Yeah.
Hope this helps!
Stackademic
Thank you for reading until the end. Before you go: