-
Notifications
You must be signed in to change notification settings - Fork 0
docs(api): document the OAuth 2.1 provider #135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| --- | ||
| title: "OAuth" | ||
| description: "Build apps that act on behalf of a Photon user with OAuth 2.1 and OpenID Connect." | ||
| --- | ||
|
|
||
| The Photon dashboard is an OAuth 2.1 and OpenID Connect provider. Register an OAuth app, send users through the authorization code flow, and call the [Dashboard API](/api-reference/introduction) with the resulting bearer token — limited to exactly the scopes the user consented to. | ||
|
|
||
| <Note> | ||
| OAuth tokens authenticate **users** against the Dashboard API on `app.photon.codes`. The [Spectrum API](/api-reference/introduction) on `spectrum.photon.codes` uses per-project HTTP Basic credentials instead; the two credential systems are separate and not interchangeable. | ||
| </Note> | ||
|
|
||
| ## Endpoints | ||
|
|
||
| | Endpoint | URL | | ||
| | --- | --- | | ||
| | Issuer | `https://app.photon.codes/api/auth` | | ||
| | Authorization | `GET https://app.photon.codes/api/auth/oauth2/authorize` | | ||
| | Token | `POST https://app.photon.codes/api/auth/oauth2/token` | | ||
| | UserInfo | `GET https://app.photon.codes/api/auth/oauth2/userinfo` | | ||
| | Revocation | `POST https://app.photon.codes/api/auth/oauth2/revoke` | | ||
| | Introspection | `POST https://app.photon.codes/api/auth/oauth2/introspect` | | ||
| | JWKS | `GET https://app.photon.codes/api/auth/jwks` | | ||
|
|
||
| ### Discovery | ||
|
|
||
| Because the issuer contains a path component (`/api/auth`), the metadata documents live at the [RFC 8414 §3.1](https://datatracker.ietf.org/doc/html/rfc8414#section-3.1) path-insertion URLs — the issuer's path goes *after* the well-known segment: | ||
|
|
||
| ``` | ||
| https://app.photon.codes/.well-known/oauth-authorization-server/api/auth | ||
| https://app.photon.codes/.well-known/openid-configuration/api/auth | ||
| ``` | ||
|
|
||
| Libraries that derive the metadata URL from the issuer per RFC 8414 resolve these automatically. Some OIDC clients instead append `/.well-known/openid-configuration` to the issuer or probe the domain root — both of those 404 here, so configure the discovery URL (or the individual endpoints) explicitly in that case. | ||
|
|
||
| ## Create an OAuth app | ||
|
|
||
| In the dashboard, open [Developer → Apps](https://app.photon.codes/dashboard/developer/apps) and create an app. You'll choose: | ||
|
|
||
| - **Name, logo, homepage** — shown to users on the consent screen. | ||
| - **Redirect URIs** — an exact-match allowlist. The `redirect_uri` in your authorization request must match one of these character for character. | ||
| - **Client type** — *confidential* for server-side apps that can keep a secret, or *public* for native and browser-based apps that can't. Public clients get no secret and rely on PKCE alone. | ||
| - **Scopes** — the maximum set your app may request. Authorization requests for scopes outside this set are rejected with `invalid_scope`. | ||
|
|
||
| <Warning> | ||
| The `client_secret` is shown **once**, when the app is created. It's stored hashed and can't be retrieved later — keep it in a secrets manager. If you lose it, rotate it from the app's page in the dashboard. | ||
|
lcandy2 marked this conversation as resolved.
|
||
| </Warning> | ||
|
|
||
| ## Authorization code flow | ||
|
|
||
| PKCE with the `S256` challenge method is required for every client, confidential ones included. `plain` is not accepted. | ||
|
|
||
| <Steps> | ||
| <Step title="Generate a PKCE verifier and challenge"> | ||
| ```sh | ||
| code_verifier=$(openssl rand -base64 32 | tr -d '=' | tr '/+' '_-') | ||
| code_challenge=$(printf '%s' "$code_verifier" | openssl dgst -sha256 -binary | openssl base64 | tr -d '=\n' | tr '/+' '_-') | ||
| ``` | ||
| </Step> | ||
| <Step title="Send the user to the authorization endpoint"> | ||
| ``` | ||
| https://app.photon.codes/api/auth/oauth2/authorize | ||
| ?response_type=code | ||
| &client_id=YOUR_CLIENT_ID | ||
| &redirect_uri=https://yourapp.example/oauth/callback | ||
| &scope=openid+profile+email+offline_access+projects:read | ||
| &state=RANDOM_STATE | ||
| &code_challenge=CODE_CHALLENGE | ||
| &code_challenge_method=S256 | ||
| ``` | ||
|
|
||
| `scope` is a space-separated list (URL-encoded). The user signs in to Photon, reviews the requested scopes on the consent screen, and on approval is redirected to your `redirect_uri` with `code` and your `state` in the query string. Verify `state` matches what you sent before using the code. | ||
| </Step> | ||
| <Step title="Exchange the code for tokens"> | ||
| ```sh | ||
| curl -X POST "https://app.photon.codes/api/auth/oauth2/token" \ | ||
| -H "Content-Type: application/x-www-form-urlencoded" \ | ||
| --data-urlencode "grant_type=authorization_code" \ | ||
| --data-urlencode "code=$code" \ | ||
| --data-urlencode "redirect_uri=https://yourapp.example/oauth/callback" \ | ||
| --data-urlencode "client_id=$CLIENT_ID" \ | ||
| --data-urlencode "client_secret=$CLIENT_SECRET" \ | ||
| --data-urlencode "code_verifier=$code_verifier" | ||
| ``` | ||
|
|
||
| Confidential clients authenticate with `client_secret` in the body as shown, or with HTTP Basic (`client_id:client_secret`) — both are accepted. Public clients omit the secret. | ||
|
|
||
| ```json | ||
| { | ||
| "access_token": "kJ2f…", | ||
| "token_type": "Bearer", | ||
| "expires_in": 3600, | ||
| "refresh_token": "hV9c…", | ||
| "id_token": "eyJhbGciOiJFZERTQSJ9.…", | ||
| "scope": "openid profile email offline_access projects:read" | ||
| } | ||
| ``` | ||
|
|
||
| The access token is opaque — don't try to parse it. `id_token` is only present when `openid` was granted, and `refresh_token` only when `offline_access` was granted. | ||
| </Step> | ||
| <Step title="Call the Dashboard API"> | ||
| ```sh | ||
| curl "https://app.photon.codes/api/projects/" \ | ||
| -H "Authorization: Bearer $ACCESS_TOKEN" | ||
| ``` | ||
|
|
||
| A missing, expired, or revoked token returns `401`. A valid token missing the scope an endpoint requires returns `403` with `insufficient_scope: <scope>` in the message. | ||
| </Step> | ||
| </Steps> | ||
|
|
||
| ## Refresh tokens | ||
|
|
||
| Request the `offline_access` scope to receive a refresh token, then exchange it when the access token expires: | ||
|
|
||
| ```sh | ||
| curl -X POST "https://app.photon.codes/api/auth/oauth2/token" \ | ||
| -H "Content-Type: application/x-www-form-urlencoded" \ | ||
| --data-urlencode "grant_type=refresh_token" \ | ||
| --data-urlencode "refresh_token=$REFRESH_TOKEN" \ | ||
| --data-urlencode "client_id=$CLIENT_ID" \ | ||
| --data-urlencode "client_secret=$CLIENT_SECRET" | ||
| ``` | ||
|
|
||
| Refresh tokens rotate: each exchange returns a new refresh token and invalidates the old one, so always store the one from the latest response. Refresh tokens live for 30 days; an app that refreshes at least that often stays connected indefinitely. | ||
|
|
||
| ## Scopes | ||
|
|
||
| | Scope | Grants | | ||
| | --- | --- | | ||
| | `openid` | Identify the user; adds an `id_token` to the token response. | | ||
| | `profile` | The user's name and profile picture. | | ||
| | `email` | The user's email address. | | ||
| | `offline_access` | A refresh token, so access survives past the first expiry. | | ||
| | `projects:read` | View the user's projects. | | ||
| | `projects:write` | Create, edit, and delete projects. | | ||
| | `members:read` | See project collaborators. | | ||
| | `members:write` | Invite and remove project collaborators. | | ||
| | `webhooks:read` | View project webhooks. | | ||
| | `webhooks:write` | Create and delete project webhooks. | | ||
| | `billing:read` | View subscription and billing details. | | ||
| | `billing:write` | Manage the subscription, including starting checkout. | | ||
| | `spectrum:read` | View Spectrum platform configuration. | | ||
| | `spectrum:write` | Edit Spectrum platform configuration. | | ||
| | `payments:read` | View payment provider setup. | | ||
| | `payments:write` | Enable and manage payment providers. | | ||
|
|
||
| ### Access-token lifetimes | ||
|
|
||
| Access tokens live **1 hour** by default. Sensitive scopes shorten that: `billing:write` tokens live **5 minutes**, and every other `:write` scope caps the token at **15 minutes**. When one token carries several scopes, the shortest lifetime wins — a token with `projects:read` and `projects:write` expires after 15 minutes. Apps that hold write scopes should request `offline_access` and refresh rather than treating the access token as long-lived. | ||
|
|
||
| ## OpenID Connect | ||
|
|
||
| With the `openid` scope granted, the token response includes an `id_token` and the UserInfo endpoint returns the user's claims: | ||
|
|
||
| ```sh | ||
| curl "https://app.photon.codes/api/auth/oauth2/userinfo" \ | ||
| -H "Authorization: Bearer $ACCESS_TOKEN" | ||
| ``` | ||
|
|
||
| The `id_token` is a JWT signed with **EdDSA (Ed25519)** — verify it against the [JWKS endpoint](https://app.photon.codes/api/auth/jwks), and make sure your JWT library supports EdDSA before relying on local verification. Available claims include `sub`, `email`, `email_verified`, `name`, and `picture`, gated by the `profile` and `email` scopes. | ||
|
lcandy2 marked this conversation as resolved.
|
||
|
|
||
| ## Revoking access | ||
|
|
||
| Apps can revoke a token they hold: | ||
|
|
||
| ```sh | ||
| curl -X POST "https://app.photon.codes/api/auth/oauth2/revoke" \ | ||
| -H "Content-Type: application/x-www-form-urlencoded" \ | ||
| --data-urlencode "token=$TOKEN" \ | ||
| --data-urlencode "client_id=$CLIENT_ID" \ | ||
| --data-urlencode "client_secret=$CLIENT_SECRET" | ||
| ``` | ||
|
|
||
| Users can also withdraw an app's access at any time from their dashboard settings, which revokes the consent along with every live token issued under it. Revocation propagates to API servers within about a minute. | ||
|
|
||
| ## Limitations | ||
|
|
||
| - **No machine-to-machine tokens.** The `client_credentials` grant is not supported — every access token represents a user who went through the consent flow. For server-to-server project automation, use the Spectrum API's [project credentials](/api-reference/introduction#authentication) instead. | ||
| - **No dynamic client registration.** The discovery document advertises a `registration_endpoint`, but programmatic registration is disabled — create apps in the [dashboard](https://app.photon.codes/dashboard/developer/apps). | ||
| - **`S256` only.** The `plain` PKCE challenge method is rejected, and PKCE cannot be skipped. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.