Authentication
SkyState supports three authentication methods. The server selects the correct handler automatically based on the headers present in each request.
Method 1: GitHub OAuth JWT (Bearer token)
Most admin endpoints — account management, project CRUD, config writes, and billing — require a GitHub OAuth access token passed as a Bearer token.
Authorization: Bearer <github_oauth_token>How it works
- Your client redirects the user to
GET /api/auth/github(optionally with?flow=clifor the CLI flow). - The server redirects to GitHub's OAuth authorization page.
- GitHub redirects back to
GET /api/auth/github/callback?code=...&state=.... - The server exchanges the code for a GitHub access token.
- The token is returned to the client:
- Web flow: the server redirects to the dashboard with the token in the URL.
- CLI flow: the server returns an HTML page showing the token for the user to copy.
The token is validated on every authenticated request by calling GET https://api.github.com/user. Validated tokens are cached for 5 minutes to reduce GitHub API calls. On success, the user's account is provisioned (or updated) in the SkyState database via JIT upsert.
Initiate the flow
GET /api/auth/github
GET /api/auth/github?flow=cli| Parameter | Required | Description |
|---|---|---|
flow | No | cli for the CLI login flow; omit for the web dashboard flow |
Response: 302 Redirect to GitHub's authorization URL.
Callback
GitHub calls this endpoint automatically after user authorization.
GET /api/auth/github/callback| Query parameter | Required | Description |
|---|---|---|
code | Yes | Authorization code from GitHub |
state | Yes | CSRF state token issued by the server |
Responses:
302— web flow: redirects to the dashboard with the token200 text/html— CLI flow: renders a page displaying the token
Method 2: Dev API Key
Dev API keys are project-scoped keys for SDK auto-provisioning. They can only write to the development environment.
Authorization: DevKey <plaintext_key>The key is hashed with SHA-256 on arrival; only the hash is stored in the database. The server sets a dev_scope: development claim on authentication, which the dev config endpoint validates before allowing writes.
Dev API keys are managed via the admin API. See Dev API Keys for how to create, list, and revoke them.
Example
curl -X PATCH https://app.skystate.io/api/dev/my-account/my-app/config/development \
-H "Authorization: DevKey sk_dev_abc123..." \
-H "Content-Type: application/json" \
-H "If-Match: \"1.2.3\"" \
-d '[{"op":"replace","path":"/featureEnabled","value":true}]'Method 3: Test Mode (non-production only)
Test mode authentication is available only when the server runs in a non-production environment with EnableTestAuth: true in configuration. It is used by integration tests and is never active in production.
X-Test-GitHub-Id: <github_user_id>
X-Test-Email: <email> (optional)
X-Test-Name: <display_name> (optional)
X-Test-Slug: <account_slug> (optional)Sending X-Test-GitHub-Id triggers JIT account upsert using the provided values. No actual GitHub OAuth exchange takes place. When test mode is not enabled, this header is ignored and the request falls through to the GitHub Bearer token handler.
Authentication Selection Order
The server evaluates headers in this order for every request to a protected endpoint:
- If
Authorizationstarts withDevKey→ DevApiKeyHandler - If
X-Test-GitHub-Idis present and test mode is enabled → TestAuthHandler - Otherwise → GitHubTokenHandler (expects
Authorization: Bearer <token>)
Unauthenticated Endpoints
These endpoints do not require any authentication:
| Method | Path | Description |
|---|---|---|
GET | / | Liveness probe |
GET | /health | Database readiness check |
GET | /auth/github | Start GitHub OAuth flow |
GET | /auth/github/callback | GitHub OAuth callback |
GET | /public/{accountSlug}/{projectSlug}/config/{envSlug} | Read published config |
POST | /webhooks/stripe | Stripe webhook receiver |