Refreshing OAUTH2 tokens and Discord data | Sapphire (2024)

Now that you have implemented OAUTH2 on the backend and frontend you will experience that after some time the tokenexpires. This is because the token is only valid for 7 days (604800 seconds to be precise). To prevent the user fromhaving to re-authenticate you can implement an automatic refresh system. This is also something you will likely want toimplement anyway and offer as a "resync" button to the user in case they, for example, join a new server and want tosetup your bot in that server so you need their latest LoginData.

warning

On this page we are not guarding this route have rate limiting, however we very strongly recommend adding this kindof guard to the route so it cannot be spammed. You can read up on how to do this in the dedicated page Applying ratelimits to routes.

Implementing the refresh route

The first step is to add a new route, more information for adding routes can be found in the Addingroutes page. For the rest of this guide we will assume the route is /oauth/refresh as path. Lets startby writing the basics:

  • CommonJS
  • ESM
  • TypeScript
const { methods, Route } = require('@sapphire/plugin-api');

class RefreshRoute extends Route {
async [methods.POST](request, response) {
// implementation
}
}
module.exports = {
RefreshRoute
};

The first step in this route is to ensure that the route is called when the user is at least authenticated, regardlessof whether their token is still valid or not. We can do so by checking the presence of request.auth.

In this part of the code we also extract some nested variables into local variables so we can reference them easierlater.

  • CommonJS
  • ESM
  • TypeScript
const { HttpCodes, methods, Route } = require('@sapphire/plugin-api');

class RefreshRoute extends Route {
async [methods.POST](request, response) {
if (!request.auth) return response.error(HttpCodes.Unauthorized);

const requestAuth = request.auth;
const serverAuth = this.container.server.auth;

let authToken = requestAuth.token;
}
}
module.exports = {
RefreshRoute
};

tip

If you use an authenticated decorator on this route, you can remove the if (!request.auth) check and useconst requestAuth = request.auth! instead.

Note regarding TypeScript example

For the TypeScript example we are using the ! operator on this.container.server.auth to tellTypeScript that we are sure that the property exists. We can safely do this because we provided the OAUTH2 informationin Using the built in OAUTH2 route (backend)

Now that we have the authentication token we should check if it will soon expire. As the token is valid for 7 days(604800 seconds), we check if it will expire within a day (86400 seconds). If it will expire in a day we willrefresh the token, and add the new token as a cookie to the response:

  • CommonJS
  • ESM
  • TypeScript
const { fetch, FetchResultTypes } = require('@sapphire/fetch');
const { HttpCodes, methods, MimeTypes, Route } = require('@sapphire/plugin-api');
const { Time } = require('@sapphire/time-utilities');
const { OAuth2Routes } = require('discord.js');
const { stringify } = require('node:querystring');

class RefreshRoute extends Route {
async [methods.POST](request, response) {
if (!request.auth) return response.error(HttpCodes.Unauthorized);

const requestAuth = request.auth;
const serverAuth = this.container.server.auth;

let authToken = requestAuth.token;

// If the token expires in a day, refresh
if (Date.now() + Time.Day >= requestAuth.expires) {
const body = await this.refreshToken(requestAuth.id, requestAuth.refresh);
if (body !== null) {
const authentication = serverAuth.encrypt({
id: requestAuth.id,
token: body.access_token,
refresh: body.refresh_token,
expires: Date.now() + body.expires_in * 1000
});

response.cookies.add(serverAuth.cookie, authentication, { maxAge: body.expires_in });
authToken = body.access_token;
}
}
}

async refreshToken(id, refreshToken) {
const { logger, server } = this.container;
try {
logger.debug(`Refreshing Token for ${id}`);
return await fetch(
OAuth2Routes.tokenURL,
{
method: 'POST',
body: stringify({
client_id: server.auth.id,
client_secret: server.auth.secret,
grant_type: 'refresh_token',
refresh_token: refreshToken,
redirect_uri: server.auth.redirect,
scope: server.auth.scopes
}),
headers: {
'Content-Type': MimeTypes.ApplicationFormUrlEncoded
}
},
FetchResultTypes.JSON
);
} catch (error) {
logger.fatal(error);
return null;
}
}
}
module.exports = {
RefreshRoute
};

There is quite a bit to take in here so lets go over it bit by bit. First of all, we check if the token will expire inday or less. We use the Time enum here so easily get the amount of seconds that fit in a day. Next wecall the refreshToken method. This method is responsible for making the request to Discord to refresh the token. Weuse the fetch method from the @sapphire/fetch package to make the request. We alsouse the stringify method from the node:querystring package to convert thebody to a querystring. The fetch method returns a Promise so we can use awaitto wait for the response. If the response is successful we return the body, otherwise we return null.

Now back in the main function we have the body with the refreshed token, or null. If the body is null then we simplydo nothing, otherwise we need to process the body further. First we encrypt the new token, then we add it to theresponse as a cookie. We also set the authToken variable to the new token so we can use it in the next step.

Refreshing the user's data

The last step that we want to add to this route is to refresh the user's data. Note that you could skip the refreshingof the token here for this, however refreshing of data will fail with an expired token and you need to provide a way torefresh the token anyway which is why we do it all on this route. If you prefer to do these steps on a separate routethen you can do so and you'll need to handle invalid responses yourself.

To refresh the user's data you can call the auth.fetchData function. This will return the LoginData, the sameway it was returned when the user first logged in. You can then use this data to update the user's data in yourfrontend's storage.

  • CommonJS
  • ESM
  • TypeScript
const { HttpCodes, methods, Route } = require('@sapphire/plugin-api');

class RefreshRoute extends Route {
async [methods.POST](request, response) {
if (!request.auth) return response.error(HttpCodes.Unauthorized);

const requestAuth = request.auth;
const serverAuth = this.container.server.auth;

let authToken = requestAuth.token;

// If the token expires in a day, refresh. Insert the code from the examples above here.

// Refresh the user's data
try {
return response.json(await serverAuth.fetchData(authToken));
} catch (error) {
this.container.logger.fatal(error);
return response.error(HttpCodes.InternalServerError);
}
}
}
module.exports = {
RefreshRoute
};

With the refreshed data fetched we return it as JSON response to the user. Because we also set the new cookie in theearlier code, the new token is also sent to the user and stored on the frontend side. Your frontend now knows all thelatest data of the user, and can refresh the UI accordingly.

Congratulations! You should now have a functioning dashboard that can refresh the user's data and token.

Refreshing OAUTH2 tokens and Discord data | Sapphire (2024)
Top Articles
Trading Tip `The Wall´ – Meet the TA Gods - The Bitcoin News
The 3 Methods of Curing Meat with Salt
How To Start a Consignment Shop in 12 Steps (2024) - Shopify
Kmart near me - Perth, WA
Christian McCaffrey loses fumble to open Super Bowl LVIII
Nehemiah 4:1–23
Online Reading Resources for Students & Teachers | Raz-Kids
Exam With A Social Studies Section Crossword
BULLETIN OF ANIMAL HEALTH AND PRODUCTION IN AFRICA
Yi Asian Chinese Union
Noaa Swell Forecast
Pj Ferry Schedule
T&G Pallet Liquidation
Cvs Learnet Modules
Theycallmemissblue
Magicseaweed Capitola
Guilford County | NCpedia
[Birthday Column] Celebrating Sarada's Birthday on 3/31! Looking Back on the Successor to the Uchiha Legacy Who Dreams of Becoming Hokage! | NARUTO OFFICIAL SITE (NARUTO & BORUTO)
R Cwbt
Inter-Tech IM-2 Expander/SAMA IM01 Pro
Foxy Brown 2025
Georgetown 10 Day Weather
Team C Lakewood
Clare Briggs Guzman
All Breed Database
Pawn Shop Moline Il
Vivification Harry Potter
Pokémon Unbound Starters
Stickley Furniture
Paradise Point Animal Hospital With Veterinarians On-The-Go
Marlene2295
Abga Gestation Calculator
Current Time In Maryland
Exploring The Whimsical World Of JellybeansBrains Only
Studio 22 Nashville Review
20 Best Things to Do in Thousand Oaks, CA - Travel Lens
Rs3 Bis Perks
Cygenoth
St Anthony Hospital Crown Point Visiting Hours
Carteret County Busted Paper
ESA Science & Technology - The remarkable Red Rectangle: A stairway to heaven? [heic0408]
How I Passed the AZ-900 Microsoft Azure Fundamentals Exam
Guided Practice Activities 5B-1 Answers
Nu Carnival Scenes
FedEx Authorized ShipCenter - Edouard Pack And Ship at Cape Coral, FL - 2301 Del Prado Blvd Ste 690 33990
Online-Reservierungen - Booqable Vermietungssoftware
Accident On 40 East Today
Ouhsc Qualtrics
Espn Top 300 Non Ppr
Marine Forecast Sandy Hook To Manasquan Inlet
Strange World Showtimes Near Century Federal Way
Latest Posts
Article information

Author: Virgilio Hermann JD

Last Updated:

Views: 5675

Rating: 4 / 5 (41 voted)

Reviews: 80% of readers found this page helpful

Author information

Name: Virgilio Hermann JD

Birthday: 1997-12-21

Address: 6946 Schoen Cove, Sipesshire, MO 55944

Phone: +3763365785260

Job: Accounting Engineer

Hobby: Web surfing, Rafting, Dowsing, Stand-up comedy, Ghost hunting, Swimming, Amateur radio

Introduction: My name is Virgilio Hermann JD, I am a fine, gifted, beautiful, encouraging, kind, talented, zealous person who loves writing and wants to share my knowledge and understanding with you.