Docs/Authentication

Authentication

Stateless JWT bearer tokens. Every authenticated endpoint expects Authorization: Bearer <token>. The API issues short-lived access tokens (about an hour) alongside long-lived refresh tokens kept in secure storage, so a session can survive for weeks without ever exposing a long-lived credential to JavaScript.

Token types

TypeLifetimePurpose
access~1 hourBearer token for API calls
refresh~30 daysExchanged for new access tokens
passport~60 secondsCross-domain SSO redirects
password_reset~1 hourSingle-use password reset
mercure~24 hoursSubscribe to a Stumper.gg lobby SSE topic

The refresh token is an HttpOnly cookie scoped to /v1/gateway/refresh for browser clients — JavaScript can't read it, so XSS can't exfiltrate it. Capacitor apps don't share the browser cookie jar across domains, so they retrieve the refresh token explicitly and pass it in the request body, storing it in iOS Keychain or Android EncryptedSharedPreferences.

Login

TypeScript
await katforge.auth.login ('anders@example.com', 'hunter2');
// or by username
await katforge.auth.login ('anders', 'hunter2');

Wrong credentials return 401 regardless of whether the identifier exists (prevents enumeration).

Guest sessions

TypeScript
const session = await katforge.auth.guest ();
// Save reclaim_token for cross-device recovery
localStorage.setItem ('reclaim', session.reclaim_token);

Upgrade to a full account later:

TypeScript
await katforge.auth.upgrade ({ email: 'anders@example.com', password: 'hunter2' });

Token refresh

The @katforge/api handles this automatically on 401. For raw clients:

shell
curl -X POST https://api.katforge.com/v1/gateway/refresh \
   --cookie 'kat_refresh=...'

OAuth

Providers: Discord, Google, Apple, Steam, KATFORGE.

TypeScript
const { url } = await katforge.auth.oauthUrl ('discord', {
   redirect: 'https://stumper.gg/oauth/callback'
});
window.location.href = url;

The callback redirect carries either access_token (existing user) or needs_username=1 (new user who needs to pick a username).

Link and unlink additional providers on an existing account:

TypeScript
await katforge.auth.oauthLink ('google', { code, state });
await katforge.auth.oauthUnlink ('google');

Errors

StatusMeaning
401Missing, invalid, or expired token
403Token valid but action not allowed
422Validation failed (violations array in body)
429Rate limited (check Retry-After header)

All errors return { "message": "..." }. Validation errors add { "violations": [{ "propertyPath": "email", "message": "..." }] }.

The @katforge/api throws typed exceptions: AuthenticationError, ValidationError, RateLimitError, NetworkError, etc. All extend KATforgeError.