Skip to main content

Auth0 setup — comprehensive operator runbook

This is the canonical setup guide for AGENTS hard rules 52-55 (the Phase 5+ auth refactor). Pair with alphaswarm_docs/auth0-actions.md for the JS Action bodies that go in the Auth0 Dashboard.

The platform supports three deployment shapes:

  • Local-first dev: ALPHASWARM_AUTH_PROVIDER=local, no Auth0 tenant needed. Everything below is skipped.
  • Single-tenant B2C: one Auth0 tenant per env, individual users sign up via Universal Login + social connections. Organizations is OFF (or "Allow individual logins" if you want both modes).
  • Multi-tenant B2B: same Auth0 tenant per env, institutional customers attach via Auth0 Organizations. Each Organization has its own branded login + Enterprise connection.

The same backend serves all three; the difference is purely the Auth0 configuration + the ALPHASWARM_AUTH_* env vars.


1. Tenants

One Auth0 tenant per AlphaSwarm environment. Three tenants per AGENTS rule:

EnvAuth0 tenantCustom domainIssuer URL in ALPHASWARM_AUTH_OIDC_ISSUER
devalphaswarm-devauth.dev.alpha-swarm.aihttps://auth.dev.alpha-swarm.ai/
stagealphaswarm-stageauth.stage.alpha-swarm.aihttps://auth.stage.alpha-swarm.ai/
prodalphaswarm-prodauth.alpha-swarm.aihttps://auth.alpha-swarm.ai/

Custom domains stabilise the issuer URL so changing Auth0 tenants later is non-breaking. Without a custom domain the issuer is https://alphaswarm-prod.us.auth0.com/ and every existing JWT cache / revocation token has to be invalidated on rebrand.

Never share Auth0 tenants across envs — Auth0 charges per MAU per tenant, but the security boundary is more important than the cost arithmetic.


2. API resource server

One API record per tenant — the AlphaSwarm backend.

FieldValue (prod example)
Namealphaswarm-api
Identifierhttps://api.alpha-swarm.ai/
Signing algorithmRS256
Allow Skipping User ConsentON
Allow Offline AccessON
Token expiration (seconds)86400 (24h ceiling — per-app overrides win)
Token expiration for browser flows (seconds)7200 (2h SPA ceiling)

Enable RBAC:

  • Settings → "Enable RBAC" → ON
  • Settings → "Add Permissions in the Access Token" → ON

Define every permission AlphaSwarm uses (Permissions tab):

read:portfolio             Read portfolio positions / PnL / risk
write:portfolio Mutate portfolio config
read:strategy Read strategy specs / backtest history
write:strategy Author / edit strategies
deploy:strategy Promote a strategy to live trading
kill_switch:execute Engage the global kill switch
trade:execute Submit live or paper orders
trade:live Bypass the paper-only guard
read:mcp:data Invoke the Data MCP tools
write:mcp:data Mutate via Data MCP (e.g. namespace policy edits)
read:mcp:codebase Invoke the Codebase MCP tools
write:mcp:codebase Apply code edits via Codebase MCP (rarely granted)
run:agent Spawn an AgentRuntime
admin:tenant Org-admin powers (invites, IdP config, billing)
admin:cluster Bypass resource filter; superadmin-only
manage:broker_credentials Read/write broker credentials at org scope
read:logs Required for the Auth0 Management API M2M client

Add Token Exchange:

  • API → Settings → "Token Exchange" → ON (required for alphaswarm-agent-broker to use RFC 8693).

3. Applications

Five application records per tenant:

RecordTypeGrantsToken TTLNotes
alphaswarm-spaSingle Page Applicationauthorization_code + refresh_tokenaccess 15m, ID 10mRefresh-token rotation ON, absolute lifetime 24h
alphaswarm-cliNativeurn:ietf:params:oauth:grant-type:device_code + refresh_tokenaccess 60mRotation ON, absolute 30d, inactivity 7d. "Business Users" mode so Device Code stays compatible with Orgs
alphaswarm-backend-m2mM2Mclient_credentials24hFor internal service-to-service + Auth0 Management API
alphaswarm-action-callback-m2mM2Mclient_credentials5mUsed inside Auth0 Actions for /_internal/auth0/sync
alphaswarm-agent-brokerM2Mclient_credentials + urn:ietf:params:oauth:grant-type:token-exchange5mRFC 8693 delegated-agent-token minting

3.1 alphaswarm-spa (SPA)

  • Application URIs:
    • Allowed callback URLs: https://app.alpha-swarm.ai/auth/callback, http://localhost:3001/auth/callback
    • Allowed logout URLs: https://app.alpha-swarm.ai/, http://localhost:3001/
    • Allowed web origins: https://app.alpha-swarm.ai, http://localhost:3001
  • Refresh Token Rotation: ON
  • Refresh Token Expiration: Absolute 24h
  • Refresh Token Inactivity: 7d
  • Idle Session Lifetime: 72h
  • Maximum Session Lifetime: 168h (7d)

Frontend env vars (Vite):

VITE_AUTH_PROVIDER=auth0
VITE_AUTH0_DOMAIN=auth.alpha-swarm.ai # custom domain
VITE_AUTH0_SPA_CLIENT_ID=<alphaswarm-spa client_id>
VITE_AUTH0_AUDIENCE=https://api.alpha-swarm.ai/
VITE_AUTH0_SCOPE=openid profile email offline_access read:portfolio write:portfolio read:strategy write:strategy read:mcp:data
VITE_AUTH0_ORGANIZATION= # B2B only — pin to a single org

3.2 alphaswarm-cli (Native)

  • Connections tab: enable the same DB / social connections as the SPA.
  • Advanced Settings → Grant Types: enable Device Code + Refresh Token.
  • "Business Users" mode (not "Organizations Required"); the Auth0 team's M2M-for-Orgs GA notes that Device Code is incompatible with the strict "Organizations Required" setting.

CLI env vars (operator's machine):

ALPHASWARM_CLI_OIDC_DOMAIN=auth.alpha-swarm.ai
ALPHASWARM_CLI_OIDC_CLIENT_ID=<alphaswarm-cli client_id>
ALPHASWARM_CLI_OIDC_AUDIENCE=https://api.alpha-swarm.ai/
ALPHASWARM_CLI_OIDC_ORGANIZATION= # B2B: pin to a single org

The CLI fetches all three from /auth/config when not set, so most operators don't need to copy-paste.

3.3 alphaswarm-backend-m2m (M2M)

  • Authorise against:
    • alphaswarm-api (all permissions the backend needs to act on its own behalf).
    • Auth0 Management API (read:users, update:users, delete:sessions, read:sessions, read:logs, read:connections, create:guardian_enrollment_tickets, delete:guardian_enrollments, create:user_tickets).

Backend env vars:

ALPHASWARM_AUTH_PROVIDER=auth0
ALPHASWARM_AUTH_OIDC_ISSUER=https://auth.alpha-swarm.ai/
ALPHASWARM_AUTH_OIDC_AUDIENCE=https://api.alpha-swarm.ai/
ALPHASWARM_AUTH_OIDC_CLIENT_ID=<alphaswarm-spa client_id> # SPA client_id (for the SPA-targeted JWKS validation path)
ALPHASWARM_AUTH_OIDC_CLIENT_SECRET= # empty — SPAs are public clients
ALPHASWARM_AUTH0_MGMT_API_AUDIENCE=https://alphaswarm-prod.us.auth0.com/api/v2/
ALPHASWARM_AUTH0_MGMT_API_CLIENT_ID=<alphaswarm-backend-m2m client_id>
ALPHASWARM_AUTH0_MGMT_API_CLIENT_SECRET= # via CredentialResolver in prod; env in dev
ALPHASWARM_AUTH0_DPOP_ENABLED=true # SDK mixed-mode
ALPHASWARM_AUTH0_DPOP_REQUIRED=false # flip true after CLI + SPA migrate
ALPHASWARM_AUTH_M2M_ENABLED=true
ALPHASWARM_AUTH_M2M_AUDIENCE=https://api.alpha-swarm.ai/
ALPHASWARM_AUTH_STEP_UP_ENABLED=true
ALPHASWARM_AUTH_STEP_UP_DEFAULT_MAX_AGE=180

3.4 alphaswarm-action-callback-m2m (M2M)

Same scopes as alphaswarm-backend-m2m but used INSIDE Auth0 Actions to call /_internal/auth0/sync + /_internal/idp/sync-groups. The Action body in auth0-actions.md shows how to mint + cache the token.

3.5 alphaswarm-agent-broker (M2M for Token Exchange)

  • Grants: client_credentials + urn:ietf:params:oauth:grant-type:token-exchange.
  • Authorised APIs: alphaswarm-api with scopes read:mcp:data, write:mcp:data, read:mcp:codebase, write:mcp:codebase.
  • Used ONLY by the Custom Token Exchange Profile body to mint delegated agent tokens.

Backend env vars:

ALPHASWARM_AUTH_AGENT_TOKEN_EXCHANGE_ENABLED=true
ALPHASWARM_AUTH_AGENT_BROKER_CLIENT_ID=<alphaswarm-agent-broker client_id>
ALPHASWARM_AUTH_AGENT_BROKER_CLIENT_SECRET= # via CredentialResolver in prod
ALPHASWARM_AUTH_AGENT_DELEGATION_TTL_SECONDS=300

4. Connections

Database connection (B2C)

  • Default Username-Password-Authentication database connection.
  • Password Strength: "Excellent" (NIST 800-63 compliant).
  • Enable: "Disable Signups from Public Signup Page" if you want invite-only onboarding (B2B-heavy deployments).

Social connections (B2C)

  • GitHub, Google (google-oauth2). Both default to the standard Auth0 connection types — no extra config beyond the Client ID + Secret from the respective developer console.

Enterprise connections (B2B)

Configured per-org in :class:IdpConnectionRecord. Auth0 supports SAML, ADFS, Azure AD (Entra), Google Workspace, PingFederate, SiteMinder, Okta Workforce Identity, OneLogin, JumpCloud, generic OIDC. The AlphaSwarm-side admin UI is IdpGroupMappingEditor.

Each enterprise connection MUST:

  • Sync the user's group claims (Azure groups, Google's group claim, Okta groups). The Action alphaswarm-idp-group-sync reads them.
  • Map to a single AlphaSwarm Organization via the matching :class:IdpConnectionRecord.organization_id. Multiple orgs may use the same connection KIND (e.g. AcmeCorp Okta + Subsidiary Okta) but each is a separate record.

5. Organizations (B2B)

One Auth0 Organization per institutional tenant. Auth0 charges per Org per month on most tiers — budget accordingly.

SettingValue
Membership on Login"Require Members to use this Organization" (strict B2B)
Allowed ConnectionsOnly the org's enterprise connection(s)
BrandingPer-org logo + colors so users land on a branded login

Use ?organization=org_xxx&login_hint=user@acme.com on /authorize to skip the org-picker step. The SPA reads VITE_AUTH0_ORGANIZATION to pin.

The post-login Action (alphaswarm-post-login) reads event.organization?.id and injects it as https://alphaswarm.internal/org_id so the FastAPI require_org dep can branch immediately.


6. Actions

Three Login-trigger Actions (in this order):

  1. alphaswarm-post-login — JIT user upsert + custom claim injection. Body in auth0-actions.md ("Phase 7 post-login Action" section, extended by "Phase 8" addendum for step-up MFA).

  2. alphaswarm-idp-group-sync — reads external IdP group claims and posts to /_internal/idp/sync-groups so the AlphaSwarm backend upserts matching Membership rows per the per-org IdpGroupMapping table. Body in auth0-actions.md ("Phase 6 — IdP group sync Action" section).

And one Custom Token Exchange Profile:

  1. alphaswarm-agent-delegation — RFC 8693 minting for delegated agent tokens. Body in auth0-actions.md ("Phase 8 — Custom Token Exchange Profile" section).

7. Pre-User-Registration trigger

One Action to block disposable emails + verify B2B invites:

exports.onExecutePreUserRegistration = async (event, api) => {
const email = (event.user.email || "").toLowerCase();
const disposable = ["mailinator.com", "guerrillamail.com", "tempmail.org",
"10minutemail.com", "throwaway.email"];
const domain = email.split("@")[1];
if (!email) { api.access.deny("invalid_email", "email required"); return; }
if (disposable.includes(domain)) {
api.access.deny("disposable_email", "disposable email domains not allowed");
return;
}
// B2B invite verification — operator chooses how strict.
if (event.client.metadata?.flow === "b2b" && event.secrets.ALPHASWARM_BACKEND_URL) {
// Call /_internal/auth/preregister-check (operator adds this route
// if they want HMAC-based invite enforcement at registration time).
}
};

8. Log Streams

One Custom Webhook log stream per env:

FieldValue
TypeCustom Webhook
Payload URLhttps://api.alpha-swarm.ai/_internal/auth0/log-stream
AuthorizationBearer <secret> (matches ALPHASWARM_AUTH0_LOG_STREAM_SECRET)
Content Typeapplication/json
Custom Headers(none beyond Authorization)
FilterAll events (the backend filters server-side)

Operator generates the shared secret:

openssl rand -hex 32

…then sets it both in the Auth0 Dashboard webhook config AND in the backend's ALPHASWARM_AUTH0_LOG_STREAM_SECRET env var. The HMAC compare on _verify_authorization rejects any other value.

Optionally also wire native Datadog / Splunk / Elastic streams for the SIEM team — those are independent of the AlphaSwarm webhook.


9. Adaptive MFA

Security → Multi-factor Authentication → Adaptive MFA → ON.

Risk levelActionWhy
lowAllow (no MFA)Normal session resumption
mediumMFA challengeSuspicious-but-not-definitive signals
highMFA challengeLikely compromised

Enabled MFA factors (Security → Multi-factor Authentication → Factors):

  • OTP (TOTP) — always-on; required for every B2B user
  • WebAuthn — recommended primary for B2B users
  • Push (Auth0 Guardian app) — B2C convenience
  • SMS — discouraged for B2B; allow as B2C fallback only
  • Email OTP — convenient B2C fallback
  • Recovery codes — always issue alongside any factor

The alphaswarm-post-login Action's Phase 8 addendum calls api.multifactor.enable("any", { allowRememberBrowser: false }) when the SPA / CLI requests acr_values=http://schemas.openid.net/pape/policies/2007/06/multi-factor on /authorize. This is the integration point for the backend's require_step_up dep.


10. Env-var checklist (prod)

# IdP
ALPHASWARM_AUTH_PROVIDER=auth0
ALPHASWARM_AUTH_REQUIRED=true
ALPHASWARM_AUTH_ENFORCE=strict
ALPHASWARM_AUTH_OIDC_ISSUER=https://auth.alpha-swarm.ai/
ALPHASWARM_AUTH_OIDC_AUDIENCE=https://api.alpha-swarm.ai/
ALPHASWARM_AUTH_OIDC_CLIENT_ID=<alphaswarm-spa client_id>
ALPHASWARM_AUTH_CLAIMS_NAMESPACE=https://alphaswarm.internal/
ALPHASWARM_AUTH_CLAIMS_NAMESPACE_ALIASES=https://alphaswarm/ # CSV; legacy reader

# Management API
ALPHASWARM_AUTH0_MGMT_API_AUDIENCE=https://alphaswarm-prod.us.auth0.com/api/v2/
ALPHASWARM_AUTH0_MGMT_API_CLIENT_ID=<alphaswarm-backend-m2m client_id>
ALPHASWARM_AUTH0_MGMT_API_CLIENT_SECRET= # via CredentialResolver

# M2M
ALPHASWARM_AUTH_M2M_ENABLED=true
ALPHASWARM_AUTH_M2M_AUDIENCE=https://api.alpha-swarm.ai/
ALPHASWARM_AUTH_M2M_TOKEN_TTL_SECONDS=900

# DPoP
ALPHASWARM_AUTH0_DPOP_ENABLED=true
ALPHASWARM_AUTH0_DPOP_REQUIRED=false # flip true once SDK rolled out
ALPHASWARM_DPOP_ENFORCEMENT_ENABLED=false # per-route enforcement

# Step-up MFA (rule 52)
ALPHASWARM_AUTH_STEP_UP_ENABLED=true
ALPHASWARM_AUTH_STEP_UP_DEFAULT_MAX_AGE=180

# Auth0 Log Stream (rule 53)
ALPHASWARM_AUTH0_LOG_STREAM_SECRET=<openssl rand -hex 32>
ALPHASWARM_AUTH0_LOG_STREAM_MAX_AGE_SECONDS=86400

# Delegated agent tokens (rule 54)
ALPHASWARM_AUTH_AGENT_TOKEN_EXCHANGE_ENABLED=true
ALPHASWARM_AUTH_AGENT_BROKER_CLIENT_ID=<alphaswarm-agent-broker client_id>
ALPHASWARM_AUTH_AGENT_BROKER_CLIENT_SECRET= # via CredentialResolver
ALPHASWARM_AUTH_AGENT_DELEGATION_TTL_SECONDS=300

# B2B Entra (existing)
ALPHASWARM_AUTH_MSAL_B2B_ENABLED=true

# Tenancy
ALPHASWARM_TENANCY_DEFAULT_STRATEGY=hybrid
ALPHASWARM_TENANCY_RLS_ENFORCE=strict # was off; flip after Phase 5 verified

# MCP RFC conformance
ALPHASWARM_MCP_DATA_CANONICAL_URI=https://api.alpha-swarm.ai/mcp/data
ALPHASWARM_MCP_CODEBASE_CANONICAL_URI=https://api.alpha-swarm.ai/mcp/codebase
ALPHASWARM_MCP_REQUIRE_RFC8707=strict # was off

# Per-user OAuth wizard
ALPHASWARM_USER_OAUTH_ENABLED=true

# Audit
ALPHASWARM_AUTH_AUDIT_ENABLED=true
ALPHASWARM_AUTH_AUDIT_RETENTION_DAYS=365

11. CLI env vars (per operator)

ALPHASWARM_CLI_OIDC_DOMAIN=auth.alpha-swarm.ai
ALPHASWARM_CLI_OIDC_CLIENT_ID=<alphaswarm-cli client_id>
ALPHASWARM_CLI_OIDC_AUDIENCE=https://api.alpha-swarm.ai/
ALPHASWARM_CLI_OIDC_ORGANIZATION= # B2B: pin to a single org
# Headless / CI fallback (no keyring backend):
ALPHASWARM_CLI_AUTH_ALLOW_PLAINTEXT_FALLBACK=0

12. Rollout order

StepActionVerification
1Create dev tenant + apps + custom domain/auth/config returns the tenant id
2Backend up with ALPHASWARM_AUTH_ENFORCE=permissiveExisting routes still serve; 401 dashboard shows zero would-be denies
3Flip ALPHASWARM_AUTH_ENFORCE=strictUnauthenticated calls return 401
4Wire Auth0 log-stream webhook + Action triggersForce a session-revoke in Dashboard; verify cleanup_for_user Celery row + audit row
5Enable ALPHASWARM_AUTH_STEP_UP_ENABLED=trueClick kill-switch → MFA prompt; complete it; subsystems halt
6Enable ALPHASWARM_AUTH_AGENT_TOKEN_EXCHANGE_ENABLED=true + create ProfileTrigger an agent that calls a DataMCP tool; verify act claim in /mcp/data response body + delegation JSON in audit
7Enable ALPHASWARM_USER_OAUTH_ENABLED=true/me/oauth-connections/providers returns the 5 providers
8Enable BYOK broker credentials (run Alembic 0065)Add an Alpaca paper key; smoke-test a paper trade
9Enable RLS strict mode (ALPHASWARM_TENANCY_RLS_ENFORCE=strict)Existing test workspace queries still work; cross-workspace fetches return zero rows
10Enable MCP RFC 8707 strict modeMCP calls with mis-audienced tokens return 401 + WWW-Authenticate header

Each flip is independently reversible.


13. Reference docs