This project lets you use Discord as an OpenID Connect provider for Cloudflare Access by wrapping Discord OAuth2 inside a Cloudflare Worker.
It was originally created by Erisa. This fork extends the idea with role support, cached role lookups, and a configurable email mode while keeping the original worker's goal intact. If this project helps you, please also consider supporting Erisa.
The worker sits between Cloudflare Access and Discord:
- Cloudflare Access sends the user to this worker's
/authorize/...endpoint. - The worker redirects the user to Discord OAuth2 with the right scopes.
- Discord sends the authorization code back to Cloudflare Access.
- Cloudflare Access calls this worker's
/tokenendpoint. - The worker exchanges the code, fetches Discord user data, optionally fetches guilds and roles, then signs an
id_token. - Cloudflare Access uses that token and the published JWK set from
/jwks.json.
Signing keys are generated once and stored in Workers KV.
- TypeScript-based Cloudflare Worker
- Hono routing
- Discord OAuth2 to OIDC bridge for Cloudflare Access
- Optional
guildsclaim - Optional per-guild
roles:<guild_id>claims - Hourly cached role lookups through KV
- Toggleable email behavior for environments where real Discord email claims are not wanted
/authorize/identify/authorize/email/authorize/guilds/authorize/roles/token/jwks.json
The /authorize/... endpoints expect:
client_idredirect_uri- optional
state
The client_id must match the configured Discord application id, and the redirect_uri must be present in config.json.
This fork supports two email modes through config.json:
includeEmail: trueThe worker requests Discord'semailscope, requires a verified Discord account, and emits the real Discord email in the ID token.includeEmail: falseThe worker does not request theemailscope, skips the verified-email requirement, and emitsfallbackEmailinstead.
This is useful because Cloudflare Access expects an email-like identity, but not every setup wants to depend on Discord email access.
Depending on the chosen authorize route and config, the worker can emit:
emailguildsroles:<guild_id>- most Discord user fields returned from
/users/@me
Copy config.sample.json to config.json and fill it in.
Example:
{
"clientId": "00000000000000",
"clientSecret": "AAAAAAAAAAAAAAAAAAA",
"redirectUrls": [
"https://YOURNAME.cloudflareaccess.com/cdn-cgi/access/callback"
],
"includeEmail": true,
"fallbackEmail": "oauth@discord.com",
"serversToCheckRolesFor": [
"123456789012345678"
],
"cacheRoles": false
}clientIdYour Discord application client id.clientSecretYour Discord application client secret.redirectUrlsAllowed redirect URLs. These must match what you configured in Discord and what Cloudflare Access will use.includeEmailWhether to request and expose the real Discord email claim.fallbackEmailEmail claim used whenincludeEmailisfalse.serversToCheckRolesForGuild ids that should be evaluated for role claims.cacheRolesWhether to use the scheduled KV-backed role cache.
- A Cloudflare account with Access / Zero Trust enabled
- A Discord application for OAuth2
- Node.js
- A Workers KV namespace
npm installSet the KV namespace in wrangler.toml.
Example:
kv_namespaces = [
{ binding = "KV", id = "YOUR_KV_ID" }
]If you want bot-based role lookups, add the bot token as a Worker secret:
npx wrangler secret put DISCORD_TOKENCreate a Discord application and add your Cloudflare Access callback URL as a redirect URI, for example:
https://YOURNAME.cloudflareaccess.com/cdn-cgi/access/callbackIn Cloudflare Zero Trust:
- Go to
Settings->Authentication - Add a new login method
- Choose
OpenID Connect - Fill in:
Auth URLhttps://YOUR_WORKER_HOST/authorize/emailor one of the other authorize modesToken URLhttps://YOUR_WORKER_HOST/tokenCertificate URLhttps://YOUR_WORKER_HOST/jwks.jsonApp IDYour Discord client idClient secretYour Discord client secretPKCEEnabled
If you want guild or role claims in Access policies, add those custom claims in the provider configuration.
Requests only identify, plus email if includeEmail is enabled.
Use this for the smallest possible identity flow.
Alias for the normal email-capable flow.
If includeEmail is disabled, this behaves like /authorize/identify.
Adds the guilds scope so the worker can emit the guilds claim.
Adds guilds.members.read so the worker can attempt role lookups through the user token when not using the cache path.
Recommended when possible.
- Set
cacheRolestotrue - Configure
serversToCheckRolesFor - Add
DISCORD_TOKENas a Worker secret - Invite the bot to every configured guild
The worker's scheduled handler refreshes role membership into KV every hour. During login, the token endpoint reads from KV instead of querying Discord live for each role lookup.
Use /authorize/roles and set cacheRoles to false.
This uses Discord's guilds.members.read scope and can hit rate limits more easily.
Use /authorize/guilds, set cacheRoles to false, and configure DISCORD_TOKEN.
This fetches member roles through the bot for guilds the user belongs to.
Start the worker with:
npm run startExample authorize URL:
http://127.0.0.1:8787/authorize/guilds?client_id=YOUR_CLIENT_ID&redirect_uri=https://YOURNAME.cloudflareaccess.com/cdn-cgi/access/callback&state=testIf you forget client_id, the worker will correctly return Bad request. like a tiny gatekeeping gremlin.
npm run start
npm run typecheck
npm run cf-typegen- The worker currently reads
clientSecretfromconfig.jsonbecause that matches the existing project shape. worker-configuration.d.tsis generated by Wrangler vianpm run cf-typegen.- After changing
wrangler.toml, rerunnpm run cf-typegen.
- Original project and core idea by Erisa
- Additional inspiration from kimcore/discord-oidc
- Additional inspiration from eidam/cf-access-workers-oidc