Skip to main content
Version: v0.2.74

Applications & SSO (OIDC)

Two different directions — don't confuse them​

Honeyframe already supports "sign in to Honeyframe with your identity provider" today: an org admin connects Google, Microsoft (Azure AD / Entra ID), or a generic OIDC provider (Okta, Auth0, Keycloak, Ping, Cloudflare Access, or anything OIDC-compliant) from Workspace → SSO providers. That's Honeyframe acting as a relying party — consuming someone else's identity so your own staff can sign in to Honeyframe.

This page is the opposite direction: Honeyframe acting as the identity provider (OP) — issuing tokens to your application so it can offer "Login with Honeyframe" and inherit a person's identity, groups, and site scope, instead of re-implementing auth against Honeyframe's own login endpoints. If you're building a custom application that needs to know who a Honeyframe user is, this page is for you. If you want your organization to sign in to Honeyframe via your company's directory, see Workspace → SSO providers instead.

Registering an application​

An admin registers each application ("client") before it can use the flow below — there's no self-service registration. Each client is:

  • A display name and a client_id.
  • A redirect URI allowlist — exact match only, no wildcards.
  • The scopes it's allowed to request.
  • Public or confidential:
    • Public (no secret) — for anything that can't keep a secret safely: a mobile app, or a single-page app running entirely in the browser. Authenticates with PKCE alone.
    • Confidential (secret + PKCE) — for a server-side backend that can hold a client secret safely (for example, your application's own BFF).

Every client — public or confidential — completes PKCE. There is no implicit flow and no client is exempt from PKCE, including confidential ones. The client secret, where one exists, is shown once at creation, the same "shown-once" convention as a Personal Access Token — only its hash is kept afterward.

"Login with Honeyframe"​

The standard Authorization Code + PKCE flow: your application redirects the user's browser to Honeyframe's authorize endpoint, the person signs in (or is already signed in) with their normal Honeyframe credentials, Honeyframe redirects back to your application with a short-lived code, and your application's backend exchanges that code for tokens.

Endpoints​

EndpointMethodReachable byPurpose
/.well-known/openid-configurationGETAnyoneProvider metadata document
/oauth/authorizeGETBrowser redirectHosted page that starts the flow (frontend route, not /api)
/api/oidc/tokenPOSTYour backendExchanges the code (+ PKCE verifier) for tokens
/api/oidc/userinfoGET / POSTBearer access tokenFetches the same claims as the ID token
/api/oidc/jwks.jsonGETAnyoneThe RSA public keys used to verify a token's signature

/oauth/authorize is served by the frontend SPA, not the /api backend — it renders the sign-in/consent step, then the browser lands back on your redirect_uri. Every other endpoint above is a normal /api JSON endpoint.

Discovery​

{
"issuer": "https://platform.your-domain.com",
"authorization_endpoint": "https://platform.your-domain.com/oauth/authorize",
"token_endpoint": "https://platform.your-domain.com/api/oidc/token",
"userinfo_endpoint": "https://platform.your-domain.com/api/oidc/userinfo",
"jwks_uri": "https://platform.your-domain.com/api/oidc/jwks.json",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["RS256"],
"scopes_supported": ["openid", "profile", "email", "groups"],
"token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post", "none"],
"code_challenge_methods_supported": ["S256"],
"claims_supported": [
"iss", "sub", "aud", "exp", "iat", "nonce", "email", "name", "groups",
"https://honeyframe.io/claims/principal",
"https://honeyframe.io/claims/site_keys"
]
}

issuer is one stable, deployment-wide URL — your application pins one issuer, not one per organization. There is no per-organization issuer or org_id claim (see Token claims below) — scope your application's data access through groups instead. Don't hardcode any of the endpoint paths above — resolve them from the discovery document (GET /.well-known/openid-configuration) like a standard OIDC client would.

Starting the flow​

GET /oauth/authorize — browser-redirected, requires the user to be authenticated:

ParamRequiredNotes
response_typeyesMust be code — no implicit flow.
client_idyesYour registered client.
redirect_uriyesExact match against your client's registered redirect URIs.
scopeyesSpace-delimited, must include openid.
stateyesOpaque, echoed back — your CSRF defense.
code_challengeyesPKCE is mandatory for every client. base64url(SHA256(code_verifier)).
code_challenge_methodyesMust be S256 — plain is rejected.
noncerecommendedEchoed into the ID token as replay defense.

Exchanging the code​

POST /api/oidc/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=<opaque code>
&redirect_uri=<must match the one used at /oauth/authorize>
&client_id=<...>
&client_secret=<...> # confidential clients only
&code_verifier=<...> # PKCE — the server recomputes SHA256 and compares
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 3600,
"id_token": "eyJ...",
"scope": "openid profile email groups"
}

Getting user info​

GET /api/oidc/userinfo, bearer-gated with the access_token from the exchange above. Returns the same claim set as the ID token, minus its timing-specific claims (aud, nonce, exp) — always a subset, never a superset.

Verifying tokens​

GET /api/oidc/jwks.json returns the RSA public keys (RS256) used to verify a token's signature — standard JWKS shape, {"keys": [...]}. When the signing key rotates, the previous key stays published in keys[] alongside the new one for one full token lifetime, so a token signed moments before rotation still verifies.

Token claims​

ClaimNotes
subStable, opaque, never an email — shaped user:<id> or site_user:<id> depending on the principal kind below. Never reused.
email
name
groupsAn array of group names, not ids — your application shouldn't need to resolve ids back to Honeyframe.
https://honeyframe.io/claims/principal"user" (a platform user with a Honeyframe seat) or "site_user" (a webapp-scoped identity with no seat). Always present, regardless of requested scope — this is how your application tells the two subject kinds apart.
https://honeyframe.io/claims/site_keysA custom, namespaced claim carrying the same site-scope convention described on Site Users — absent or null means unrestricted, an array means a site allowlist. Present only for site_user principals.

There is no org_id or org_slug claim in v1 — tenancy isn't carried on the token. If your application needs to scope data by organization, resolve it the same way Site Users and the Webapp Data API already do, not from this token.

Scopes​

openid is required on every request. profile and email add the matching claims. groups adds the groups claim. There is no separate site_keys scope — the https://honeyframe.io/claims/site_keys and https://honeyframe.io/claims/principal claims above are always included for a site_user principal, unconditional on requested scope.

Using the identity against your data​

Completing "Login with Honeyframe" establishes who the person is. If your application also needs to call the dashboard/webapp data endpoints as that specific person, it exchanges the OIDC identity for a site-user token via a small bridge step — not part of the first release; details will be announced in the release notes once it ships. Meanwhile the data endpoints themselves (config fetch, card execute, execute-batch, block execute, filter-options preview) are unaffected: they keep working exactly as documented on Site Users and the Webapp Data API. Adding "Login with Honeyframe" in front of them doesn't change that contract.

Not in the first release​

  • No self-service application registration — an admin creates each client.
  • No refresh-token grant, logout endpoint, or revocation endpoint.
  • No consent screen. First-party applications (the ones you build in-house) get silent consent, the way a platform's own first-party apps typically skip a permission-grant screen. A consent screen for third-party applications is planned for later.
  • No encrypted ID tokens (JWE). Tokens are signed only, not encrypted — every client is expected to talk to the token endpoint directly over TLS, not through an untrusted intermediary.