Authorize
Send the user to haunt.gg to sign in and approve your app.
The start of the authorization code flow. Redirect the user's browser here —
this is not an endpoint you call from your server. haunt.gg handles the login,
shows its consent screen, and sends the browser back to your redirect_uri with
a one-time code.
/api/auth/oauth2/authorizePKCE is mandatory for every haunt.gg app, and S256 is the only accepted
method. Generate a random code_verifier, send its SHA-256 hash as
code_challenge, and keep the verifier for the token exchange.
Query Parameters
client_idquerystringrequiredredirect_uriquerystringrequired127.0.0.1 (and ::1), where the port is ignored so a native app can listen on a random one — see the note below.response_typequerystringrequiredcode. Anything else is rejected before the flow starts.code_challengequerystringrequiredcode_verifier. Required — PKCE is not optional here.code_challenge_methodquerystringrequiredS256. plain is rejected.scopequerystringopenid identify email. Every entry must be one your app is registered for. Omit it entirely and haunt.gg grants your app's full registered scope set.statequerystringnoncequerystringid_token as the nonce claim. Verify it after the exchange to bind the token to this request.promptquerystringnone, login, consent, create, login consent. login and create force a fresh sign-in; consent always re-shows the approval screen; none never shows any UI and instead fails with a redirect error. select_account is advertised in discovery but not supported.Native apps: register 127.0.0.1, not localhost. The port is only
ignored for loopback IP literals (RFC 8252 §7.3). Register
http://localhost:3000/callback and come back on port 51734 and the request
is refused with invalid_redirect; register http://127.0.0.1:3000/callback
and any port works. Both are accepted at registration time, so this only
surfaces once you are testing.
Unknown query parameters are dropped, not passed through. login_hint,
id_token_hint, max_age, display, ui_locales, acr_values and
resource are all ignored at this endpoint — resource is only honoured at
the token endpoint.
Example Request
// 1. PKCE
const verifier = base64url(crypto.getRandomValues(new Uint8Array(32)));
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
const challenge = base64url(new Uint8Array(digest));
// Keep `verifier` and `state` in the user's session — you need them later.
const state = base64url(crypto.getRandomValues(new Uint8Array(16)));
const url = new URL("https://haunt.gg/api/auth/oauth2/authorize");
url.searchParams.set("client_id", CLIENT_ID);
url.searchParams.set("redirect_uri", "https://example.com/auth/callback");
url.searchParams.set("response_type", "code");
url.searchParams.set("scope", "openid identify offline_access");
url.searchParams.set("state", state);
url.searchParams.set("code_challenge", challenge);
url.searchParams.set("code_challenge_method", "S256");
redirect(url.toString());Response
This endpoint never returns JSON. It answers with a 302 to your redirect URL —
either with a code, or with an OAuth error.
https://example.com/auth/callback
?code=1a2B3c4D5e6F7g8H9i0J1k2L3m4N5o6P
&state=RANDOM_STATE
&iss=https://haunt.ggErrors your app receives
error | When |
|---|---|
access_denied | The user pressed Cancel on the consent screen. |
invalid_scope | A requested scope is not registered on your app. error_description names them. |
invalid_request | PKCE problem — the challenge and method were not both sent, or the method was not S256. |
login_required | prompt=none but the user is not signed in. |
consent_required | prompt=none but the user has not approved these permissions yet. |
interaction_required | prompt=none but the flow needs a UI step. |
account_selection_required | prompt=none and the account could not be determined. |
Errors your app never sees
Some failures happen before the redirect URL is validated, and OAuth
forbids redirecting to an unverified URL. Those send the user to
https://haunt.gg/login?error=…&error_description=… and your app simply never
gets a callback. This is what you are looking at when a test flow appears to
hang on the haunt.gg login page:
invalid_client— unknown or missingclient_idinvalid_redirect— theredirect_uriis not registered, character for characterclient_disabled— the app was suspended by haunt.gg staffunauthorized_client— the app is not registered for the authorization code grantunsupported_response_type—response_typewas missing or notcode
Consent
haunt.gg shows its own consent screen with your app's name, the owner's handle,
and your website, terms and privacy links if you registered them. It lists
exactly the permissions in your scope.
If the same user already approved the same set of permissions, the screen is
skipped and the browser bounces straight back with a code. Adding a scope later
re-prompts. prompt=consent always re-prompts.
Rate limit
100 requests per minute per IP address, counted for this endpoint alone.