Sign in with Forge
Forge Core is a full OAuth2/OIDC provider — any app can let its users sign in with an existing Forge account, the same way a "Sign in with Google" button works. PKCE is mandatory for every client.
1. Register a client
Dynamic client registration (POST /oauth2/register) is disabled in this deployment and returns 403 access_denied. An admin registers your app instead, from Forge UI's OAuth Apps page or directly via POST /api/core/oauth-apps. The client_secret is shown once, at creation time.
| applicationType | redirect_uri rules |
|---|---|
web | Must be https — no loopback exception |
native | http allowed only on a loopback address (127.0.0.1 / localhost), per RFC 8252 |
2. Required scopes
openid— required for OIDC; issues an ID tokenprofile— name and profile claims via userinfoemail— email and email-verified claims via userinfooffline_access— issues a refresh token
Any other scope is rejected with an invalid_scope error.
3. PKCE is mandatory
Every client — confidential or public — must use PKCE with the S256 method. Generate acode_verifier and derive its code_challenge before redirecting to authorize.
4. The flow
- Redirect the user to /oauth2/authorize with your client_id, redirect_uri, scope, state, and PKCE challenge.
- Forge Core redirects back to your redirect_uri with a code and your state.
- Exchange the code (plus your code_verifier) at /oauth2/token for an access_token, id_token, and — if you requested offline_access — a refresh_token.
- Call /oauth2/userinfo with the access token to get profile/email claims.
?response_type=code &client_id=<your client_id> &redirect_uri=<your redirect_uri> &scope=openid profile email &state=<random state> &code_challenge=<derived from your verifier> &code_challenge_method=S256
curl -X POST http://localhost:3030/api/auth/oauth2/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "code=<code from the callback>" \ -d "redirect_uri=<your redirect_uri>" \ -d "client_id=<your client_id>" \ -d "client_secret=<your client_secret>" \ -d "code_verifier=<your original code_verifier>"
Verifying the ID token locally
Verify the ID token's signature against Forge Core's JWKS instead of trusting it blindly — no extra round trip needed to establish the user's sub.
import { createRemoteJWKSet, jwtVerify } from "jose";
const JWKS = createRemoteJWKSet(new URL("http://localhost:3030/api/auth/jwks"));
const { payload } = await jwtVerify(idToken, JWKS, {
issuer: "http://localhost:3030",
audience: "<your client_id>",
});Prefer not to hand-roll this?
The @casteluke/forge-client package wraps this entire flow — including a React hook that owns the browser redirect for you. See /docs/sdk.
5. Error responses
Authorize-time errors are redirected — to your redirect_uri if it was already validated, otherwise to Forge's own generic error page. Token-exchange errors come back as a JSON body. One detail worth knowing: a PKCE mismatch returns 401 invalid_request, not the more typical 400 invalid_grant.
6. Revoking a token
curl -X POST http://localhost:3030/api/auth/oauth2/revoke \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "token=<access_token or refresh_token>" \ -d "client_id=<your client_id>" \ -d "client_secret=<your client_secret>"
Per RFC 7009, this always returns 200 with an empty body — don't use it to probe whether a token is valid.
Hardening already in place
- Secret rotation: an admin can rotate your client's secret via POST /core/oauth-apps/:id/rotate-secret.
- Per-client rate limiting: OAuth2 endpoints are rate-limited per client_id, not just IP — see /docs/security.
- JWKS rotation: signing keys rotate automatically on a 90-day schedule with a 30-day grace period.
- Audit logging: token issuance, consent, and revocation events are all recorded — see /docs/security.
Known gap: multi-integrator validation
Only one independent sister app has exercised this flow end-to-end so far. If something here doesn't match real behavior for your integration, that's the most likely reason — flag it to the project owner.