Integration walkthrough

Create your app, then run the authorization-code + PKCE flow end to end against Hep.gg.

Integration walkthrough

This page walks through the full authorization-code flow with PKCE: build a code verifier and challenge, send the user to the authorize endpoint, exchange the returned code for tokens, and read the user's claims. Every host below is real and copy-pasteable.

Create your app

Sign-in apps are self-serve. In the Hep.gg dashboard, open Login -> My Apps, then New app. You configure:

  • Name (plus an optional description, icon, and launch URL shown to users).
  • Redirect URIs: the exact URIs Hep.gg may redirect back to after sign-in. The redirect_uri you send to the authorize endpoint must match one of these byte-for-byte.
  • Who can sign in: leave it open to any signed-in Hep.gg user, or restrict it to one or more of your own groups.
  • PKCE: required by default. Leave it on unless your client cannot support it.

Open the app afterwards to choose the scopes it may request (openid is always included; toggle profile, email, groups, offline_access), set the access and refresh token lifetimes, rotate the secret, or delete the app. Your account can hold several apps; the exact limit is shown on the My Apps page and scales with your plan.

When you create the app you receive a client_id (32 hex characters) and a client_secret (a long base64url string).

Restricting who can sign in

By default any signed-in Hep.gg user with a verified email can sign in to your app. To limit it to specific people, use groups:

  1. In the dashboard, open Login -> My Groups and create a group.
  2. Add members by their exact Hep.gg user ID. You can look the ID up to confirm the right person before adding them.
  3. Back in your app's settings, set Who can sign in to that group (you can select more than one).

After that, only members of a group you selected can complete sign-in; everyone else is stopped at the Hep.gg sign-in step and never reaches your app. A user's group names are also delivered to your app through the groups scope, so you can branch on them in your own code.

Step 1: Build the PKCE pair

PKCE (S256) is required by default. Generate a high-entropy code_verifier, then derive the code_challenge as the base64url-encoded SHA-256 of the verifier. Keep the verifier on the client that started the flow; you send it to the token endpoint in step 3.

curl
# code_verifier: 43-128 chars, URL-safe
code_verifier=$(openssl rand -base64 60 | tr '+/' '-_' | tr -d '=\n')
# code_challenge = base64url( sha256( code_verifier ) )
code_challenge=$(printf '%s' "$code_verifier" \
  | openssl dgst -binary -sha256 \
  | openssl base64 | tr '+/' '-_' | tr -d '=\n')
echo "verifier:  $code_verifier"
echo "challenge: $code_challenge"

Step 2: Send the user to authorize

GEThttps://hep.gg/api/v1/login/oauth/authorizeAuth required
Start the sign-in flow. Browser redirect endpoint.

Redirect the user's browser to this URL with the query parameters below. This is a browser flow: it relies on the user's Hep.gg session cookie, so it must run as a top-level navigation in the browser, not a server-side or XHR request. There is no client_secret here.

Query parameters
response_type
stringrequired
Must be code. Any other value is rejected.
client_id
stringrequired
Your registered client id (32 hex characters).
redirect_uri
stringrequired
Must exactly match one of your app's registered redirect URIs.
scope
stringrequired
Space-delimited scopes. Must include openid. Each scope must be enabled for your client. See Scopes and claims.
state
stringoptional
Opaque value echoed back unchanged on the redirect. Strongly recommended for CSRF protection and to correlate the response with the request that started it.
code_challenge
stringoptional
PKCE challenge from step 1. Required when your client has PKCE enabled (the default).
code_challenge_method
stringoptionaldefault: plain
S256 (recommended) or plain. Always use S256. If omitted while a challenge is present, it is treated as plain.
nonce
stringoptional
Opaque value bound into the resulting id_token as the nonce claim. Recommended; verify it on return to defend against replay.

Example authorization URL:

https://hep.gg/api/v1/login/oauth/authorize
  ?response_type=code
  &client_id=0a1b2c3d4e5f60718293a4b5c6d7e8f9
  &redirect_uri=https://yourapp.example.com/callback
  &scope=openid%20profile%20email
  &state=xyz123
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256
  &nonce=n-0S6_WzA2Mj

What happens at the authorize endpoint

Hep.gg validates the request, then handles the user's session:

  • If the user is not signed in to Hep.gg, they are redirected to /signin and returned to your authorize URL after authenticating.
  • The user must have a verified email. Without one, they are sent to their Hep.gg profile to add one before the flow can complete.
  • Suspended users (Hep.gg access denied after a prior approval) are blocked.
  • If your app has allowed_groups, the user must belong to at least one.

On a fresh consent, Hep.gg shows a consent screen. If the user already has an active refresh token for your app, consent is skipped silently. Once approved, the browser is redirected back to your redirect_uri.

Success redirect

https://yourapp.example.com/callback?code=<authorization_code>&state=xyz123

Verify that state matches the value you sent, then exchange the code immediately. Codes are single-use and expire after 60 seconds.

Error responses

  • User declines consent: redirect to your redirect_uri with ?error=access_denied&error_description=User+denied+consent&state=....
  • Redirectable validation errors (for example unsupported_response_type, invalid_scope, invalid_request): redirect to your redirect_uri with ?error=<code>&error_description=...&state=....
  • Non-redirectable errors (unknown client_id, or a redirect_uri that is not registered): an HTTP 400 JSON body { "ok": false, "error": "...", "error_description": "..." }. Hep.gg will not redirect to an unregistered URI.

Step 3: Exchange the code for tokens

POSThttps://hep.gg/api/v1/login/oauth/tokenAuth required
Exchange an authorization code for tokens, or rotate a refresh token.

Call this from your backend with Content-Type: application/x-www-form-urlencoded. The endpoint requires client authentication. Two methods are supported:

  • client_secret_basic: HTTP Basic header, Authorization: Basic base64(client_id:client_secret).
  • client_secret_post: client_id and client_secret in the form body.

Bad or missing client credentials return 401 with { "error": "invalid_client" }.

Body fields (authorization_code grant)
grant_type
stringrequired
authorization_code.
code
stringrequired
The authorization code from the redirect.
redirect_uri
stringrequired
Must match the redirect_uri you used at the authorize endpoint.
code_verifier
stringoptional
The PKCE verifier from step 1. Required when the code was issued with a challenge, or when your client requires PKCE.
curl
curl -X POST https://hep.gg/api/v1/login/oauth/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=authorization_code \
  -d code="$AUTH_CODE" \
  -d redirect_uri="https://yourapp.example.com/callback" \
  -d code_verifier="$CODE_VERIFIER"

A 200 response is a token set:

{
  "access_token": "eyJhbGciOiJSUzI1Ni␣...",
  "id_token": "eyJhbGciOiJSUzI1Ni␣...",
  "refresh_token": "Yz3w␣...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "openid profile email"
}
  • id_token is present when the openid scope was granted (always, since it is required).
  • refresh_token is present only when the offline_access scope was granted. See Tokens and security.
  • expires_in is the access token lifetime in seconds (default 3600, configurable per app).

Errors return HTTP 400 or 401 with an OAuth error code: invalid_request, invalid_grant, unsupported_grant_type, or invalid_client.

Step 4: Read the user's claims

You can decode the id_token directly (after verifying it, see Tokens and security), or call the UserInfo endpoint with the access token.

GEThttps://hep.gg/api/v1/login/oauth/userinfoAuth required
Return claims about the user, scoped to the access token's grants.

Pass the access token as a bearer token. No body or query parameters; the identity and the granted scopes come from the token itself.

curl
curl https://hep.gg/api/v1/login/oauth/userinfo \
  -H "Authorization: Bearer $ACCESS_TOKEN"

A 200 response returns the claims allowed by the token's scopes (sub is always present):

{
  "sub": "u_8f2a1c9b",
  "email": "player@example.com",
  "email_verified": true,
  "name": "Player One",
  "nickname": "Player One",
  "preferred_username": "PlayerOne",
  "picture": "https://cdn.hep.gg/avatars/u_8f2a1c9b.png",
  "groups": ["staff", "beta"]
}

A missing or invalid bearer token returns 401 with header WWW-Authenticate: Bearer realm="hep.gg" and body { "error": "invalid_token" }.

See Scopes and claims for the exact mapping of scope to claim.