Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Authentication

Amodal auth has two independent layers. Keep them straight and everything else follows.

LayerQuestion it answersFieldAmodal label
Access mode (deploy protection)Who can open the hosted app URL?hosted_auth_modeConfiguration → Auth → Deploy Protection
End-user identityWho does the runtime think the chat caller is?end_user_auth.typeConfiguration → Auth → End-User Identity

Access modehosted_auth_mode:

  • public — anyone with the URL can open the app shell. (Amodal: Public.)
  • user_auth — gated; only signed-in members of the agent's organization can open it. The edge worker redirects unauthenticated browser requests to the hosted login. (Amodal: Team only.) This is the default for a new agent.

End-user identityend_user_auth.type: none (anonymous), jwks, jwt_secret, oidc, header, or custom. This selects which identity the runtime assigns to a caller. It does not decide whether auth is required.

The runtime always requires an authenticated caller on chat/runtime requests. Identity mode picks who the caller is; it never makes auth optional. type: "none" assigns a synthesized anonymous identity — it does not mean "no token."

Browser ──▶ Edge worker ───────────────▶ Runtime
            • gated? check amodal_session   • verifies the injected/bearer
              cookie, else redirect to        identity per end_user_auth.type
              /auth/login                    • runs the agent
            • injects Authorization
              server-side for valid
              cookies / lets valid
              agent-bound bearer tokens
              through

Browser requests: the hosted cookie flow

For an app served by Amodal at the agent's own origin — the default chat shell or a custom SPA — hosted auth is cookie-backed and handled at the edge:

  1. The browser hits a gated route with no session. The edge worker redirects (a full-page navigation, not an in-page fetch) to the agent's /auth/login.
  2. The user logs in through the hosted flow. The platform sets an HttpOnly amodal_session cookie on the agent origin.
  3. On every subsequent same-origin call, the edge worker validates the cookie and injects the Authorization bearer server-side before proxying to the runtime.

The browser app never sees, reads, or stores the token. Concretely:

  • Use an absolute same-origin runtimeUrl, normally window.location.origin in the browser. Do not pass "", undefined, or a cleared env var into the SDK; that can produce requests such as /undefined/chat/stream.
  • Omit getToken for hosted cookie auth. Do not call /auth/session to fetch a token into JS.
  • The hosted login is a top-level redirect. A cross-origin OAuth flow (e.g. the hosted IdP) cannot run as an in-page fetch or sub-resource — the browser will block it.

Pass getToken only when an external system owns identity (see the table below).

API clients

Non-browser callers (CLI, CI, your server, another product) send Authorization: Bearer themselves:

CredentialUse forCreated from
Short-lived runtime tokenSmoke tests, CI, server-to-server calls to the agent runtime. Agent-bound and expiring.POST /api/agents/{agent_id}/tokens with an operator/admin pk_ token.
ak_ agent runtime API keyA persistent credential for an external product/service calling the runtime.Agent API key settings.

A valid runtime token reaches the runtime even when the agent is user_auth (gated) — the edge lets a platform-signed, agent-bound bearer token through while still redirecting browser users to the hosted login. So you do not need to make an agent public to smoke-test it.

pk_ platform automation tokens are for the Platform API control plane (creating, configuring, and deploying agents). They are not used to call the agent runtime — mint a runtime token for that. See Build With A Coding Agent for the end-to-end automation flow.

Choose a setup

You're buildingAccess modeIdentityHow the client authenticates
Team-internal app/SPA served by Amodaluser_auth ("Team only")none, or your IdPHosted cookie. Same-origin calls, omit getToken. One login covers the app shell and chat.
External product embedding the agentpublic or user_authjwt_secret / oidc / jwksYour backend mints the JWT (with scope_id/scopeContext claims); pass it via getToken. See Embedding.
Public demo (no per-user identity)publicnoneNo token from the browser; mint tokens only server-side.
CLI / CI / server-to-serveranyanyMinted runtime token or ak_ key as Authorization: Bearer.

End-user identity modes

ModeUse when
none (anonymous)Demo or any app where per-user identity is not required. Synthesizes anonymous.
jwks (hosted preset)The platform host owns login and token issuance (the hosted WorkOS path).
jwt_secretYour backend mints HMAC JWTs for users.
oidcFull OAuth/OIDC login against Okta, Azure AD, Auth0, Keycloak, etc.
headerAn upstream proxy authenticates users and injects an identity header.
customA custom runtime AuthStrategy module.

Configure these in Amodal (Configuration → Auth) or programmatically with PUT /api/agents/{agent_id} (hosted_auth_mode, end_user_auth). Apply auth changes before deploying — or redeploy afterward — so the runtime machine picks them up.

Common mistakes

  • Don't fetch or store a session token in JS for hosted auth. It is cookie-backed and injected at the edge; reading it from JS is neither possible nor needed. Omit getToken.
  • Don't make an agent public just to call it from a script. A minted runtime token reaches a gated agent.
  • "Anonymous" still needs a token on the API. It removes per-user identity, not the runtime's bearer requirement.
  • The hosted login is a full-page redirect, not an in-page request.
  • Don't pass an empty runtime URL to the React SDK. Use window.location.origin for an Amodal-hosted SPA, and pass that same value to AmodalProvider runtimeUrl and AmodalChat serverUrl.