A standard OAuth 2.0 / OpenID Connect provider that uses Discord for authentication and role-based authorization.
- ✅ Full OAuth 2.0 Authorization Code flow
- ✅ OpenID Connect discovery endpoint
- ✅ RS256 JWT signing with JWKS endpoint
- ✅ Discord role → subteam mapping
- ✅ Redis-backed storage for auth codes and requests
- ✅ Standard endpoints for easy integration
Client App (Dashboard)
↓ redirects to
OAuth Provider (this app)
↓ redirects to
Discord OAuth
↓ callback with user + roles
OAuth Provider
↓ issues JWT with subteams
Client App
pip install -r requirements.txtGenerate an RSA keypair for JWT signing:
# Generate private key
openssl genrsa -out private_key.pem 2048
# Extract public key
openssl rsa -in private_key.pem -pubout -out public_key.pem# Using Docker
docker run -d -p 6379:6379 redis:latest
# Or install locally
# macOS: brew install redis && brew services start redis
# Ubuntu: sudo apt install redis-server && sudo systemctl start redisCopy .env.example to .env and fill in your values:
cp .env.example .envRequired configuration:
-
Discord OAuth App: Create at https://discord.com/developers/applications
- Set
DISCORD_CLIENT_ID,DISCORD_CLIENT_SECRET - Add redirect URI:
https://auth.yourclub.com/auth/discord/callback
- Set
-
Discord Bot: Create a bot in the same app
- Set
DISCORD_BOT_TOKEN - Enable "Server Members Intent"
- Invite to your guild with
guilds.members.readscope
- Set
-
Guild & Roles:
- Set
DISCORD_GUILD_IDto your Discord server ID - Map role IDs to subteam names in
ROLE_TO_SUBTEAM
- Set
-
OAuth Clients:
- Register each dashboard/app in
OAUTH_CLIENTS - Format:
{"client-id": {"client_secret": "secret", "redirect_uris": ["https://..."]}}
- Register each dashboard/app in
python app.py| Endpoint | Method | Purpose |
|---|---|---|
/.well-known/openid-configuration |
GET | OpenID Connect discovery |
/jwks.json |
GET | Public key for JWT verification |
/oauth/authorize |
GET | Start OAuth flow |
/oauth/token |
POST | Exchange code for tokens |
/userinfo |
GET | Get user info from token |
| Endpoint | Method | Purpose |
|---|---|---|
/auth/discord/callback |
GET | Discord OAuth callback |
Redirect user to:
GET https://auth.yourclub.com/oauth/authorize
?client_id=dfr-dashboard
&redirect_uri=https://dashboard.yourclub.com/auth/callback
&response_type=code
&scope=openid profile roles
&state=random_state_123
The OAuth provider redirects to Discord, user logs in and authorizes.
User is redirected back to your client:
GET https://dashboard.yourclub.com/auth/callback
?code=AUTH_CODE_HERE
&state=random_state_123
curl -X POST https://auth.yourclub.com/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-u "dfr-dashboard:supersecret" \
-d "grant_type=authorization_code" \
-d "code=AUTH_CODE_HERE" \
-d "redirect_uri=https://dashboard.yourclub.com/auth/callback"Response:
{
"access_token": "eyJhbGc...",
"id_token": "eyJhbGc...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid profile roles"
}The JWT contains:
{
"iss": "https://auth.yourclub.com",
"aud": "yourclub-services",
"sub": "123456789",
"discord_id": "123456789",
"discord_username": "user#1234",
"roles": ["123456789012345678", "234567890123456789"],
"subteams": ["software", "mechanical"],
"iat": 1700000000,
"exp": 1700003600
}Any service can verify the JWT using the public key from /jwks.json:
import jwt
import requests
# Fetch JWKS
jwks_res = requests.get("https://auth.yourclub.com/jwks.json")
jwks = jwks_res.json()
# Verify token (most libraries handle JWKS automatically)
payload = jwt.decode(
token,
jwks,
algorithms=["RS256"],
audience="yourclub-services",
issuer="https://auth.yourclub.com"
)
print(f"User: {payload['discord_username']}")
print(f"Subteams: {payload['subteams']}")auth_req:<discord_state>- Auth request data (TTL: 10 min)auth_code:<code>- Authorization code data (TTL: 5 min)
To rotate the JWT signing key:
- Generate new keypair
- Add to JWKS with new
kid - Update signing to use new
kid - Keep old key in JWKS for verification during transition
- Remove old key after all tokens expire
⚠️ HTTPS Required: Run behind reverse proxy with TLS in production⚠️ Secure Secrets: Use strongclient_secretvalues⚠️ Redis Security: Enable Redis AUTH in production⚠️ Rate Limiting: Add rate limiting to prevent abuse⚠️ OAUTHLIB_INSECURE_TRANSPORT: Remove in production (only for dev)
Any service that supports OAuth 2.0 / OIDC can integrate:
-
Configure the service with:
- Discovery URL:
https://auth.yourclub.com/.well-known/openid-configuration - Or manually set authorization/token/userinfo endpoints
- Discovery URL:
-
Register the service as a client in
OAUTH_CLIENTS -
Service will receive JWTs with
subteamsclaim for authorization
- Check Redis is running and accessible
- Verify
AUTH_REQUEST_TTLisn't too short - Check system time is synchronized
- Verify client is registered in
OAUTH_CLIENTS - Check
client_secretmatches - Ensure
redirect_uriis in allowed list
- Confirm public key in
/jwks.jsonmatches private key - Check token hasn't expired
- Verify
audandissclaims match configuration
MIT