Skip to content

Commit 2bba848

Browse files
appleboyclaude
andcommitted
feat(go-oidc): add example using coreos/go-oidc/v3 library
- Add browser-based authorization code example with provider discovery - Support PKCE (S256) and optional client secret for public clients - Verify ID token nonce, signature, and at_hash against the JWKS - Query UserInfo endpoint and render claims as JSON response - Register new module under dependabot weekly updates - Link new example from top-level README quick reference Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f2d28e0 commit 2bba848

6 files changed

Lines changed: 417 additions & 0 deletions

File tree

.github/dependabot.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ updates:
1616
directory: /go-webservice
1717
schedule:
1818
interval: weekly
19+
- package-ecosystem: gomod
20+
directory: /go-oidc
21+
schedule:
22+
interval: weekly
1923
- package-ecosystem: pip
2024
directory: /python-cli
2125
schedule:

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Multi-language usage examples for AuthGate authentication (Go, Python, Bash).
1212
| [go-m2m](go-m2m/) | Service-to-service | Client Credentials | Go | Go 1.25+ |
1313
| [python-m2m](python-m2m/) | Service-to-service | Client Credentials | Python | Python 3.10+, uv |
1414
| [go-webservice](go-webservice/) | API protection | Bearer validation | Go | Go 1.25+ |
15+
| [go-oidc](go-oidc/) | Web login (no SDK) | Auth Code (coreos/go-oidc) | Go | Go 1.25+ |
1516

1617
## Environment Setup
1718

@@ -103,6 +104,16 @@ curl -H "Authorization: Bearer <token>" http://localhost:8080/api/profile
103104
curl -H "Authorization: Bearer <token>" http://localhost:8080/api/data
104105
```
105106

107+
## OIDC Web Login (no SDK)
108+
109+
Browser-based Authorization Code flow against any OpenID Connect provider using the standard [`github.com/coreos/go-oidc/v3`](https://github.com/coreos/go-oidc) library and `golang.org/x/oauth2`. Demonstrates discovery, state + nonce CSRF protection, ID token verification, and the UserInfo endpoint — handy when integrating AuthGate into an existing Go web app without the SDK.
110+
111+
```bash
112+
cd go-oidc
113+
go run main.go
114+
# then open http://localhost:8080/
115+
```
116+
106117
## OAuth 2.0 Flows
107118

108119
- **Authorization Code + PKCE** — Browser-based login, most secure for CLI tools on machines with a browser. The client opens a browser, the user authenticates, and a code is exchanged for tokens.

go-oidc/README.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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.

go-oidc/go.mod

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module github.com/go-authgate/examples/go-oidc
2+
3+
go 1.25.8
4+
5+
require (
6+
github.com/coreos/go-oidc/v3 v3.18.0
7+
github.com/joho/godotenv v1.5.1
8+
golang.org/x/oauth2 v0.36.0
9+
)
10+
11+
require github.com/go-jose/go-jose/v4 v4.1.4 // indirect

go-oidc/go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
github.com/coreos/go-oidc/v3 v3.18.0 h1:V9orjXynvu5wiC9SemFTWnG4F45v403aIcjWo0d41+A=
2+
github.com/coreos/go-oidc/v3 v3.18.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=
3+
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
4+
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
5+
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
6+
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
7+
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
8+
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=

0 commit comments

Comments
 (0)