Skip to main content

Discord OAuth

GuildGate OAuth helpers control state consumption, credential encryption, and token-refresh races on the server. Redirect routing and user-profile mapping remain in the application.

Authorization state​

Before redirecting, create random state, write its hash to the store, and associate it with a browser nonce.

A state record may contain:

  • stateHash
  • a safe returnTo path
  • locale
  • creation and expiry times
  • limited metadata

Use OAuthStateStore.consume() in the callback. Separate get and delete operations are vulnerable to replay races.

Return URL boundary​

Do not accept returnTo as an arbitrary external URL. Use only an application-relative path or a server-side allowlist. Reject protocol-relative values such as //evil.example.

Credential encryption​

Access and refresh tokens are stored as ciphertext in OAuthCredentialRecord. A key ring should provide:

  • one active write key
  • old keys that can read existing records
  • a key identifier or version

Tokens, client secrets, and authorization headers must not enter audit events.

Distributed refresh single-flight​

In 1.1.0, Discord token refresh uses the shared lock store:

  1. Read the credential.
  2. If refresh is needed, acquire a lease for the user/provider key.
  3. Read the credential again after acquiring the lease.
  4. If another worker already refreshed it, reuse the new token.
  5. Otherwise call the Discord token endpoint and update the record.

The second read prevents a waiting worker from reusing the same refresh token.

For long network calls, a custom lock store should support renewal. Stop the credential write if lease ownership is lost.

Production endpoint guard​

When Discord authorize or token endpoints are configurable, production requires HTTPS and a public host. Loopback, unspecified, and IPv4-mapped loopback targets are rejected.

Failure behavior​

  • invalid or consumed state: do not create a session
  • Discord invalid_grant: mark the credential unusable and require login again
  • network timeout: apply bounded retry
  • refresh lease unavailable: wait briefly or reread the credential
  • decryption failure: record a security event without exposing secret details

Scope policy​

Request only the Discord scopes the dashboard needs. Most guild dashboards use identify and guilds. Do not mix bot installation scopes with user OAuth scopes without a reason.

Callback checklist​

  • state is consumed in one operation
  • nonce or session association is verified
  • return path is allowed
  • token endpoint is called only by the server
  • credentials are encrypted at rest
  • refresh is single-flight across workers
  • the per-user session cap is applied after login
  • no token or secret appears in an error response or log