Channel authentication cutover runbook
This runbook moves an environment from API-issued Telegram sessions to Supabase sessions issued through the API's channel authentication. Accounts from the API-issued Telegram sessions are not carried over. Every person signs in again and gets a new Supabase user, professional user and client account.
Run it first in a test environment, then in production. Production rollout is a separate, scheduled operational action.
:::warning Not yet rehearsed No test-environment rehearsal of this runbook has been recorded, and hosted Supabase acceptance has not been run. Treat the steps as the planned procedure and record the rehearsal's results before using it in production. :::
What changes at once
The API release that ships with this change replaces the old Telegram exchange, bot webhook and session secrets with channel authentication, and rejects every API-issued Mini App token. So these switch together, in one window:
- the API release;
- both Mini App releases, which sign in through the new
/auth/*routes; - both bots' webhooks, which move to
/webhooks/telegram/:app.
Supabase Auth fetches the issuer's discovery document when the provider is registered, so the provider can be registered only once the API release serves it, at the start of the window.
Create or update the provider locally
After applying the database migrations, start the API and its public HTTPS
tunnel. The tunnel must forward to the API (development port 3000). The API
serves the issuer itself; no separate issuer process is needed.
Set these values in apps/api/.env or the shell environment:
| Variable | Value |
|---|---|
SUPABASE_URL | Supabase gateway origin, usually http://127.0.0.1:54321 locally |
CHANNEL_AUTH_ISSUER_URL | Current public HTTPS origin of the API tunnel |
CHANNEL_AUTH_SUPABASE_PROVIDER | Stable provider identifier, for example custom:bookygram-channels |
CHANNEL_AUTH_ASSERTION_AUDIENCE | Provider client ID, for example bookygram-channels |
SUPABASE_SECRET_KEY | Supabase secret key or legacy service_role JWT for this instance; needed only by this administrative command |
Keep an existing provider identifier and audience if already configured. The normal API also needs its publishable key, bot bindings and assertion signing key as described in Environment variables. The setup command does not need or generate the assertion signing key.
Run from the repository root:
npm run auth:provider --workspace api
This command reads apps/api/.env when present; exported shell variables take
precedence. Keep the administrative key out of frontend variables. It is not
needed by the running API.
Expected output includes "outcome":"created", "updated", or "unchanged".
The command creates the provider if missing, or updates its issuer, discovery
URL, audience, scopes, enabled state, email-optional setting and nonce checking
in place. It verifies the saved settings before reporting success. It never
deletes a provider or rotates an existing provider secret, and does not print
credentials or remote response bodies.
Supabase Auth fetches discovery when creating or updating the provider. The
API must already advertise the same issuer URL, and Supabase must be able to
reach that HTTPS URL. Discovery and JWKS must return JSON, not a frontend page
or tunnel interstitial. A 400 during registration can mean that discovery
is unreachable or invalid; 401/403 indicates an administrative access
problem. A generic 404 is not treated as a missing provider.
After a database reset, run the command again. When the API tunnel changes,
update CHANNEL_AUTH_ISSUER_URL, restart the API, update both Mini Apps'
VITE_API_URL, and rerun the command. Update bot webhook URLs too. Keep the
provider identifier stable. Finally, fully close and reopen the Mini App for
fresh Telegram launch data; a previously consumed launch cannot be reused in
a new exchange context.
Provider setup uses the Supabase Auth administrative API rather than SQL in
auth tables: Auth owns discovery validation, client-secret encryption and
issuer-cache invalidation. Database migrations alone do not register this
environment-specific provider.
Prerequisites
- Hosted compatibility accepted for the target Supabase project; see Channel auth compatibility. Do not continue without it.
- A database with no client accounts from the API-issued Telegram sessions.
The
client_accounts.supabase_user_idmigration requires a Supabase subject on every row and fails while any such account remains. Professionalusersrows keyedtelegram:<id>do not block it, but nobody can sign in to them. - An assertion key, generated as in Rotate the channel assertion key step 1.
- Disposable Telegram test accounts for verification.
Phase 1: prepare (no user impact)
- Prepare the API release configuration with every variable in
Environment variables,
including each bot's new
webhookSecret.CHANNEL_AUTH_ISSUER_URLis the API's public origin. RemoveTELEGRAM_BOT_TOKEN,PROFESSIONAL_SESSION_SECRET,TELEGRAM_INIT_DATA_MAX_AGE_SECONDS,CLIENT_BOT_TOKEN,CLIENT_SESSION_SECRETandCLIENT_BOT_WEBHOOK_SECRET; the new release ignores them, and keeping them only widens exposure. - Prepare the provider registration: the provider identifier
(
CHANNEL_AUTH_SUPABASE_PROVIDER), the API's issuer, the assertion audience as client ID, email-optional registration and nonce checking enabled, as proven by the compatibility harness. Do not register it yet; Supabase Auth rejects a registration whose issuer does not serve discovery.
Phase 2: cutover window
Announce a short window. Open Mini Apps lose their session at step 1 and must be reopened.
- Apply the database migrations, including the
channel_authschema, as in Database migrations, then deploy the API release. The previous release cannot create client accounts once the migrations are applied, so do both in one step. API-issued Mini App sessions stop working immediately: the API verifies only Supabase access tokens. Nothing else is needed to invalidate them. - Register the provider in Supabase Auth with the values prepared in phase
1, using
npm run auth:provider --workspace apiagainst that environment. Expected:GET <api-origin>/.well-known/openid-configurationanswers200, and a lookup of the provider returns it. Sign-ins fail until this step is done. - Move both webhooks, as in Move Telegram webhooks to channel authentication. Do this right after step 2. Updates that failed against the old route are retried by Telegram and delivered to the new route after the switch (Telegram keeps retrying for up to a day).
- Deploy both Mini App releases.
- Drain webhook retries. Check both bots with
getWebhookInfountilpending_update_countstays near0, then check for pending facts as in Troubleshoot contact onboarding. Expected: no pending facts for signed-in people older than a few minutes.
Phase 3: verify
With disposable test accounts:
- A new user signs in through each bot without sharing a phone.
- The same Telegram user in both apps gets one Supabase user and keeps both roles; a client-only user is denied professional routes.
- A client shares contact and the account becomes
ready. - Leaving an app open past an hour keeps it working (refresh).
- Declining to share contact leaves the account
contact_requiredwith a usable session. - Opening either Mini App in a browser shows sample data and creates no session.
- Website sign-in works as before.
Rollback
- Before phase 2: nothing has changed for users.
- From phase 2 step 1: fix forward. The database migrations are forward only, and the previous API release cannot create client accounts on the migrated schema. The API release and both Mini Apps can each be redeployed on their own.