README
¶
04: AS Metadata Discovery
Non-UI | No infrastructure needed | Builds on Examples 01-03
What you'll learn
- Start auth server with discovery, token, JWKS, and introspection endpoints — This is the most complete auth server we've built so far — it serves discovery, tokens, JWKS, introspection, and registration.
- Fetch the discovery document (raw HTTP) — The discovery document is a JSON object listing every endpoint the server supports. This is the same format Keycloak, Auth0, and Google use.
- Discover using the client SDK (client.DiscoverAS) — client.DiscoverAS() fetches and parses the metadata into a typed Go struct. Production code should use this — no manual JSON parsing needed.
- Register a client (using discovered URL) — Instead of hardcoding /apps/register, we use the registration_endpoint from discovery. The same code works against OneAuth, Keycloak, or any RFC 8414-compliant server.
- Get a token (using discovered token endpoint) — We use discoveredMeta.TokenEndpoint instead of hardcoding /api/token. This is the key benefit — the same client code works against any compliant AS.
- Use the token on a resource server — The resource server validates the token as in previous examples. Discovery doesn't change how tokens work — it only changes how the client finds the endpoints.
- Discover Keycloak endpoints (optional) — Same DiscoverAS() call, completely different server. If Keycloak isn't running, this step is skipped — run 'make upkcl' in examples/ to start it.
Flow
sequenceDiagram
participant App as Client App
participant AS as Auth Server
participant RS as Resource Server
Note over App,RS: Step 1: Start auth server with discovery, token, JWKS, and introspection endpoints
Note over App,RS: Step 2: Fetch the discovery document (raw HTTP)
App->>AS: GET /.well-known/openid-configuration
AS-->>App: JSON {issuer, token_endpoint, jwks_uri, ...}
Note over App,RS: Step 3: Discover using the client SDK (client.DiscoverAS)
App->>AS: client.DiscoverAS(serverURL)
AS-->>App: typed ASMetadata struct
Note over App,RS: Step 4: Register a client (using discovered URL)
App->>AS: POST {discovered_registration_endpoint}
AS-->>App: {client_id, client_secret}
Note over App,RS: Step 5: Get a token (using discovered token endpoint)
App->>AS: POST {discovered_token_endpoint}
AS-->>App: {access_token, token_type, expires_in}
Note over App,RS: Step 6: Use the token on a resource server
App->>RS: GET /resource (Bearer token)
RS-->>App: 200 {data}
Note over App,RS: Step 7: Discover Keycloak endpoints (optional)
App->>AS: client.DiscoverAS(keycloakRealmURL)
AS-->>App: {issuer, token_endpoint, jwks_uri, ...}
Steps
About this example
Actors: App (a bot), Auth Server (AS), Resource Server (RS). Think: the GitHub bot doesn't know Slack's token URL — it discovers it. What are these?
In Examples 01-03, we hardcoded the auth server's URLs:
http.PostForm(authServer.URL + "/api/token", ...) // hardcoded path
In production, you don't know the paths ahead of time. Different auth servers
use different URL structures (Keycloak: /realms/{name}/protocol/openid-connect/token,
Auth0: /oauth/token, OneAuth: /api/token).
RFC 8414 solves this: every auth server publishes a JSON document at
/.well-known/openid-configuration listing all its endpoints.
Clients fetch this once and use the discovered URLs for everything.
Step 1: Start auth server with discovery, token, JWKS, and introspection endpoints
References: RFC 8414 — AS Metadata Discovery
This is the most complete auth server we've built so far — it serves discovery, tokens, JWKS, introspection, and registration.
Step 2: Fetch the discovery document (raw HTTP)
References: RFC 8414 — AS Metadata Discovery
The discovery document is a JSON object listing every endpoint the server supports. This is the same format Keycloak, Auth0, and Google use.
What's in the metadata?
| Field | What it tells the client |
|---|---|
issuer |
The AS's canonical URL — must match the iss claim in tokens |
token_endpoint |
Where to POST for tokens (client_credentials, auth code) |
jwks_uri |
Where to GET public keys for token verification |
introspection_endpoint |
Where to POST for token introspection (RFC 7662) |
registration_endpoint |
Where to POST for dynamic client registration (RFC 7591) |
scopes_supported |
What scopes the AS recognizes |
grant_types_supported |
What OAuth grant types are available |
token_endpoint_auth_methods_supported |
How clients can authenticate (basic, post) |
code_challenge_methods_supported |
PKCE methods (S256) |
A client only needs to know the server's base URL. Everything else is discovered.
Step 3: Discover using the client SDK (client.DiscoverAS)
References: RFC 8414 — AS Metadata Discovery
client.DiscoverAS() fetches and parses the metadata into a typed Go struct. Production code should use this — no manual JSON parsing needed.
Step 4: Register a client (using discovered URL)
Instead of hardcoding /apps/register, we use the registration_endpoint from discovery. The same code works against OneAuth, Keycloak, or any RFC 8414-compliant server.
Step 5: Get a token (using discovered token endpoint)
References: RFC 6749 §4.4 — Client Credentials Grant
We use discoveredMeta.TokenEndpoint instead of hardcoding /api/token. This is the key benefit — the same client code works against any compliant AS.
Step 6: Use the token on a resource server
References: RFC 6750 — Bearer Token Usage
The resource server validates the token as in previous examples. Discovery doesn't change how tokens work — it only changes how the client finds the endpoints.
Step 7: Discover Keycloak endpoints (optional)
References: RFC 8414 — AS Metadata Discovery
Same DiscoverAS() call, completely different server. If Keycloak isn't running, this step is skipped — run 'make upkcl' in examples/ to start it.
Why discovery matters
Without discovery, switching auth providers means updating every URL in your code. With discovery, you change one base URL and everything else adapts:
// Works against OneAuth, Keycloak, Auth0 — same code
meta, _ := client.DiscoverAS("https://auth.example.com")
http.PostForm(meta.TokenEndpoint, ...) // discovered
jwksKS := keys.NewJWKSKeyStore(meta.JWKSURI) // discovered
This is especially important for the Keycloak interop tests where the same test code validates against both Keycloak and OneAuth.
What's next?
In 05 — Introspection, you'll see how resource servers can validate tokens remotely by asking the auth server "is this token valid?" — an alternative to local JWT verification that works even for opaque tokens.
References
- RFC 6749 §4.4 — Client Credentials Grant
- RFC 6750 — Bearer Token Usage
- RFC 8414 — AS Metadata Discovery
Run it
go run ./examples/04-discovery/
Pass --non-interactive to skip pauses:
go run ./examples/04-discovery/ --non-interactive
Documentation
¶
Overview ¶
Example 04: AS Metadata Discovery (RFC 8414)
In Examples 01-03, we hardcoded URLs like authServer.URL+"/api/token". In production, clients discover endpoints automatically from a single well-known URL. This is how Keycloak, Auth0, and OneAuth all work.
Run: go run ./examples/04-discovery/ Docs: Run with --readme to regenerate README.md