Authentication
SkyState has four auth modes at the HTTP layer:
- Developer bearer tokens - minted by the hosted developer login flow. They carry
skystate_role=developerandskystate_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, anduser_id;emailandnameare 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}.
| Method | Route | Request | Response/behavior |
|---|---|---|---|
GET | /v1/auth/authorize/context | query 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/callback | form 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/token | application/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_token | Returns 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/logout | JSON body with refreshToken | Revokes the refresh token family and returns 204. |
GET | /v1/auth/dev/cli-code?code=<code>&error=<error> | query code, optional error | Renders the developer CLI code page. |
GET | /v1/auth/dev/cli-code/status?code=<code> | query code | Returns pending while the exchange code exists, then consumed. |
Developer Flow
- The client navigates the browser to
{authFrontUrl}/authorizewithclient_id=skystate-developerand the standard authorization-code + PKCE parameters. - Auth-front signs the developer in with Firebase and form-posts to
POST /v1/auth/authorize/callback. - The API redirects to the registered redirect URI with
codeandstate. - The client exchanges the code at
POST /v1/auth/tokenwithgrant_type=authorization_code. - 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/configThe response includes project branding and allowed identity providers. Projects without end-user auth enabled return 404.