OAuth2
OAuth2 enables application developers to build applications that utilize authentication and data from the Discord API. Developers can use this to create things such as web dashboards to display user info, fetch linked third-party accounts like Twitch or Steam, access users' guild information without actually being in the guild, and much more. OAuth2 can significantly extend the functionality of your bot if used correctly.
A quick example
Setting up a basic web server
Most of the time, websites use OAuth2 to get information about their users from an external service. In this example, we will use express to create a web server to use a user's Discord information to greet them. Start by creating three files: config.json, index.js, and index.html.
config.json will be used to store the client ID, client secret, and server port.
{
"clientId": "",
"clientSecret": "",
"port": 53134
}index.js will be used to start the server and handle requests. When someone visits the index page (/), an HTML file will be sent in response.
const express = require('express');
const { port } = require('./config.json');
const app = express();
app.get('/', (request, response) => {
return response.sendFile('index.html', { root: '.' });
});
app.listen(port, () => console.log(`App listening at http://localhost:${port}`));index.html will be used to display the user interface and OAuth data once logged in.
<!DOCTYPE html>
<html>
<head>
<title>My Discord OAuth2 App</title>
</head>
<body>
<div id="info">Hoi!</div>
</body>
</html>After running npm i express, you can start your server with node index.js. Once started, connect to http://localhost:53134, and you should see "Hoi!".
Although we're using express, there are many other alternatives to handle a web server, such as: fastify, koa, and the native Node.js http module.
Getting an OAuth2 URL
Now that you have a web server up and running, it's time to get some information from Discord. Open your Discord applications, create or select an application, and head over to the "OAuth2" page.

Take note of the client id and client secret fields. Copy these values into your config.json file; you'll need them later. For now, add a redirect URL to http://localhost:53134 like so:

Once you've added your redirect URL, you will want to generate an OAuth2 URL. Lower down on the page, you can conveniently find an OAuth2 URL Generator provided by Discord. Use this to create a URL for yourself with the identify scope.

The identify scope will allow your application to get basic user information from Discord. You can find a list of all scopes here.
Implicit grant flow
Implicit grant flow, as previously covered in this section, is vulnerable to token leakage and replay attacks. Please use the authorization grant flow instead. You can find out more about the best implementation practices in the Oauth2 RFC.
More details
The state parameter
OAuth2's protocols provide a state parameter, which Discord supports. This parameter helps prevent CSRF attacks and represents your application's state. The state should be generated per user and appended to the OAuth2 URL. For a basic example, you can use a randomly generated string encoded in Base64 as the state parameter.
function generateRandomString() {
let randomString = '';
const randomNumber = Math.floor(Math.random() * 10);
for (let i = 0; i < 20 + randomNumber; i++) {
randomString += String.fromCharCode(33 + Math.floor(Math.random() * 94));
}
return randomString;
}
window.onload = () => {
// ...
if (!accessToken) {
const randomString = generateRandomString();
localStorage.setItem('oauth-state', randomString);
document.getElementById('login').href += `&state=${btoa(randomString)}`;
return (document.getElementById('login').style.display = 'block');
}
};When you visit a URL with a state parameter appended to it and then click Authorize, you'll notice that after being redirected, the URL will also have the state parameter appended, which you should then check against what was stored. You can modify the script in your index.html file to handle this.
<script>
// ...
const fragment = new URLSearchParams(window.location.hash.slice(1));
const [accessToken, tokenType, state] = [
fragment.get('access_token'),
fragment.get('token_type'),
fragment.get('state'),
];
if (!accessToken) {
// ...
}
if (localStorage.getItem('oauth-state') !== atob(decodeURIComponent(state))) {
return console.log('You may have been clickjacked!');
}
</script>Don't forgo security for a tiny bit of convenience!
Authorization code grant flow
What you did in the quick example was go through the implicit grant flow, which passed the access token straight to the user's browser. This flow is great and simple, but you don't get to refresh the token without the user, and it is less secure than going through the authorization code grant flow. This flow involves receiving an access code, which your server then exchanges for an access token. Notice that this way, the access token never actually reaches the user throughout the process.
Unlike the implicit grant flow, you need an OAuth2 URL where the response_type is code. After you change the response_type, try visiting the link and authorizing your application. You should notice that instead of a hash, the redirect URL now has a single query parameter appended to it, i.e. ?code=ACCESS_CODE. Modify your index.js file to access the parameter from the URL if it exists. In express, you can use the request parameter's query property.
app.get('/', (request, response) => {
console.log(`The access code is: ${request.query.code}`);
return response.sendFile('index.html', { root: '.' });
});Now you have to exchange this code with Discord for an access token. To do this, you need your client_id and client_secret. If you've forgotten these, head over to your applications and get them. You can use undici to make requests to Discord.
To install undici, run the following command:
npm install undiciRequire undici and make your request.
If you are used to the Fetch API and want to use that instead of how undici does it, instead of using
undici#request, use undici#fetch with the same parameters as node-fetch.
const { request } = require('undici');
const express = require('express');
const { port } = require('./config.json');
const { clientId, clientSecret, port } = require('./config.json');
const app = express();
app.get('/', async ({ query }, response) => {
const { code } = query;
if (code) {
try {
const tokenResponseData = await request('https://discord.com/api/oauth2/token', {
method: 'POST',
body: new URLSearchParams({
client_id: clientId,
client_secret: clientSecret,
code,
grant_type: 'authorization_code',
redirect_uri: `http://localhost:${port}`,
scope: 'identify',
}).toString(),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
const oauthData = await tokenResponseData.body.json();
console.log(oauthData);
} catch (error) {
// NOTE: An unauthorized token will not throw an error
// tokenResponseData.statusCode will be 401
console.error(error);
}
}
return response.sendFile('index.html', { root: '.' });
});The content-type for the token URL must be application/x-www-form-urlencoded, which is why URLSearchParams is
used.
Now try visiting your OAuth2 URL and authorizing your application. Once you're redirected, you should see an access token response in your console.
{
"access_token": "an access token",
"token_type": "Bearer",
"expires_in": 604800,
"refresh_token": "a refresh token",
"scope": "identify"
}With an access token and a refresh token, you can once again use the /api/users/@me endpoint to fetch the user object.
const userResult = await request('https://discord.com/api/users/@me', {
headers: {
authorization: `${oauthData.token_type} ${oauthData.access_token}`,
},
});
console.log(await userResult.body.json());To maintain security, store the access token server-side but associate it with a session ID that you generate for the user.