-
Notifications
You must be signed in to change notification settings - Fork 0
Authentication
🌐 English · Deutsch
The dashboard authenticates users with Discord OAuth2 and keeps the session in a signed, server-held cookie. The security model has a single guiding principle: secrets never reach the browser. The Discord client secret and the gateway token live only in the SvelteKit server (the BFF), the gateway only accepts connections from localhost plus a token, and every privileged action is re-checked against Red's permission system on the bot side. This page explains the login flow, the session model and the permission levels.

- The user clicks Login with Discord. The BFF (
GET /auth/login) generates a randomstatevalue, stores it in a short-lived httpOnly cookie, and redirects the browser to Discord's authorize URL with theidentifyandguildsscopes. - The user approves on Discord and is sent back to
DISCORD_REDIRECT_URI(/auth/callback). - The BFF (
GET /auth/callback) verifies the returnedstateagainst the cookie, then exchanges the authorizationcodefor an access token server-side usingDISCORD_CLIENT_SECRET. - With that token, the BFF fetches the Discord user (
identify) and derives the UI locale (de-DEfor German users, otherwiseen-US). - The BFF creates a session and sets the signed session cookie. The browser never sees the client secret or the raw access token handling.
Browser ──/auth/login──▶ BFF ──redirect──▶ Discord
Browser ◀──consent────── Discord
Browser ──/auth/callback (code,state)──▶ BFF ──code→token (secret)──▶ Discord
BFF ◀──user──────────────── Discord
Browser ◀──Set-Cookie: dks_session────── BFF
The session is stored in a cookie named dks_session. It is not an opaque database key — it is the user payload plus an HMAC-SHA256 signature keyed on SESSION_SECRET:
dks_session = base64url(payload) . base64url(HMAC-SHA256(payload, SESSION_SECRET))
| Property | Value |
|---|---|
| Cookie name | dks_session |
| Signature | HMAC-SHA256, verified with constant-time comparison |
| Flags |
httpOnly, sameSite=lax, secure in production |
| Lifetime | 7 days |
| Payload | user id, username, avatar, locale, issued-at (iat), token data |
Because the cookie is signed (not encrypted), it cannot be tampered with: any change to the payload invalidates the HMAC. The signing and verification happen exclusively in the BFF (lib/server/session.ts), so the SESSION_SECRET never leaves the server.
The bot owner can invalidate all sessions at once ("Refresh sessions" in Settings). This sets a session_epoch timestamp on the cog. On every request the BFF compares the session's iat against the current epoch (cached for ~60 s to avoid a round-trip per request) and clears any session older than the epoch.
The BFF talks to the bot over the cog's JSON-RPC gateway. Two things protect it:
-
Localhost binding. The gateway listens on
127.0.0.1by default — it is not reachable from the network. -
Shared token. Every BFF request carries the
X-Dashboard-Tokenheader. The cog compares it in constant time (compare_digest). Only the BFF knows the token.
The browser never talks to the gateway directly. It only ever calls the BFF's own /api/* endpoints, which then call the gateway server-side. See API & Gateway for the protocol.
When listing servers, the gateway returns only guilds where the user is actually a member (or where they are the bot owner). Even within a guild context, a logged-in non-member cannot read that guild's data: the server forces at least guild_member for any guild-scoped call, so manipulating a guild_id in a request cannot leak another server's content.
Every contribution (widget, panel, list, page) and every privileged RPC method declares a minimum permission level. The gateway resolves the user's effective level against Red's own permission system and enforces it server-side on every call — frontend filtering is only for UX.
| Level | Value | Mapped to Red |
|---|---|---|
authenticated |
0 | Logged in via Discord OAuth2 |
guild_member |
1 | Member of the guild |
guild_mod |
2 | Red's mod role (bot.is_mod) |
guild_admin |
3 | Red's admin role or Discord manage_guild
|
guild_owner |
4 | guild.owner_id == user.id |
bot_owner |
5 | bot.is_owner(user) |
Levels are cumulative: a higher level satisfies any lower requirement. The mapping lives in the cog's permissions.py.
secrets here ──┐
▼
Browser ──OAuth2──▶ BFF (SvelteKit server) ──token──▶ gateway (localhost) ──▶ Red bot
cookie (HMAC) DISCORD_CLIENT_SECRET compare_digest is_owner / is_admin / …
GATEWAY_TOKEN permission re-check
SESSION_SECRET
- Discord client secret, gateway token and session secret all live only in the BFF.
- The browser holds only a signed cookie — no usable secret.
- Permissions are enforced on the bot, not trusted from the frontend.
-
Configuration —
SESSION_SECRET,GATEWAY_TOKENand the OAuth2 variables - API & Gateway — how the BFF calls the gateway and where permissions are checked
- Architecture — the BFF pattern in detail
🇬🇧 English
Users
- Getting Started
- Configuration
- Authentication
- Web UI
- Feature Catalog
- Embed Builder
- Public Pages
- Cog Management
- Statistics
- Logs
- Custom Pages
- Deployment
- Self-Update
Developers