Authentication
Authenticate users in your Discord Activity
Authentication connects your activity to a Discord user's identity. The OAuth2 flow runs through the Embedded App SDK and a backend token endpoint.
How It Works
The frontend calls authorize() on the Embedded App SDK to get an authorization code. That code is sent to the /api/token backend endpoint, which exchanges it with Discord's OAuth2 API for an access token. The frontend then calls authenticate() with the token to complete the flow and receive the user's session data.
Diagram showing the OAuth2 flow: frontend calls authorize() getting a code, sends it to /api/token which exchanges with Discord API for access_token, then frontend calls authenticate()
React Setup
DiscordContextProvider handles the full authentication flow automatically when the authenticate prop is set:
<DiscordContextProvider authenticate scope={['identify', 'guilds']}>
<Activity />
</DiscordContextProvider><DiscordContextProvider authenticate scope={['identify', 'guilds']}>
<Activity />
</DiscordContextProvider>The scope array determines which permissions are requested. Children render only after the flow completes or errors.
Accessing User Data
useDiscordSdk() returns a session object with the authenticated user's information:
import { useDiscordSdk } from '../hooks/useDiscordSdk'
export function Profile() {
const { session } = useDiscordSdk()
if (!session) {
return null
}
return <div>{session.user.username}</div>
}import { useDiscordSdk } from '../hooks/useDiscordSdk'
export function Profile() {
const { session } = useDiscordSdk()
if (!session) {
return null
}
return <div>{session.user.username}</div>
}| Field | Type | Description |
|---|---|---|
session.user.id | string | User's Discord ID |
session.user.username | string | Username |
session.user.discriminator | string | Discriminator |
session.user.avatar | string | null | Avatar hash |
session.user.public_flags | number | Public user flags |
Construct an avatar URL from the user ID and avatar hash:
const avatarUrl = `https://cdn.discordapp.com/avatars/${session.user.id}/${session.user.avatar}.png?size=256`const avatarUrl = `https://cdn.discordapp.com/avatars/${session.user.id}/${session.user.avatar}.png?size=256`A Discord Activity displaying the authenticated user's avatar, username, and user ID retrieved from session data after OAuth2 authentication
OAuth Scopes
| Scope | Description |
|---|---|
identify | Access user ID, username, avatar |
guilds | Access user's guild list |
guilds.members.read | Read guild member data |
rpc.voice.read | Access voice channel info |
Additional scopes can be passed to the scope prop on DiscordContextProvider.
Token Endpoint
The /api/token.ts backend handler exchanges the authorization code for an access token. This keeps the client secret secure on the server:
import type { RoboRequest } from '@robojs/server'
export default async (req: RoboRequest) => {
const { code } = (await req.json()) as { code: string }
const response = await fetch('https://discord.com/api/oauth2/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
client_id: process.env.VITE_DISCORD_CLIENT_ID!,
client_secret: process.env.DISCORD_CLIENT_SECRET!,
grant_type: 'authorization_code',
code
})
})
const { access_token } = await response.json()
return { access_token }
}export default async (req) => {
const { code } = await req.json()
const response = await fetch('https://discord.com/api/oauth2/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
client_id: process.env.VITE_DISCORD_CLIENT_ID,
client_secret: process.env.DISCORD_CLIENT_SECRET,
grant_type: 'authorization_code',
code
})
})
const { access_token } = await response.json()
return { access_token }
}Loading State
Use the loadingScreen prop to display a component while authentication is in progress:
<DiscordContextProvider authenticate loadingScreen={<div>Loading...</div>}>
<Activity />
</DiscordContextProvider><DiscordContextProvider authenticate loadingScreen={<div>Loading...</div>}>
<Activity />
</DiscordContextProvider>The loading screen renders during the pending, loading, and authenticating statuses. It is replaced by children once the status reaches ready or error.
A Discord Activity showing the loading screen during authentication, with a loading indicator or 'Loading...' message while the OAuth2 flow is in progress
Error Handling
| Error | Cause |
|---|---|
| Missing redirect_uri | No redirect URI configured in Developer Portal |
| invalid_client | Wrong client ID or secret |
| invalid_grant | Expired or reused authorization code |
Verify your credentials and Developer Portal OAuth2 settings if authentication fails.
Vanilla Implementation
For non-React projects, run the authorization flow manually:
import { DiscordSDK } from '@discord/embedded-app-sdk'
const discordSdk = new DiscordSDK(import.meta.env.VITE_DISCORD_CLIENT_ID)
await discordSdk.ready()
const { code } = await discordSdk.commands.authorize({
client_id: import.meta.env.VITE_DISCORD_CLIENT_ID,
response_type: 'code',
state: '',
prompt: 'none',
scope: ['identify', 'guilds']
})
const response = await fetch('/api/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code })
})
const { access_token } = await response.json()
const auth = await discordSdk.commands.authenticate({ access_token })import { DiscordSDK } from '@discord/embedded-app-sdk'
const discordSdk = new DiscordSDK(import.meta.env.VITE_DISCORD_CLIENT_ID)
await discordSdk.ready()
const { code } = await discordSdk.commands.authorize({
client_id: import.meta.env.VITE_DISCORD_CLIENT_ID,
response_type: 'code',
state: '',
prompt: 'none',
scope: ['identify', 'guilds']
})
const response = await fetch('/api/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code })
})
const { access_token } = await response.json()
const auth = await discordSdk.commands.authenticate({ access_token })This performs the same flow as DiscordContextProvider: authorize, exchange code for token, then authenticate.
