Skip to main content
OAO supports three authentication modes behind the same application boundary:
  • Development auth is the credential-free local default. The console signs in to the seeded development tenant through an HTTP-only session cookie.
  • Google IAP verifies the signed assertion injected at the application edge, then resolves the principal or onboards a human through the application policy.
  • WorkOS AuthKit is the hosted human-auth adapter. OAO still requires a matching PostgreSQL principal and project membership; a valid WorkOS identity alone does not grant tenant access.
Server-to-server integrations use organization-scoped OAO API keys. One key works against every project of its organization, including projects created after the key.

Google IAP

IAP and OAO enforce separate gates: IAP decides who may reach the service, then OAO verifies the signed sub and applies its initial-owner/member policy. Set AUTH_PROVIDER=iap with the exact Cloud Run resource audience. The default organization/project are initialized by the app with names defined in code and IDs stored in PostgreSQL; no tenant or onboarding-domain environment variables are required. On a fresh installation, the first verified human login becomes owner; subsequent IAP-authorized humans join as members with explicit developer scopes. IAP’s human allowlist is the set of possible initial owners—restrict it to trusted people. Service accounts and API keys cannot claim ownership. The existing login path handles this without a setup screen or separate API. Membership and audit writes are atomic; concurrent logins produce one owner. Existing users retain their roles. Restarts and removed owners never reopen the ownership claim. Removed memberships stay revoked while identity records remain. See GCP deployment for scope restrictions and rollout.

Organization roles for IAP users

Open Members to manage a signed-in IAP user’s Organization role. The separate Project role column shows their membership in the selected project. IAP identity matching uses the immutable Google subject, including older internal principal aliases. Switching projects requires an identity-linked membership; unlinked same-name principals cannot retain access after demotion. Roles shown in the console come from stored memberships, never from a wildcard scope or a shared display-name fallback.
  • Owner has full platform access (*). An existing organization Owner can appoint additional Owners and can change another Owner’s access.
  • Admin has the explicit current administration permissions, including provider/MCP configuration, event webhooks, audit access, API-key creation and organization project management. Owners and Admins can grant other users Admin access. Admins cannot grant Owner access or change/remove an Owner, and receive no wildcard.
  • Member retains the automatic developer permissions for agents, skills, sessions and runs. Viewer has only the explicit read permissions.
Selecting a role updates organization membership, effective scopes, and all existing IAP project memberships for that person atomically. Use Add member inside a project to grant an existing verified IAP organization user access to that project; their project role and scopes inherit their current organization profile. Refresh the user’s console to display the new role; subsequent authenticated requests resolve current permissions. Lowering access revokes API keys created by that person and their derived keys, including keys created through their other project identities. Rotate any affected integrations before relying on them again. Remove revokes their organization/project memberships and their keys while retaining identity records to prevent automatic rejoining. Also remove their Google IAP access as part of offboarding. You cannot change or remove your own access; the last Owner must remain. Only IAP-authenticated humans with the required organization role can manage these grants. API keys cannot grant human access. Users first appear after signing in through IAP; arbitrary subject/scope entry is disabled in this mode. Remove from project revokes only the selected project membership and keeps the organization role, other project memberships, and organization-scoped keys. The IAP default project’s membership cannot be removed because it anchors console authentication, and the project itself cannot be deleted. Use Remove organization access to offboard that user instead. Remove organization access retains the existing organization-wide offboarding behavior described above. Existing stored roles are preserved during rollout. If an existing person really has Owner membership, they will still display as Owner until another Owner explicitly changes it. This feature does not rewrite existing users or repair operator-created/shared identity mappings automatically. First-installation ownership still comes from the first verified human IAP login; project creation is unchanged and does not promote a member’s organization role. On older installations where one person has differently named internal records across projects, create/delete projects from that person’s original project; those legacy records are not migrated by this change. Their IAP role changes and revocations still apply across identity-linked project records. The console discovers the configured application provider from GET /v1/auth/provider. This keeps a promoted container image independent of the hosted authentication provider. For service-to-service calls through IAP, place the short-lived IAP credential in Proxy-Authorization and the OAO API key in Authorization. This preserves edge authentication and OAO authorization as independent checks. Never use a service-account key.

Create an integration key

A human principal needs both project:admin (or *) and an organization owner or admin membership to create a key from API keys in the console. Project membership and wildcard scopes alone do not grant that organization role. The console label All scopes describes permissions, not organization ownership. Enter a descriptive name, select the least-privilege scopes, and save the secret from the acknowledgement dialog. The console removes the plaintext when that dialog closes and cannot reveal it later. API keys are stored per organization. The key list and revocation act on the same organization-wide pool from any project. For automated setup, a caller authenticated with an API key has principal kind api_key and qualifies through project:admin or *, without an organization membership check. Callers with principal kind human or the internal service kind need both organization owner/admin membership and project:admin (or *). Every creator can grant only scopes they already hold; * permits any scope. Use the project API. In local development, first create a cookie session and read the seeded project ID:
For a client that creates and watches sessions, grant only:
Add tool_call:claim and tool_call:submit only when the integration executes caller-owned tools. Add run:cancel only when it should cancel runs. Skill administration requires explicit permissions: skill:read to list and export packages, skill:write to create drafts and publish versions, skill:bind together with agent:write to attach versions to agents, and skill:revoke to deprecate or revoke versions. Delegation inspection uses delegation:read. Messaging requires both delegation:message and run:create; cancellation requires both delegation:cancel and run:cancel. Both the API-key and member permission pickers include these scopes. If a key receives 403 Principal lacks the required scope, inspect GET /v1/context with that key and compare principal.scopes with the operation’s requirements. Keys created before these picker options were available keep their original permissions. Create a replacement with the needed scopes, update the integration, verify access, then revoke the old key. Selecting all listed scopes creates an explicit list; it does not grant * or future permissions. If creation returns Organization owner or admin role is required, ask an organization owner to check your organization membership. A project admin role or the All scopes label does not satisfy that membership check. MCP administration uses separate least-privilege scopes: mcp:read, mcp:write, mcp:discover, and mcp:bind. Credential metadata, creation, rotation, and revocation use credential:read_metadata, credential:write, credential:rotate, and credential:revoke. A run creator needs mcp:execute at call time; OAO rechecks this live before decrypting the bound credential. Create and capture the key once:
The first successful console or API response contains shown: true and secret. A replay of the same API request deliberately omits the secret and returns shown: false. OAO stores a keyed hash, not plaintext, so copy the secret into your secret manager immediately. Send the key as a bearer credential:

Cross-project key resolution

An API key resolves its organization and scopes from the key record, then selects the acting project per request: a /v1/projects/{projectId}/... path picks that project (it must belong to the key’s organization); any other path falls back to the organization’s earliest-created project. On first use inside a project, OAO materialises a per-project mirror principal of kind api_key for the key, so audit entries, idempotency records, and runs always reference a project-local principal. The same key therefore works in new projects without any additional setup. Project creation and deletion require an organization owner or admin membership for human principals. API keys qualify through their scopes: * or project:admin. For DELETE /v1/projects/{projectId} an API key deliberately acts from its default (earliest-created) project rather than the addressed one, so it can delete the project named in the path — except its own acting project, and no caller can delete the organization’s last project.
Keep OAO API keys on a server. Do not embed them in browser JavaScript, mobile bundles, repository files, query strings, logs, or SSE URLs.

Write safety

Every project-state mutation requires a caller-generated Idempotency-Key header. Retry the same logical request with the same key and identical body. Reusing a key with a different body produces a conflict instead of creating ambiguous duplicate work.
Keys are scoped to the authenticated principal and route. Keep them opaque, non-empty, and at most 200 characters.

Browser sessions

The React console uses secure HTTP-only cookies in WorkOS mode. IAP browser requests are authenticated by the signed edge assertion. Unsafe browser writes are origin-checked in both modes. API-key clients do not need the browser cookie or a custom tenant header: the key resolves its organization, project, principal, and scopes before tenant queries run. The context response keeps the PostgreSQL principal subject as the stable authorization identity and may add provider-supplied displayName for the UI. For example, a provisioned WorkOS user can display as Ben Selleslagh while still authorizing as the existing development-user principal. The display name does not grant scopes or change tenant membership. The console receives its configured authentication provider at startup. In WorkOS mode, a missing session goes directly through POST /v1/auth/login and the HTTPS redirectUrl returned by the API; it does not probe or establish the development identity. The callback sets the HTTP-only session cookies and redirects back to the configured app origin. While that navigation is in progress, concurrent console requests reuse the same login attempt so they cannot replace its one-time state cookie. If the provider request fails before navigation starts, the console releases that failed attempt so a later retry can request a new login without requiring a page reload. When the short-lived access session expires, the console uses POST /v1/auth/refresh once and retries the interrupted request. Concurrent requests share that refresh attempt. The refresh credential stays in its own HTTP-only, strict-same-site cookie, whose lifetime is configured with WORKOS_COOKIE_MAX_AGE and defaults to 400 days. A rejected refresh starts a fresh hosted login; a transient provider failure remains retryable without forcing a logout or requiring a page refresh. In WorkOS mode, the console shows Log out beside the signed-in user. It posts to POST /v1/auth/logout, which clears OAO’s HTTP-only session and refresh cookies. When WorkOS returns a hosted logout URL, the console follows that HTTPS redirect to end the provider session too. Development auth omits the button because local requests intentionally resolve to the deterministic development principal without an external session. If a WorkOS browser session is missing, cannot be refreshed, or belongs to a different configured application origin, the console starts a fresh hosted sign-in flow instead of leaving the current page in an error state. Recovery is limited to 401 unauthenticated responses and the specific 403 forbidden response Request origin is not allowed; ordinary authorization failures remain visible and are not converted into login loops. POST /v1/auth/login accepts this recovery even when stale cookies are present and still uses only the server-configured callback and application origin. The successful callback replaces the stale local session. The callback state is single-use and expires after ten minutes. If a WorkOS callback is reopened after that state was consumed, arrives without its state cookie, or contains a mismatched state value, OAO creates a new state cookie and redirects directly to a fresh hosted login instead of displaying a JSON Invalid authentication state error. Development-provider callbacks remain strict and return the explicit 400 response for invalid state.

Manage project members

The console’s Organization and Projects pages read the active tenant and project directly from the API. The Projects page can create a project (New project) and delete a non-active project; creation provisions the creator’s principal, owner membership, identity records, and tenant links in the new project within one transaction. The console switches its active project through POST /auth/switch-project: the endpoint verifies that the signed-in subject has a provisioned principal and membership in the target project, then sets an oao_active_project cookie. Every later request re-resolves that principal, so revoked membership or a deleted project falls back to the session’s own project, and logout clears the cookie. For WorkOS bearer identities provisioned into several projects, the request’s URL path selects the project; without one, the earliest-created project is used. The Members page lists project principals with their roles, scopes, and safe WorkOS display metadata. A project administrator can add an OAO principal, change another member’s role, or remove another member. The active principal’s role and removal actions are disabled in the console to prevent accidental self-lockout. Adding an OAO member is not a WorkOS invitation. For hosted sign-in, first add the project principal, then run the explicit WorkOS provisioning flow with that principal’s ID, the existing organization and project IDs, and the verified WorkOS user and organization IDs. See apps/api/README.md in the repository for the exact WorkOS callback, webhook, cookie, and local proxy environment variables.