Skip to content

Authentication

SkyState has four auth modes at the HTTP layer:

  • Developer bearer tokens - minted by the hosted developer login flow. They carry skystate_role=developer and skystate_account_id.
  • End-user bearer tokens - minted by the hosted end-user login flow. They carry skystate_role=end_user, skystate_account_id, skystate_project_slug, skystate_environment, and user_id; email and name are added when present at sign-in.
  • API keys - project-scoped credentials sent as Authorization: ApiKey <sky_...>.
  • Anonymous requests - no auth header, used only on explicitly anonymous routes.

Firebase ID tokens are accepted only by POST /v1/auth/authorize/callback (the firebase_id_token form field) as login proof. They are not accepted as product-route bearer tokens.

Auth Exchange Endpoints

The exchange surface is standard OAuth 2.0 authorization code + PKCE, fronted by the hosted login page at {authFrontUrl}/authorize. There is no flow or mode field on the wire: the API resolves the flow from client_id - the fixed developer client skystate-developer, or an account-scoped end-user client {account_id}.{slug}.

MethodRouteRequestResponse/behavior
GET/v1/auth/authorize/contextquery client_id, redirect_uri, and scope (required as env:{environment} for end-user clients, ignored for the developer client)Returns hosted-login context JSON: flow, project branding (project_name, logo_data, logo_media_type, primary_color, surface_color, bg_color), allowed_idps, and firebase_tenant_id. Read-only; nothing is stored.
POST/v1/auth/authorize/callbackform body with client_id, redirect_uri, state, code_challenge, code_challenge_method, firebase_id_token; end-user clients must also send scope=env:{environment}Completes hosted login and redirects to the validated redirect URI with code and state. Validation failures (unknown client, bad redirect URI) render an error page; Firebase, tenant, and provider failures redirect with error=access_denied.
POST/v1/auth/tokenapplication/x-www-form-urlencoded with grant_type and client_id; grant_type=authorization_code adds code, code_verifier, redirect_uri; grant_type=refresh_token adds refresh_token plus optional scope and previous_access_tokenReturns access_token, token_type, expires_in, refresh_token, and scope (RFC 6749 snake_case). The refresh grant rotates the single-use refresh token; a replayed refresh token is rejected. Errors are {error, error_description}.
POST/v1/auth/logoutJSON body with refreshTokenRevokes the refresh token family and returns 204.
GET/v1/auth/dev/cli-code?code=<code>&error=<error>query code, optional errorRenders the developer CLI code page.
GET/v1/auth/dev/cli-code/status?code=<code>query codeReturns pending while the exchange code exists, then consumed.

Developer Flow

  1. The client navigates the browser to {authFrontUrl}/authorize with client_id=skystate-developer and the standard authorization-code + PKCE parameters.
  2. Auth-front signs the developer in with Firebase and form-posts to POST /v1/auth/authorize/callback.
  3. The API redirects to the registered redirect URI with code and state.
  4. The client exchanges the code at POST /v1/auth/token with grant_type=authorization_code.
  5. Product routes use Authorization: Bearer <access_token>.

Developer tokens whose account no longer exists are rejected during bearer validation.

End-User Flow

End-user auth uses the same exchange endpoints with the account-scoped composite client_id={account_id}.{slug}; the environment is selected with scope=env:{environment} (development, staging, or production).

Each account owns its own Google Cloud Identity Platform (GCIP) tenant, provisioned when end-user auth is enabled. GET /v1/auth/authorize/context returns the account's tenant as firebase_tenant_id, and the hosted login page signs the user in against that tenant. At POST /v1/auth/authorize/callback the server re-derives the expected tenant from client_id and asserts the Firebase token's firebase.tenant claim matches it, fail-closed; firebase_tenant_id is never accepted from the caller.

On refresh (grant_type=refresh_token), email and name are copied into the new access token only when the optional previous_access_token form field carries the previous SkyState access token. When present it must be a SkyState-signed end-user access token (carrying the end-user role and user_id) for the same subject, or the refresh fails with 401 invalid_grant. A developer token is rejected even when its subject matches. The SDK sends this field automatically.

End-user state routes require token scope to match the route project and environment.

Public Auth Config

Public project auth settings can be read anonymously through:

text
GET /v1/readonly/{accountId}/projects/{slug}/auth/end-user/config

The response includes project branding and allowed identity providers. Projects without end-user auth enabled return 404.