--- title: Hep.gg Login - OAuth2 / OIDC Identity Provider (LLM) description: Add Sign in with Hep.gg to your app. The full OpenID Connect authorization-code (with optional PKCE) flow, claims, refresh, revocation, and logout. --- # Hep.gg Login - OAuth2 / OIDC Identity Provider Hep.gg is an OpenID Connect (OIDC 1.0) identity provider. Use it to add "Sign in with Hep.gg" to your app. Apps and groups are self-serve: their owner creates and manages them in the Hep.gg dashboard (Login -> My Apps and Login -> My Groups). Any signed-in user with a verified email can sign in, unless the app restricts access to one or more of the owner's groups. Issuer: https://hep.gg Discovery: https://hep.gg/.well-known/openid-configuration JWKS: https://hep.gg/.well-known/jwks.json The discovery document is canonical; any well-behaved OIDC client library (node-openid-client, oidc-client-ts, Authlib, MSAL-style generic OIDC, Cloudflare Zero Trust "Generic OIDC", etc.) just needs the issuer URL. ## Endpoints (also in discovery) authorization_endpoint https://hep.gg/api/v1/login/oauth/authorize token_endpoint https://hep.gg/api/v1/login/oauth/token userinfo_endpoint https://hep.gg/api/v1/login/oauth/userinfo revocation_endpoint https://hep.gg/api/v1/login/oauth/revoke end_session_endpoint https://hep.gg/api/v1/login/oauth/end-session jwks_uri https://hep.gg/.well-known/jwks.json ## Capabilities response_types_supported ["code"] (authorization code only) grant_types_supported ["authorization_code", "refresh_token"] id_token_signing_alg_values ["RS256"] token_endpoint_auth_methods ["client_secret_basic", "client_secret_post"] code_challenge_methods_supported ["S256"] (PKCE optional per-client) scopes_supported ["openid","profile","email","groups","offline_access"] subject_types_supported ["public"] response_modes_supported ["query"] Notes: - PKCE is REQUIRED by default per app (the "Require PKCE" flag defaults ON). The app owner can turn it off in the app's settings. Browser SPAs and mobile apps keep it ON. Server-side connectors that cannot send a code_challenge (for example Cloudflare Zero Trust / CF Access, some traditional confidential clients) need the flag turned OFF. - Verified email is REQUIRED. Users who haven't verified an email are redirected to their profile to add one; the OIDC flow does not return a code until they do. - Group gating is REQUIRED when the app has allowed_groups set. Users not in any allowed group are sent to /signin/denied. Empty allowed_groups means any signed-in verified-email user passes. ## Claims (in id_token + userinfo) Always present: sub Stable, opaque Hep.gg user identifier. Use this as the foreign key in your own DB. iss "https://hep.gg" aud Your client_id iat, exp, auth_time nonce Echoed back if you sent one on /authorize. Conditional on scope: scope=profile -> name, preferred_username, picture scope=email -> email, email_verified (email_verified is always true if email is present) scope=groups -> groups (array of group slugs, e.g. ["cf-zero-trust"]) Access tokens are also RS256 JWTs and carry an additional "scope" claim (space-delimited) and "token_use":"access" so userinfo can validate them. ## Flow (authorization code + optional PKCE) 1. Redirect the user to: GET https://hep.gg/api/v1/login/oauth/authorize ?response_type=code &client_id= &redirect_uri= &scope=openid+profile+email+groups &state= &nonce= &code_challenge= // only if PKCE required &code_challenge_method=S256 // only if PKCE required 2. Server flow: - No Hep.gg session -> 302 /signin?return= - No verified email -> 302 /dashboard/profile?needEmailForLogin=1 - App has allowed_groups but user is not in any -> 302 /signin/denied?app= - Otherwise -> 302 /signin/consent (or silent re-consent if the user already has an unrevoked refresh token for this app) 3. After consent, browser is redirected to: ?code=&state= The code is one-time-use, expires 60 seconds after issuance. Replaying it revokes the entire refresh-token chain for that user/app pair. 4. Exchange the code for tokens: POST https://hep.gg/api/v1/login/oauth/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code code= redirect_uri= client_id= (if not using Basic auth) client_secret= (if not using Basic auth) code_verifier= (only if PKCE was used) Client authentication: either include client_id + client_secret in the form body (client_secret_post), or send them in an HTTP Basic header (client_secret_basic): "Authorization: Basic base64(client_id:secret)". Successful response (200 OK): { "access_token": "", "refresh_token": "", "id_token": "", "token_type": "Bearer", "expires_in": 3600, "scope": "openid profile email groups" } Refresh tokens are only included when scope contains offline_access OR the call is a refresh grant. 5. Verify the id_token: - Fetch JWKS from https://hep.gg/.well-known/jwks.json (cache for the returned max-age; 24h is safe). - alg must be RS256, kid must match a JWK in the set. - Issuer must be exactly "https://hep.gg". - aud must equal your client_id. - exp must be in the future, iat in the past (allow ~60s skew). - nonce must match what you sent on /authorize. 6. Optional: call userinfo with the access token: GET https://hep.gg/api/v1/login/oauth/userinfo Authorization: Bearer Returns the same claims as id_token, scoped to what was requested. ## Refresh tokens POST https://hep.gg/api/v1/login/oauth/token grant_type=refresh_token refresh_token= client_id= client_secret= Tokens are rotated on every use: the response includes a new refresh_token and the one you sent is revoked. If you ever present a revoked token, the ENTIRE refresh-token chain for that user/app is revoked as a replay defense. Always update your stored refresh token immediately after a successful exchange. ## Revocation POST https://hep.gg/api/v1/login/oauth/revoke token= token_type_hint=refresh_token client_id= client_secret= Always returns 200 with an empty body. Access tokens are not revocable individually - they expire on their own (default 1 hour, configurable per app). ## Logout (RP-initiated) GET https://hep.gg/api/v1/login/oauth/end-session ?post_logout_redirect_uri= &state= Destroys the user's Hep.gg session and redirects them to post_logout_redirect_uri (or "/") with ?state=... appended. This does not reach into your app's session - kill your local session yourself. ## Errors Server-to-server errors return standard OAuth2 envelopes: { "error": "invalid_grant", "error_description": "Authorization code already used" } HTTP 400 / 401 / 403 depending on cause. Browser-side errors on /authorize redirect back to your redirect_uri with: ?error=&error_description=&state= Common error codes: invalid_request Missing or malformed parameter (e.g. PKCE required but no code_challenge sent). invalid_client Unknown client_id or wrong client_secret. invalid_grant Code expired, already used, or doesn't match the presented redirect_uri/code_verifier. invalid_scope Requested scope outside the app's allowed_scopes. unsupported_response_type response_type other than "code". access_denied User clicked Cancel on the consent screen. ## Cloudflare Zero Trust setup Zero Trust > Settings > Authentication > Add new > OpenID Connect (generic) Name: Hep.gg App ID: Client secret: Auth URL: https://hep.gg/api/v1/login/oauth/authorize Token URL: https://hep.gg/api/v1/login/oauth/token Certificate URL: https://hep.gg/.well-known/jwks.json Scopes: openid profile email groups PKCE: OFF (also turn "Require PKCE" off in your Hep.gg app settings; CF Access does not send a code_challenge.) Group claim mapping in CF Access policies: Selector: oidc_claims.groups contains ## Common pitfalls - "code_challenge is required" on /authorize: The app has PKCE required but the client did not send code_challenge. Either disable PKCE for that app in its settings (Login -> My Apps) or have the client send a real code_challenge + code_challenge_method=S256. - Redirect URI mismatch: redirect_uri is matched exactly. https://example.com/cb and https://example.com/cb/ are different. Query strings and trailing slashes count. - Migrating from another identity provider: Remap the subject IDs you stored under your old provider to the Hep.gg sub. The sub is stable per Hep.gg user. - Group claim missing: Make sure you requested scope=groups. Some clients drop unknown scopes silently; Hep.gg returns invalid_scope if you ask for one the app isn't allowed to use.