Discord Activities
An Activity asks Discord for a code, hands it to auth4.dev, and gets back auth4.dev tokens plus the Discord access token it needs to finish the Embedded App SDK handshake.
This builds on Discord sign-in: auth4.dev exchanges the code with the Discord application you already connected, using its stored client secret. A player who signs in from the Activity and from your website gets the same user ID (sub).
1. Before you start
- Set up Discord sign-in for the project with the same Discord application as your Activity.
- Create a browser application for the Activity. To keep players signed in past five minutes, turn on browser refresh tokens for it.
- Your Activity's origin is the Discord proxy:
https://{discord application id}.discordsays.com - In the Discord Developer Portal, under Activities >URL Mappings, map a prefix such as
/auth4toauth.auth4.dev. Activities can only reach other hosts through these mappings.
2. Turn on the Activity exchange
The exchange is off for every application until a project owner or admin turns it on and names the Activity origin. Only that origin may call it; any other origin, including your website, is refused.
- In the console, open the project, go toApplications and choose the browser application for the Activity.
- In the Discord Activity section, turn onLet players sign in from a Discord Activity.
- In Activity origin, enter
https://{discord application id}.discordsays.com. You can also paste just the Discord application ID. - Select Save Discord Activity.
Turn the switch off and save to stop the exchange. Viewers see the setting but can't change it. The section appears for browser applications only.
To change the setting from a script, use the management API. The full request and error list is in the API reference.
PATCH /management/tenants/{projectId}/clients/{clientId}/discord-activity
Authorization: Bearer {management token}
Content-Type: application/json
{ "enabled": true, "origin": "https://123456789012345678.discordsays.com" }3. Sign the player in
A minimal example with the Discord Embedded App SDK and the auth4.dev browser SDK. Call signInWithDiscordActivity() with the code from Discord'sauthorize(), then pass the returned Discord token to authenticate().
import { DiscordSDK, patchUrlMappings } from "@discord/embedded-app-sdk";
import { Auth4BrowserClient } from "@auth4/sdk-browser";
const DISCORD_APP_ID = "123456789012345678";
const discord = new DiscordSDK(DISCORD_APP_ID);
// Route auth.auth4.dev through the URL mapping before the auth4 client is created.
patchUrlMappings([{ prefix: "/auth4", target: "auth.auth4.dev" }]);
const auth4 = new Auth4BrowserClient({
issuer: "https://auth.auth4.dev/t/{projectId}",
clientId: "{clientId}",
redirectUri: `https://${DISCORD_APP_ID}.discordsays.com/`,
refresh: true,
});
await discord.ready();
const { code } = await discord.commands.authorize({
client_id: DISCORD_APP_ID,
response_type: "code",
state: "",
prompt: "none",
scope: ["identify", "email"],
});
const { session, discord: discordToken } = await auth4.signInWithDiscordActivity({ code });
await discord.commands.authenticate({ access_token: discordToken.accessToken });
// Call your game API with auth4.dev tokens, held in memory only.
const accessToken = auth4.getAccessToken();
console.log("Signed in as", session.user.sub);Both token sets stay in memory. The SDK checks the ID token as it does for a redirect sign-in and never stores anything in cookies or browser storage. Your game API validates the auth4.dev access token as usual; see Server APIs.
How it behaves
- The Discord account is matched or created exactly as hosted Discord sign-in does, and the ID token's
idpclaim isdiscord. - Include the
identifyscope inauthorize(). Addemailso a Discord-verified address satisfies the verified email step. - An Activity can't show hosted-login screens. If a sign-in would need the verified email step or a Flows terms screen, the SDK rejects with
discord_activity_failedand the message namesinteraction_required. Request theemailscope or set the application's verified email setting to off. - Your Flows run as usual:
pre_registrationfor new players andpost_loginfor every sign-in. - auth4.dev keeps no copy of the Discord access token and drops Discord's refresh token.
When something goes wrong
unauthorized_client: the exchange is off for the application, or the client ID isn't a browser application in this project.access_denied: the request didn't come from the configured Activity origin, the player's account is disabled, or a Flow denied the sign-in.invalid_grant: Discord refused the code. Codes work once and expire quickly, so callauthorize()again.temporarily_unavailable: Discord sign-in isn't configured for the project, or Discord didn't answer. Retry later.