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.

<Callout title="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.
</Callout>

## Quick start

<Steps>

<Step>
### Create an application

Discord Developer Portal → Applications → **New Application** → OAuth2. Add a
redirect under OAuth2 → Redirects:
`https://app.example.com/auth/oauth/callback/discord`.

</Step>

<Step>
### Plug it

```typescript title="src/app.ts"
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 },
    },
  }),
);
```

</Step>

<Step>
### Set the client secret

```text
# .env.local
OAUTH_DISCORD_CLIENT_SECRET=...
```

</Step>

</Steps>

## How identity works

| Aspect      | Behavior                                                                            |
| ----------- | ----------------------------------------------------------------------------------- |
| Profile     | `GET https://discord.com/api/users/@me` (string `id` is the subject)                |
| Email       | 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

<Accordions>
<Accordion title="Users sign in with no email attached">

Phone-only Discord accounts expose `email: null`. The session works; the user
row simply has no address until they add one at Discord.

</Accordion>
<Accordion title="invalid_oauth2 error code">

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.

</Accordion>
<Accordion title="Email never marked verified">

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.

</Accordion>
</Accordions>

## Learn more

- [OAuth](/docs/plugins/oauth) — shared flows and security model
- [GitHub](/docs/plugins/github) — primary-email lookup pattern
- [Vault](/docs/elements/vault) — where secrets live

## Next

<Cards>
  <Card
    title="GitHub"
    description="OAuth2 with verified-email lookup."
    href="/docs/plugins/github"
  />
  <Card title="Facebook" description="Never-verified emails." href="/docs/plugins/facebook" />
  <Card title="Google" description="OIDC reference provider." href="/docs/plugins/google" />
</Cards>
