|
| 1 | +# Go OIDC — `github.com/coreos/go-oidc/v3` |
| 2 | + |
| 3 | +Browser-based OpenID Connect sign-in using the standard [go-oidc](https://github.com/coreos/go-oidc) library and `golang.org/x/oauth2`, without the AuthGate SDK. This is useful when you want to integrate AuthGate (or any OIDC provider) into an existing Go web app that already uses the upstream OAuth2 toolchain. |
| 4 | + |
| 5 | +## OAuth Flow |
| 6 | + |
| 7 | +**Authorization Code** with: |
| 8 | + |
| 9 | +- **State** cookie — CSRF protection on the redirect back to `/callback`. |
| 10 | +- **Nonce** — CSRF / replay protection tied to the ID token. Generated on `/login`, verified against `id_token.nonce` on callback. |
| 11 | +- **PKCE (S256)** — required by most modern providers (AuthGate rejects public clients without it: `invalid_request: pkce required for public clients`). Verifier generated on `/login`, challenge sent via `oauth2.S256ChallengeOption`, verifier sent back on code exchange via `oauth2.VerifierOption`. |
| 12 | +- **ID token verification** — signature checked against the provider's JWKS; `iss`, `aud`, `exp` validated automatically by `verifier.Verify`. |
| 13 | +- **`at_hash`** — when present, access token is bound to the ID token via `idToken.VerifyAccessToken`. |
| 14 | + |
| 15 | +## Prerequisites |
| 16 | + |
| 17 | +- Go 1.25+ |
| 18 | +- An OIDC provider (e.g., AuthGate) with a confidential client that allows `http://localhost:8088/callback` as a redirect URI |
| 19 | + |
| 20 | +## Environment Variables |
| 21 | + |
| 22 | +| Variable | Required | Description | |
| 23 | +| --------------- | -------- | --------------------------------------------------------------------------- | |
| 24 | +| `ISSUER_URL` | Yes | OIDC issuer URL (discovery: `$ISSUER_URL/.well-known/openid-configuration`) | |
| 25 | +| `CLIENT_ID` | Yes | OAuth 2.0 client identifier | |
| 26 | +| `CLIENT_SECRET` | No | OAuth 2.0 client secret. Omit for **public** clients (PKCE-only) | |
| 27 | +| `REDIRECT_URL` | No | Defaults to `http://localhost:8088/callback` | |
| 28 | + |
| 29 | +## Usage |
| 30 | + |
| 31 | +```bash |
| 32 | +export ISSUER_URL=https://auth.example.com |
| 33 | +export CLIENT_ID=your-client-id |
| 34 | +export CLIENT_SECRET=your-client-secret |
| 35 | +go run main.go |
| 36 | +``` |
| 37 | + |
| 38 | +Or create a `.env` file in the `go-oidc/` directory: |
| 39 | + |
| 40 | +```bash |
| 41 | +ISSUER_URL=https://auth.example.com |
| 42 | +CLIENT_ID=your-client-id |
| 43 | +CLIENT_SECRET=your-client-secret |
| 44 | +REDIRECT_URL=http://localhost:8088/callback |
| 45 | +``` |
| 46 | + |
| 47 | +Then open <http://localhost:8088/> in a browser and click **Sign in**. |
| 48 | + |
| 49 | +## Routes |
| 50 | + |
| 51 | +| Route | Description | |
| 52 | +| ----------- | ------------------------------------------------------------------------------------------ | |
| 53 | +| `/` | Landing page with a sign-in link | |
| 54 | +| `/login` | Generates state + nonce, sets cookies, redirects to the provider's authorize endpoint | |
| 55 | +| `/callback` | Validates state, exchanges code, verifies ID token + nonce, fetches userinfo, renders JSON | |
| 56 | + |
| 57 | +## How It Works |
| 58 | + |
| 59 | +1. `oidc.NewProvider(ctx, issuerURL)` fetches the discovery document and extracts the authorize/token/userinfo/JWKS endpoints. |
| 60 | +2. `provider.Verifier(&oidc.Config{ClientID: ...})` builds an `*oidc.IDTokenVerifier` that caches JWKS keys and validates `iss`, `aud`, `exp`, and signature. |
| 61 | +3. `oauth2.Config` is wired to `provider.Endpoint()` so the upstream OAuth2 helpers do the heavy lifting of the code exchange. |
| 62 | +4. On `/login`, fresh random state, nonce, and PKCE verifier are stored in short-lived HttpOnly cookies. The nonce is passed via `oidc.Nonce(nonce)` so the provider echoes it into the ID token, and the PKCE challenge is added via `oauth2.S256ChallengeOption(verifier)`. |
| 63 | +5. On `/callback`, the handler: |
| 64 | + - Rejects the response if `state` does not match the cookie. |
| 65 | + - Exchanges the code via `oauth2Config.Exchange(ctx, code, oauth2.VerifierOption(pkceVerifier))`. |
| 66 | + - Extracts the raw ID token from `oauth2Token.Extra("id_token")`. |
| 67 | + - Calls `verifier.Verify` to cryptographically validate it. |
| 68 | + - Rejects the response if `idToken.Nonce` does not match the cookie. |
| 69 | + - Calls `idToken.VerifyAccessToken` when an `at_hash` claim is present. |
| 70 | + - Queries the UserInfo endpoint via `provider.UserInfo` and returns both ID token claims and UserInfo claims as JSON. |
| 71 | + |
| 72 | +## Example Response |
| 73 | + |
| 74 | +`GET /callback` (successful flow) returns something like: |
| 75 | + |
| 76 | +```json |
| 77 | +{ |
| 78 | + "subject": "user-uuid-1234", |
| 79 | + "issuer": "https://auth.example.com", |
| 80 | + "audience": ["your-client-id"], |
| 81 | + "expiry": "2026-04-23T14:23:45Z", |
| 82 | + "id_token_claims": { |
| 83 | + "sub": "user-uuid-1234", |
| 84 | + "email": "alice@example.com", |
| 85 | + "email_verified": true, |
| 86 | + "nonce": "..." |
| 87 | + }, |
| 88 | + "access_token": "eyJhbGci...", |
| 89 | + "refresh_token": "abcdef12...", |
| 90 | + "token_type": "Bearer", |
| 91 | + "token_expiry": "2026-04-23T15:23:45Z", |
| 92 | + "userinfo": { |
| 93 | + "sub": "user-uuid-1234", |
| 94 | + "email": "alice@example.com", |
| 95 | + "name": "Alice" |
| 96 | + }, |
| 97 | + "userinfo_fetch_ok": true |
| 98 | +} |
| 99 | +``` |
| 100 | + |
| 101 | +## Notes |
| 102 | + |
| 103 | +- For production, store `id_token`, `access_token`, and `refresh_token` in a session (e.g., encrypted cookie, server-side store) rather than echoing them to the browser — this example prints them to make the flow transparent. |
| 104 | +- The `Secure` cookie flag is set automatically when the server is reached over TLS (`r.TLS != nil`). Behind a reverse proxy that terminates TLS, configure the proxy to pass the correct scheme or set the flag explicitly. |
| 105 | +- `go-oidc` handles JWKS key rotation transparently — you do not need to refresh the verifier manually. |
0 commit comments