Discord
Official plugin — Discord sign-in where email can be real, verified, or legitimately absent.
Discord is OAuth2 with one quirk: /users/@me email can be null
(phone-only accounts). oauth() signs those people in without an email
instead of failing them.
The one rule
Keep the email scope in the request (it is on by default). Without it Discord never reports
verified: true, and unverified emails cannot claim existing accounts.
Quick start
Create an application
Discord Developer Portal → Applications → New Application → OAuth2. Add a
redirect under OAuth2 → Redirects:
https://app.example.com/auth/oauth/callback/discord.
Plug it
import { oke } from "okengine";
import { oauth } from "okengine/plugins";
export const app = oke({
name: "shop",
env: "dev",
gate: { auth: {} },
}).plug(
oauth({
baseUrl: "https://app.example.com",
providers: {
discord: { enabled: true },
},
}),
);Set the client secret
# .env.local
OAUTH_DISCORD_CLIENT_SECRET=...How identity works
| Aspect | Behavior |
|---|---|
| Profile | GET https://discord.com/api/users/@me (string id is the subject) |
taken as-is when present; null flows through as no email | |
| Email trust | verified: true only; the flag silently going missing keeps the address unverified |
| Name | global_name, falling back to username |
Consequence: an integration bug that drops the verified field degrades
to unverified — never to falsely verified. That direction of failure is what
keeps account takeover off the table.
Default scopes: identify, email. The authorize URL always carries
prompt=consent.
Options
| Option | Type | Default | Meaning |
|---|---|---|---|
enabled | boolean | false | Turn the provider on |
clientId | string | Vault/env* | \*OAUTH_DISCORD_CLIENT_ID |
redirectUri | string | {baseUrl}…/discord | Exact registered URI |
scopes | string[] | driver defaults | Extra scopes |
storeProviderTokens | boolean | false | Keep tokens in Vault |
Surfaces
| Flow | Path |
|---|---|
| Start | POST /auth/oauth/discord/start |
| Callback | GET+POST /auth/oauth/callback/discord |
| Link | POST /auth/oauth/discord/link |
Troubleshooting
Phone-only Discord accounts expose email: null. The session works; the user
row simply has no address until they add one at Discord.
The client secret was rotated in the portal while old codes were in flight. Restart the flow — flow rows are single-use and expire after ten minutes.
The app lacks the email scope or the user has not confirmed their address at
Discord. Unverified emails provision new accounts but never take over
existing ones.
Learn more
- OAuth — shared flows and security model
- GitHub — primary-email lookup pattern
- Vault — where secrets live