Skip to main content

OAuth Provider Setup

This guide walks you through creating OAuth applications for each platform. Lumio uses distinct credential sources for login, channel, and bot flows. They are stored in different places and are not interchangeable:

Login credentialsSystem channel credentialsSystem bot credentialsAccount channel/bot credentials
PurposeUser authentication ("Sign in with Twitch")Lumio-managed channel OAuth appsLumio-managed bot OAuth appsPer-account platform API access (chat, events, moderation)
Stored inapps/id/.env.local and apps/api/config/local.toml[auth] *_channel_client_id / *_channel_client_secret keys in the API[auth] *_bot_client_id / *_bot_client_secret keys in the APIThe app_credentials database table (AES-256-GCM encrypted)
Entered byThe operator, once, in config filesThe operator, once per environment / provider appThe operator, once per environment / provider appEach account, in Dashboard → Connections → App Credentials
ScopesUser-level, defined in apps/id/src/auth.tsChannel scopes, defined in apps/api/src/platforms.rsBot scopes, defined in apps/api/src/platforms.rsChannel/bot scopes, defined in apps/api/src/platforms.rs
Resulting recordlogin_connectionschannel_connections, pinned credential_source = "system"bot_connections, pinned credential_source = "system"channel_connections or bot_connections, pinned credential_source = "account"
caution

Login credentials are not channel credentials, and channel credentials are not bot credentials. System channel and bot credentials are the global Lumio OAuth apps used only by connections pinned to system; account credentials are used only by connections pinned to account. The login scopes in apps/id/src/auth.ts are user-level — they are not a subset of the channel scopes and must not be stripped on the assumption that they belong on the channel side.

A third surface — bot connections — is covered in Bot Connections at the end of this guide.

Redirect URI Registration Matrix

System-sourced channel connections, account bot connections, and admin global-bot connections use the unified web-host callback:

{web.public_url}/api/connections/oauth/{platform}/callback

For production Twitch/Kick/Trovo system apps, register:

https://lumio.vision/api/connections/oauth/{platform}/callback

For staging, register:

https://lumio.web.staging.zaflun.dev/api/connections/oauth/{platform}/callback

For local development, register:

http://localhost:4000/api/connections/oauth/{platform}/callback

The server dispatches the callback by the OAuth state handle's kind (channel, bot, or global_bot) and return_to. Account/BYO channel connections and degraded bot flows still use source-specific legacy callback URLs because their authorize path emits the legacy state format.

Login apps

Login OAuth belongs to the ID app. These apps are separate from system channel/bot apps.

PlatformProduction redirect URIStaging redirect URILocal redirect URINotes
Twitch loginhttps://id.lumio.vision/api/auth/callback/twitchhttps://lumio.id.staging.zaflun.dev/api/auth/callback/twitchhttp://localhost:4002/api/auth/callback/twitchTwitch allows multiple redirect URIs per app, so the login pair can also serve as the Twitch channel fallback when the channel pair is unset and the app also registers the unified web callback.
YouTube / Google loginhttps://id.lumio.vision/api/auth/callback/googlehttps://lumio.id.staging.zaflun.dev/api/auth/callback/googlehttp://localhost:4002/api/auth/callback/googleThe ID app provider slug is google; channel connections use the Lumio platform slug youtube.
Kick loginhttps://id.lumio.vision/api/auth/callback/kickhttps://lumio.id.staging.zaflun.dev/api/auth/callback/kickhttp://localhost:4002/api/auth/callback/kickKick accepts one redirect URI per app, so the login app cannot be reused for channel or bot system OAuth. Use separate apps for hosted and local development.
Trovo loginhttps://id.lumio.vision/api/auth/callback/trovohttps://lumio.id.staging.zaflun.dev/api/auth/callback/trovohttp://localhost:4002/api/auth/callback/trovoTrovo accepts one redirect URI per app, so the login app cannot be reused for channel or bot system OAuth. Use separate apps for hosted and local development.

System channel and bot apps

System OAuth app pairs live in the API [auth] config. Channel apps use *_channel_client_id / *_channel_client_secret; dedicated bot apps use *_bot_client_id / *_bot_client_secret. The Channel app and Bot app for the same provider register the same callback in a given environment; the encrypted state handle selects whether Lumio redeems the code with the channel, account-bot, or admin global-bot secret pair.

Platform / appProduction redirect URIStaging redirect URILocal redirect URINotes
Twitch Channel apphttps://lumio.vision/api/connections/oauth/twitch/callbackhttps://lumio.web.staging.zaflun.dev/api/connections/oauth/twitch/callbackhttp://localhost:4000/api/connections/oauth/twitch/callbackTwitch supports multiple redirect URIs on one app. A single Twitch Channel app can carry production, staging and local callbacks when that matches the operator model.
Twitch Bot apphttps://lumio.vision/api/connections/oauth/twitch/callbackhttps://lumio.web.staging.zaflun.dev/api/connections/oauth/twitch/callbackhttp://localhost:4000/api/connections/oauth/twitch/callbackDedicated bot keys are preferred. If absent, bot connects fall back to the Twitch channel pair, then to the Twitch login pair.
Kick Channel apphttps://lumio.vision/api/connections/oauth/kick/callbackhttps://lumio.web.staging.zaflun.dev/api/connections/oauth/kick/callbackhttp://localhost:4000/api/connections/oauth/kick/callbackKick accepts one redirect URI per app. Use one Channel app per environment, including a separate local development app.
Kick Bot apphttps://lumio.vision/api/connections/oauth/kick/callbackhttps://lumio.web.staging.zaflun.dev/api/connections/oauth/kick/callbackhttp://localhost:4000/api/connections/oauth/kick/callbackThe one-URI limit is per app, so the Channel app and Bot app may each register the same environment callback.
Trovo Channel apphttps://lumio.vision/api/connections/oauth/trovo/callbackhttps://lumio.web.staging.zaflun.dev/api/connections/oauth/trovo/callbackhttp://localhost:4000/api/connections/oauth/trovo/callbackTrovo accepts one redirect URI per app. Use one Channel app per environment, including a separate local development app.
Trovo Bot apphttps://lumio.vision/api/connections/oauth/trovo/callbackhttps://lumio.web.staging.zaflun.dev/api/connections/oauth/trovo/callbackhttp://localhost:4000/api/connections/oauth/trovo/callbackThe one-URI limit is per app, so the Channel app and Bot app may each register the same environment callback.
YouTube Bot apphttps://lumio.vision/api/connections/oauth/youtube/callbackhttps://lumio.web.staging.zaflun.dev/api/connections/oauth/youtube/callbackhttp://localhost:4000/api/connections/oauth/youtube/callbackYouTube channel connections remain BYO/account. The system callback is only for an operator-managed YouTube bot app.

Account/BYO callbacks

Account-owned credentials are entered per account in Dashboard → Connections → App Credentials and are stored in the database. These callbacks are legacy/BYO callbacks: register them only for account-managed connections, for local setups intentionally using account credentials, or for platforms that have degraded from system to account because no system pair is configured.

Platform / flowProduction redirect URIStaging redirect URILocal redirect URINotes
Twitch BYO channelhttps://lumio.vision/api/connections/channel/twitch/callbackhttps://lumio.web.staging.zaflun.dev/api/connections/channel/twitch/callbackhttp://localhost:4000/api/connections/channel/twitch/callbackNeeded only when Twitch channel connects use account credentials.
YouTube BYO channelhttps://lumio.vision/api/connections/channel/youtube/callbackhttps://lumio.web.staging.zaflun.dev/api/connections/channel/youtube/callbackhttp://localhost:4000/api/connections/channel/youtube/callbackYouTube channel connections are BYO/account; the platform slug is youtube, not google.
Kick BYO channelhttps://lumio.vision/api/connections/channel/kick/callbackhttps://lumio.web.staging.zaflun.dev/api/connections/channel/kick/callbackhttp://localhost:4000/api/connections/channel/kick/callbackNeeded only when Kick channel connects use account credentials.
Trovo BYO channelhttps://lumio.vision/api/connections/channel/trovo/callbackhttps://lumio.web.staging.zaflun.dev/api/connections/channel/trovo/callbackhttp://localhost:4000/api/connections/channel/trovo/callbackNeeded only when Trovo channel connects use account credentials.
Spotify BYO channelhttps://lumio.vision/api/connections/channel/spotify/callbackhttps://lumio.web.staging.zaflun.dev/api/connections/channel/spotify/callbackhttp://127.0.0.1:4000/api/connections/channel/spotify/callbackSpotify rejects localhost; Lumio rewrites localhost to 127.0.0.1 only for Spotify local callbacks.
Account bot legacy callbackhttps://lumio.vision/api/connections/bot/{platform}/callbackhttps://lumio.web.staging.zaflun.dev/api/connections/bot/{platform}/callbackhttp://localhost:4000/api/connections/bot/{platform}/callbackUsed only when an account bot flow degrades to account credentials instead of using a system bot/channel pair.
Admin global-bot legacy callbackhttps://admin.lumio.vision/api/bot-connections/{platform}/callbackhttps://lumio.admin.staging.zaflun.dev/api/bot-connections/{platform}/callbackhttp://localhost:4001/api/bot-connections/{platform}/callbackKept for legacy/in-flight global bot authorizations. Current system global bots return through the web-host unified callback and then redirect back to Admin via return_to.

Production needs six system apps for the Twitch, Kick, and Trovo system-managed paths: one Channel app and one Bot app per platform. A production + staging setup uses ten apps: Twitch can register both production and staging redirect URIs on each of its two apps, while Kick and Trovo need separate Channel and Bot apps per environment.

PlatformProduction system appsStaging system appsSystem callback
Twitch2 apps: Channel + Botsame 2 appshttps://lumio.vision/api/connections/oauth/twitch/callback and https://lumio.web.staging.zaflun.dev/api/connections/oauth/twitch/callback
Kick2 apps: Channel + Bot2 apps: Channel + Botone callback per environment: .../api/connections/oauth/kick/callback
Trovo2 apps: Channel + Bot2 apps: Channel + Botone callback per environment: .../api/connections/oauth/trovo/callback

Kick and Trovo limit redirect URIs per app, not apps per URI. Their Channel app and Bot app both register the same environment callback, for example https://lumio.vision/api/connections/oauth/kick/callback; the state.kind value selects whether Lumio uses the channel secret pair or the bot secret pair. Their login app cannot fall back into channel or bot system mode because its only URI is {ID_APP_URL}/api/auth/callback/{platform}.

YouTube bot credentials have dedicated [auth] key slots too (youtube_bot_client_id / youtube_bot_client_secret), but YouTube channel connections stay BYO/account and there is no YouTube channel system app to fall back to. Without the YouTube bot keys, the YouTube bot flow degrades to account.


Twitch

Developer Console

https://dev.twitch.tv/console

Login Credentials (ID App)

  1. Go to ApplicationsRegister Your Application
  2. Set Name to something like Lumio Login
  3. Set OAuth Redirect URL to: {ID_APP_URL}/api/auth/callback/twitch
    • Local: http://localhost:4002/api/auth/callback/twitch
  4. Set Category to Website Integration
  5. Copy Client ID and generate a Client Secret
  6. Add to apps/id/.env.local:
    AUTH_TWITCH_ID=your_client_id
    AUTH_TWITCH_SECRET=your_client_secret

Login Scopes (requested automatically):

ScopePurpose
openidOpenID Connect authentication
user:read:emailRead user email
user:read:emotesRead user emotes
chat:readRead chat messages
chat:editSend chat messages
user:write:chatWrite chat messages
user:read:subscriptionsRead user subscriptions
moderator:read:chattersRead chatter list
moderator:manage:chat_messagesDelete chat messages
moderator:manage:banned_usersBan/unban users

Channel Credentials

Twitch channel connections default to system. Configure a global Twitch channel app in the API [auth] config (twitch_channel_client_id / twitch_channel_client_secret) and register the unified callback:

{web.public_url}/api/connections/oauth/twitch/callback

Twitch allows multiple redirect URIs on one app, so if the channel-specific pair is empty, Lumio may use the Twitch login pair as the system pair. If no global pair is available, new Twitch channel connects degrade to the account-credential path and use the legacy account callback:

{WEB_URL}/api/connections/channel/twitch/callback

Channel Scopes (27 scopes, requested automatically):

ScopePurpose
channel:read:subscriptionsRead subscriber list and events
channel:read:redemptionsRead channel point redemptions
channel:manage:redemptionsManage channel point redemptions
channel:read:hype_trainRead hype train events
channel:read:pollsRead poll data
channel:manage:pollsCreate and manage polls
channel:read:predictionsRead prediction data
channel:manage:predictionsCreate and manage predictions
channel:read:goalsRead channel goals
bits:readRead cheer/bits events
moderator:read:followersRead follower list
moderator:read:suspicious_usersRead suspicious user flags
moderator:manage:suspicious_usersUpdate suspicious user treatment
moderator:manage:banned_usersBan and unban users
channel:botSend chat messages as the channel bot
user:read:chatRead chat messages
channel:read:adsRead ad schedule
channel:manage:raidsStart and cancel raids
channel:moderateModerate chat
moderator:read:blocked_termsRead blocked terms
moderator:read:chat_settingsRead chat settings
moderator:read:unban_requestsRead unban requests
moderator:read:banned_usersRead banned users list
moderator:read:chat_messagesRead chat messages as moderator
moderator:read:warningsRead user warnings
moderator:read:moderatorsRead moderator list
moderator:read:vipsRead VIP list

Bot Credentials

Twitch bot connections default to system. Configure a separate Twitch bot app in [auth] with twitch_bot_client_id / twitch_bot_client_secret and register the same unified callback as the channel app:

{web.public_url}/api/connections/oauth/twitch/callback

If the Twitch bot pair is empty, the bot flow falls back to the Twitch channel pair, then to the Twitch login pair, and logs a one-time warning. If neither bot nor channel/login keys are available, the bot flow degrades to account.


YouTube / Google

Developer Console

https://console.cloud.google.com/apis/credentials

Prerequisites

  1. Create a Google Cloud project (or use an existing one)
  2. Enable YouTube Data API v3 under APIs & Services > Library
  3. Configure OAuth consent screen under APIs & Services > OAuth consent screen
    • User Type: External (or Internal for Google Workspace)
    • Add the scopes listed below
    • Add test users if app is in "Testing" status

Login Credentials (ID App)

  1. Go to APIs & Services > CredentialsCreate CredentialsOAuth 2.0 Client ID
  2. Application type: Web application
  3. Set Authorized redirect URI to: {ID_APP_URL}/api/auth/callback/google
    • Local: http://localhost:4002/api/auth/callback/google
  4. Copy Client ID and Client Secret
  5. Add to apps/id/.env.local:
    AUTH_GOOGLE_ID=your_client_id
    AUTH_GOOGLE_SECRET=your_client_secret

Login Scopes:

ScopePurpose
openidOpenID Connect authentication
emailUser email address
profileUser name and profile picture
youtube.readonlyRead YouTube channel data — also used at login to fetch snippet.customUrl for the username
youtube.force-sslYouTube Data API access (live chat)

Username resolution. Right after the OAuth handshake the ID App calls youtube/v3/channels?part=snippet&mine=true with the user's fresh access token and stores snippet.customUrl (e.g. cruex_de) as the login connection's username. Without this lookup Google's OIDC profile only exposes email (typically a …@pages.plusgoogle.com pseudo-email), which would never match the YouTube display name a viewer actually @-mentions in chat. If the YouTube call fails (no channel attached to the Google account, scope revoked) we fall back to email. The handle drives both the multichat @-mention highlighter and the broadcaster-status fallback that unlocks moderation buttons before the broadcaster's first chat message.

Channel Credentials

YouTube channel connections are BYO/account. Each account supplies its own Google Cloud OAuth client.

  1. Create a separate OAuth 2.0 Client ID in the same Google Cloud project
  2. Set Authorized redirect URI to: {WEB_URL}/api/connections/channel/youtube/callback
    • Local: http://localhost:4000/api/connections/channel/youtube/callback
    • The channel-connection platform slug is youtube, not google — only the ID App's login callback uses google.
  3. Users enter their own Client ID + Secret in Dashboard → Connections → App Credentials

Channel Scopes (2 scopes):

ScopePurpose
youtube.readonlyRead channel, video, and live chat data
youtube.force-sslFull YouTube Data API access (chat, comments)
info

Both login and channel credentials request access_type=offline and prompt=consent to ensure refresh tokens are always issued.

Bot Credentials

YouTube bot connections have a system credential mode but no channel-system app to fall back to. Configure youtube_bot_client_id / youtube_bot_client_secret in [auth] and register the unified callback when the deployment uses an operator-managed YouTube bot app:

{web.public_url}/api/connections/oauth/youtube/callback

If the YouTube bot pair is empty, the YouTube bot flow degrades to account.


Discord

Developer Console

https://discord.com/developers/applications

Login Credentials

Discord has no channel connection — it is a login provider, a bot platform, and a guild integration. Only the login flow uses OAuth credentials from config; the Discord bot authenticates with a static bot token instead of OAuth.

  1. Go to ApplicationsNew Application
  2. Under OAuth2, copy Client ID and Client Secret
  3. Add Redirect URL: {ID_APP_URL}/api/auth/callback/discord
    • Local: http://localhost:4002/api/auth/callback/discord
  4. Add to apps/id/.env.local:
    AUTH_DISCORD_ID=your_client_id
    AUTH_DISCORD_SECRET=your_client_secret

Login Scopes:

ScopePurpose
identifyRead user identity
emailRead user email
guildsRead server list
messages.readRead messages

Kick

Developer Console

https://kick.com/settings/developer

Login Credentials (ID App)

  1. Create a new application in the Kick Developer settings
  2. Set Redirect URL to: {ID_APP_URL}/api/auth/callback/kick
    • Local: http://localhost:4002/api/auth/callback/kick
  3. Copy Client ID and Client Secret
  4. Add to apps/id/.env.local:
    AUTH_KICK_ID=your_client_id
    AUTH_KICK_SECRET=your_client_secret

Login Scopes:

ScopePurpose
user:readRead user profile
chat:writeSend chat messages

Channel Credentials

Kick channel connections default to system. Create a separate app per environment and register that environment's unified callback:

{web.public_url}/api/connections/oauth/kick/callback

Set kick_channel_client_id and kick_channel_client_secret in the API [auth] config. There is no fallback to the Kick login credentials because the login app's single redirect URI is already {ID_APP_URL}/api/auth/callback/kick. If no global Kick channel pair is configured, new connects degrade to the account-credential path and use the legacy account callback:

{WEB_URL}/api/connections/channel/kick/callback

Channel Scopes (6 scopes):

ScopePurpose
user:readRead user profile information
channel:readRead channel information
events:readRead channel events
events:subscribeSubscribe to real-time events
chat:writeSend chat messages
moderation:chat_message:manageDelete chat messages

Bot Credentials

Kick bot connections default to system. Create a separate Kick Bot app per environment, register the same unified callback as the Kick Channel app, and set kick_bot_client_id / kick_bot_client_secret in the API [auth] config. The one-URI limit is per Kick app, so the Channel app and Bot app can both use:

{web.public_url}/api/connections/oauth/kick/callback

If the Kick bot pair is empty, the bot flow falls back to the Kick channel pair and logs a one-time warning. If neither pair is available, the bot flow degrades to account.


Trovo

Developer Console

https://developer.trovo.live/myconsole

Login Credentials (ID App)

  1. Create a new application in the Trovo Developer Console
  2. Set Redirect URL to: {ID_APP_URL}/api/auth/callback/trovo
    • Local: http://localhost:4002/api/auth/callback/trovo
  3. Copy Client ID and Client Secret
  4. Add to apps/id/.env.local:
    AUTH_TROVO_ID=your_client_id
    AUTH_TROVO_SECRET=your_client_secret

Login Scopes:

ScopePurpose
user_details_selfRead own user details
chat_send_selfSend chat messages

Channel Credentials

Trovo channel connections default to system. Create a separate app per environment and register that environment's unified callback:

{web.public_url}/api/connections/oauth/trovo/callback

Set trovo_channel_client_id and trovo_channel_client_secret in the API [auth] config. There is no fallback to the Trovo login credentials because the login app's single redirect URI is already {ID_APP_URL}/api/auth/callback/trovo. If no global Trovo channel pair is configured, new connects degrade to the account-credential path and use the legacy account callback:

{WEB_URL}/api/connections/channel/trovo/callback

Channel Scopes (2 scopes):

ScopePurpose
channel_details_selfRead own channel details
channel_subscriptionsRead subscriber data

Bot Credentials

Trovo bot connections default to system. Create a separate Trovo Bot app per environment, register the same unified callback as the Trovo Channel app, and set trovo_bot_client_id / trovo_bot_client_secret in the API [auth] config:

{web.public_url}/api/connections/oauth/trovo/callback

If the Trovo bot pair is empty, the bot flow falls back to the Trovo channel pair and logs a one-time warning. If neither pair is available, the bot flow degrades to account.


Spotify

Developer Console

https://developer.spotify.com/dashboard

Channel Credentials Only

Spotify is channel connection only — there is no Spotify login.

  1. Go to DashboardCreate App
  2. Set Redirect URI to: {WEB_URL}/api/connections/channel/spotify/callback
    • Local: http://127.0.0.1:4000/api/connections/channel/spotify/callback
    • Spotify rejects localhost redirect URIs, so Lumio rewrites localhost127.0.0.1 for Spotify (and only Spotify) when building the callback URL. Register the 127.0.0.1 form.
  3. Under "Which API/SDKs are you planning to use?", select both Web API and Web Playback SDK
  4. Users enter their own Client ID + Secret in Dashboard → Connections → App Credentials

Channel Scopes (7 scopes):

ScopePurpose
user-read-playback-stateRead current playback state
user-modify-playback-stateControl playback (play, pause, skip)
user-read-currently-playingRead currently playing track
playlist-read-privateRead private playlists
playlist-read-collaborativeRead collaborative playlists
playlist-modify-publicModify public playlists
playlist-modify-privateModify private playlists
Spotify Auth Style

Spotify uses Authorization: Basic base64(client_id:client_secret) for token refresh, unlike other platforms which send credentials in the POST body.


Where to Configure

Login credentials must be set in two places — the ID App's .env.local and the API's config/local.toml. The API needs them for login token refresh and Twitch chat API calls.

1. ID App (apps/id/.env.local)

Used by NextAuth for the OAuth login flow:

# NextAuth
AUTH_SECRET=generate-a-random-secret-here
AUTH_URL=http://localhost:4002

# Twitch
AUTH_TWITCH_ID=
AUTH_TWITCH_SECRET=

# Discord
AUTH_DISCORD_ID=
AUTH_DISCORD_SECRET=

# Google / YouTube
AUTH_GOOGLE_ID=
AUTH_GOOGLE_SECRET=

# Kick
AUTH_KICK_ID=
AUTH_KICK_SECRET=

# Trovo
AUTH_TROVO_ID=
AUTH_TROVO_SECRET=

2. API Server (apps/api/config/local.toml)

local.toml is git-ignored and is the last file layer before environment variables (default.toml → {env}.toml → local.toml → ENV). The API uses these for login token refresh and Twitch chat operations. They must match the ID App's credentials:

[auth]
# Same credentials as AUTH_TWITCH_ID / AUTH_TWITCH_SECRET in ID App
twitch_client_id = "..."
twitch_client_secret = "..."
# Same credentials as AUTH_GOOGLE_ID / AUTH_GOOGLE_SECRET in ID App
google_client_id = "..."
google_client_secret = "..."
# Same client id as AUTH_DISCORD_ID in ID App (audience validation only — see below).
discord_client_id = "..."
# Same credentials as AUTH_KICK_ID / AUTH_KICK_SECRET in ID App
kick_client_id = "..."
kick_client_secret = "..."
# Same credentials as AUTH_TROVO_ID / AUTH_TROVO_SECRET in ID App
trovo_client_id = "..."
trovo_client_secret = "..."

# Global system Channel apps. Twitch may fall back to the login pair; Kick and
# Trovo cannot because each provider app accepts one redirect URI.
twitch_channel_client_id = "..."
twitch_channel_client_secret = "..."
kick_channel_client_id = "..."
kick_channel_client_secret = "..."
trovo_channel_client_id = "..."
trovo_channel_client_secret = "..."

# Dedicated global system Bot apps. If one of these pairs is empty, that bot
# flow falls back to the platform's channel pair and logs a warning. If neither
# bot nor channel keys are configured, the bot flow degrades to account/BYO.
twitch_bot_client_id = "..."
twitch_bot_client_secret = "..."
kick_bot_client_id = "..."
kick_bot_client_secret = "..."
trovo_bot_client_id = "..."
trovo_bot_client_secret = "..."
youtube_bot_client_id = "..."
youtube_bot_client_secret = "..."

# AES-256-GCM key used to encrypt OAuth tokens and credentials at rest.
# Generate with: openssl rand -base64 32
token_encryption_key = "..."

Every one of these keys can also be supplied as an environment variable, which wins over the file: LUMIO__AUTH__TWITCH_CLIENT_ID, LUMIO__AUTH__TWITCH_CHANNEL_CLIENT_ID, LUMIO__AUTH__TWITCH_BOT_CLIENT_ID, LUMIO__AUTH__DISCORD_CLIENT_ID, LUMIO__AUTH__TOKEN_ENCRYPTION_KEY, and so on.

warning

The credentials in apps/api/config/local.toml must be identical to those in apps/id/.env.local. The API uses them to refresh login tokens — if they don't match, token refresh will fail silently and users will be logged out.

[auth] carries a Discord entry — discord_client_id — but no discord_client_secret: Discord login tokens are not refreshed by the API, so no secret is needed. The client id is required for token-exchange audience validation (below).

Audience validation is mandatory (fail-closed)

On every token exchange (POST /v1/auth/token, POST /v1/auth/authorize, GraphQL exchangeToken, POST /v1/auth/link) the API validates both the token's subject (the claimed user) and its audience — the OAuth client the token was minted for. Twitch's client_id, Google's aud, and Discord's application.id must each match the configured twitch_client_id / google_client_id / discord_client_id.

This blocks a confused-deputy attack where an attacker registers their own OAuth app, gets a victim to authorize it, and replays the victim-subject token against Lumio (ZAF-1015). The check is fail-closed: if a provider's client id is unset, token exchange for that provider fails (HTTP 500 / INTERNAL_SERVER_ERROR) rather than skipping the check. A foreign-audience token is rejected as an invalid credential (HTTP 401, audited as user:login_failed), identically to a subject mismatch. Set all three client ids in any deployment that serves logins for that provider.

System app environment variables

Config keyEnvironment variable
twitch_channel_client_idLUMIO__AUTH__TWITCH_CHANNEL_CLIENT_ID
twitch_channel_client_secretLUMIO__AUTH__TWITCH_CHANNEL_CLIENT_SECRET
kick_channel_client_idLUMIO__AUTH__KICK_CHANNEL_CLIENT_ID
kick_channel_client_secretLUMIO__AUTH__KICK_CHANNEL_CLIENT_SECRET
trovo_channel_client_idLUMIO__AUTH__TROVO_CHANNEL_CLIENT_ID
trovo_channel_client_secretLUMIO__AUTH__TROVO_CHANNEL_CLIENT_SECRET
twitch_bot_client_idLUMIO__AUTH__TWITCH_BOT_CLIENT_ID
twitch_bot_client_secretLUMIO__AUTH__TWITCH_BOT_CLIENT_SECRET
kick_bot_client_idLUMIO__AUTH__KICK_BOT_CLIENT_ID
kick_bot_client_secretLUMIO__AUTH__KICK_BOT_CLIENT_SECRET
trovo_bot_client_idLUMIO__AUTH__TROVO_BOT_CLIENT_ID
trovo_bot_client_secretLUMIO__AUTH__TROVO_BOT_CLIENT_SECRET
youtube_bot_client_idLUMIO__AUTH__YOUTUBE_BOT_CLIENT_ID
youtube_bot_client_secretLUMIO__AUTH__YOUTUBE_BOT_CLIENT_SECRET

3. Account Credentials (Database)

Account channel/bot credentials are not configured in files. Each user enters their own Client ID + Secret via Dashboard → Connections → App Credentials. They are stored AES-256-GCM encrypted in the app_credentials table, one row per (account, platform). The resulting access/refresh tokens land in channel_connections or bot_connections, also encrypted.


Bot Connections

Bot connections let Lumio post to chat with a bot identity instead of the channel identity. The OAuth bot platforms are Twitch, YouTube, Kick, and Trovo; Spotify has no bot connection and Discord uses a static admin bot token.

OAuth bot connections default to the system credential source and use dedicated bot app keys from [auth]: twitch_bot_client_id, kick_bot_client_id, trovo_bot_client_id, or youtube_bot_client_id plus the matching secret. Register the unified web-host callback for system bot flows:

{web.public_url}/api/connections/oauth/{platform}/callback

The admin global-bot flow starts in Admin → Providers, returns through the web host, and redirects back to the admin Providers page through the state handle's return_to. Discord global bot setup is not OAuth: paste the Discord bot token in Admin → Providers.

When a deployment has no global key pair for a bot platform, the account bot authorize path degrades to the legacy account source and uses:

{WEB_URL}/api/connections/bot/{platform}/callback
PlatformBot scopes
Twitchchat:read, chat:edit, user:write:chat, user:bot, user:read:chat
YouTubeyoutube.force-ssl
Kickchat:write
Trovochat_send_self
Discordno OAuth — the Discord bot uses a static bot token

If a platform's dedicated bot keys are unset but the channel keys exist, the bot flow uses the channel keys and logs a warning. This keeps local and operator-managed installs working while making the missing bot app visible. If both bot and channel keys are missing, the flow falls back to account, which is the normal local development path.

Kick production verification now applies to both production Kick apps. The Bot app is the one whose chat.message.sent subscription cap matters for chat sending: unverified apps are capped at 1,000 subscriptions for that event type, verified apps at 10,000.

See Bot Connections for the full flow.

See Also