OAuth2 authorization-code flow
このコンテンツはまだ日本語訳がありません。
Status
Stable· Last verified 2026-07-11 · APIv10· Source OAuth2
The authorization-code grant logs a user in with Discord and lets your app act
on their behalf. The runnable
oauth2-login
example implements this flow (including refresh/revoke) with zero dependencies and
unit tests you can run without secrets.
The flow at a glance
Section titled “The flow at a glance”1. AUTHORIZE Redirect the user to Discord's consent screen with a `state` value.2. CONSENT User approves; Discord redirects to your redirect_uri with ?code&state3. VALIDATE Your server checks `state` matches what it issued (CSRF defense).4. EXCHANGE Your SERVER POSTs the code to the token endpoint (with client creds) → access_token (+ refresh_token, expires_in, scope).5. CALL API Use Authorization: Bearer <access_token> (e.g. GET /users/@me).6. MAINTAIN Refresh before expiry; revoke on logout.[!NOTE] A note on endpoint versions Discord documents the authorize endpoint without
/apior a version, and the token / revoke endpoints unversioned (https://discord.com/api/oauth2/token). A versioned form also resolves. Resource endpoints such as/users/@meare v10-pinned per the atlas API-version convention.
Step 1 — Send the user to authorize
Section titled “Step 1 — Send the user to authorize”https://discord.com/oauth2/authorize ?response_type=code &client_id=YOUR_CLIENT_ID &scope=identify # space-separated if multiple: "identify email" &redirect_uri=<url-encoded, EXACT match of a portal Redirect URI> &state=<opaque CSRF token> # &prompt=consent # optional: "consent" always shows the screen; "none" skips if already authorized # &integration_type=1 # optional: request a user-install (1) vs guild-install (0)Verified authorize parameters: response_type, client_id, scope,
redirect_uri, state, prompt, integration_type.
Step 2–3 — Handle the redirect and validate state
Section titled “Step 2–3 — Handle the redirect and validate state”Discord redirects to redirect_uri?code=…&state=…. Before doing anything,
confirm the returned state equals the one you issued (the example binds it to an
httpOnly cookie and compares in constant time). If it doesn’t match, abort — this
is your CSRF defense.
Step 4 — Exchange the code for tokens (server-side)
Section titled “Step 4 — Exchange the code for tokens (server-side)”POST https://discord.com/api/oauth2/tokenContent-Type: application/x-www-form-urlencoded
grant_type=authorization_codecode=THE_CODEredirect_uri=SAME_REDIRECT_URIClient authentication (verified): HTTP Basic auth (client_id:client_secret)
or client_id + client_secret supplied in the form body. Either way, the
secret is used server-side only.
A successful response is JSON with access_token, token_type (Bearer),
expires_in, refresh_token, and the granted scope. Authorization codes are
single-use — never retry an exchange with the same code.
Step 5 — Call the API as the user
Section titled “Step 5 — Call the API as the user”GET https://discord.com/api/v10/users/@meAuthorization: Bearer ACCESS_TOKENReturns the user object (id, username, global_name, avatar…). More
scopes unlock more endpoints — request the minimum.
Step 6 — Refresh and revoke
Section titled “Step 6 — Refresh and revoke”Refresh before expires_in elapses (verified params grant_type=refresh_token,
refresh_token, plus client auth):
POST https://discord.com/api/oauth2/tokengrant_type=refresh_token&refresh_token=…&client_id=…&client_secret=…The response is a new token set; store the possibly-rotated refresh_token.
Revoke on logout (verified endpoint + params token, optional
token_type_hint, plus client auth):
POST https://discord.com/api/oauth2/token/revoketoken=…&token_type_hint=access_token&client_id=…&client_secret=…[!CAUTION] Verified — revocation cascades “When you revoke a token, any active access or refresh tokens associated with that authorization will be revoked, regardless of the
tokenandtoken_type_hintvalues.” So revoking the access token also kills the refresh token (and vice versa) — revoke once to fully log the user out. Source: OAuth2.
The oauth2-login
example’s tokens.mjs implements refresh + revoke with an injectable fetch and
mocked-response tests.
Error handling
Section titled “Error handling”Authorization step (redirect errors). If the user denies consent or something is
wrong, Discord redirects to your redirect_uri with an error (e.g.
access_denied), usually an error_description, and your state — no
code. Handle it:
const err = url.searchParams.get("error");if (err) return render(`Authorization failed: ${url.searchParams.get("error_description") ?? err}`);Token step (HTTP errors). The token endpoint returns a non-2xx status with a JSON
body on failure. Check res.ok and surface the body:
const res = await fetch(tokenUrl, { method: "POST", headers, body });if (!res.ok) { const detail = await res.text(); // e.g. {"error":"invalid_grant"} throw new Error(`token exchange failed (${res.status}): ${detail}`);}[!TIP] Observation — standard OAuth2 error codes The token endpoint follows OAuth2 (RFC 6749) error responses. Common bodies:
invalid_grant(bad/expired/reused code or refresh token),invalid_client(bad credentials),invalid_scope,invalid_request. Treat exact strings as the RFC-standard set unless the docs specify otherwise; log the full body when debugging.
Security rules
Section titled “Security rules”client_secretis server-side only — never in a browser/mobile bundle. Reset it if leaked (OAuth2 → Reset Secret). See Security.- Always use
state(CSRF) and validate it on return; bind it to the session. - Exact
redirect_uri— mismatches are rejected; loose matching invites open redirects. - Least privilege scopes; HTTPS in production; store tokens server-side (create a session — don’t hand tokens to the browser).
- Scopes catalogue — every scope, verified, with access tiers.
- Other grants — client-credentials, implicit, and the PKCE status.
- Linked Roles — verify accounts for role gating.