feat(api): add /.well-known/openid-configuration discovery endpoint - #166
feat(api): add /.well-known/openid-configuration discovery endpoint#166rabble wants to merge 3 commits into
Conversation
|
this is a draft, we can work on atproto integration after launch |
Reliability assurance suite (divine-iac-coreconfig PR #440) found that GET /.well-known/openid-configuration returned the SvelteKit SPA index HTML on both staging and production. Any OIDC client doing autodiscovery got HTML, failed to parse, and treated keycast as unreachable. Root cause: keycast had no handler for the OIDC discovery path. The request fell through to ServeDir's fallback (which serves index.html for any non-file path), so it returned 200 + text/html instead of a JSON discovery document. Fix: add `keycast_api::api::http::openid_configuration` mirroring the existing `atproto_oauth_metadata` (RFC 8414, oauth-authorization-server) sibling. Wired into `well_known_routes` in keycast/src/main.rs alongside the other /.well-known/* JSON endpoints. The discovery document is conservative — it advertises only what keycast actually supports today (OAuth 2.0 code flow, PKCE-S256, ES256 signing). Keycast is not a full OIDC provider (no id_token issuance, no userinfo endpoint), so a client that tries to use strict OIDC features beyond OAuth code flow will fail at runtime rather than fail silently — that's the correct degradation. Two regression tests cover the response shape and trailing-slash hygiene on APP_URL.
3dece63 to
7a7871e
Compare
|
I took this over and pushed What changed:
Verification:
I also tried |
NotThatKindOfDrLiz
left a comment
There was a problem hiding this comment.
Approved after takeover. The env-racy tests are fixed, the OpenID well-known response no longer advertises unsupported OIDC Provider capabilities, ATProto entryway origin handling stays scoped to the ATProto metadata endpoint, and CI checks are green.
dcadenas
left a comment
There was a problem hiding this comment.
The new discovery document advertises OAuth behavior that /api/oauth/token does not implement. Reconcile the metadata with the endpoint's actual request format and security behavior before merge.
| Json(OpenIdConfiguration { | ||
| issuer: origin.clone(), | ||
| authorization_endpoint: endpoint(&origin, "/api/oauth/authorize"), | ||
| token_endpoint: endpoint(&origin, "/api/oauth/token"), |
There was a problem hiding this comment.
Reconcile this advertised endpoint with /api/oauth/token before merge. The handler uses Json<TokenRequest>, so a standards-compliant client that follows discovery and submits the normal application/x-www-form-urlencoded token request gets a 415. The adjacent none claim also promises a public-client flow while the handler leaves PKCE optional and does not bind the request client_id to the authorization code. Narrow this metadata now by omitting or explicitly qualifying token_endpoint and removing token_endpoint_auth_methods_supported: ["none"]; leave handler hardening for a separate change.
|
@rabble I took over the remaining review blocker and pushed What changed:
Verification:
Local note: Please review the takeover commit and merge when you are ready. @dcadenas, this should address the requested token endpoint request-format mismatch; PKCE remains accurately advertised as supported, not required. |
NotThatKindOfDrLiz
left a comment
There was a problem hiding this comment.
Approved after takeover pass. The discovery metadata/token endpoint mismatch is resolved by accepting form-encoded token requests while preserving existing JSON callers, and CI is green.
dcadenas
left a comment
There was a problem hiding this comment.
The discovery document still advertises an unauthenticated public-client token flow whose request binding and PKCE behavior do not match that contract. Narrow the metadata and pin the corrected shape in tests before merge, while keeping deeper token-handler hardening out of this PR.
| Json(OpenIdConfiguration { | ||
| issuer: origin.clone(), | ||
| authorization_endpoint: endpoint(&origin, "/api/oauth/authorize"), | ||
| token_endpoint: endpoint(&origin, "/api/oauth/token"), |
There was a problem hiding this comment.
Remove or narrow the public-client token claim at api/src/api/http/openid_configuration.rs:34-40 before advertising this flow. It currently includes token_endpoint_auth_methods_supported: ["none"], while the authorization-code exchange does not compare the request client_id with the stored code and only enforces PKCE when a challenge exists:
keycast/api/src/api/http/oauth.rs
Lines 2603 to 2631 in 0fa43b3
A discovery-driven unauthenticated client can therefore follow the metadata into a token exchange with weaker binding than advertised. Update the discovery test to pin the narrowed metadata, keep deeper token-handler hardening outside this PR, and verify that form and JSON token decoding still work.
Summary
GET /.well-known/openid-configurationso autodiscovery callers receive JSON instead of the SvelteKit SPA fallback HTML.openidscope, ID Token signing,jwks_uri, or UserInfo claims.ATPROTO_ENTRYWAY_ORIGIN, while the OpenID compatibility endpoint uses the normal app origin fromAPP_URL/VITE_DOMAIN./api/oauth/tokento accept both standards-compliantapplication/x-www-form-urlencodedrequests and the existing JSON request format.ATPROTO_ENTRYWAY_ORIGINdoes not leak into this endpoint.Motivation
/.well-known/openid-configuration, the static SPA fallback servedindex.htmlwith a 200 response.Related Issue
Testing
cargo fmt --all -- --checkcargo clippy --workspace --all-targets --all-features -- -D warnings -A deprecatedcargo test --package keycast_api --test oauth_token_request_body_testcargo test --package keycast_api --test openid_configuration_testcargo test --package keycast_api --test atproto_oauth_metadata_testDATABASE_URL=postgres://postgres:password@localhost:5432/keycast_test REDIS_URL=redis://localhost:16379 TEST_REDIS_URL=redis://localhost:16379 cargo test --workspace --verboseDATABASE_URL=postgres://postgres:password@localhost:5432/keycast_test REDIS_URL=redis://localhost:16379 TEST_REDIS_URL=redis://localhost:16379 cargo test -p keycast_api --features integration-tests --lib --tests --verboseDATABASE_URL=postgres://postgres:password@localhost:5432/keycast_test REDIS_URL=redis://localhost:16379 TEST_REDIS_URL=redis://localhost:16379 cargo test -p keycast_core --features integration-tests --lib --tests --verbosecargo test -p keycast_signer --features integration-tests --lib --tests --verbosecargo test --workspace --verboseafter0fa43b3could not complete because port 5432 is occupied by non-Keycastconfig-postgres-1, whose credentials reject the repo defaultpostgres:passwordtest URL.bun run testwas attempted locally, but this machine is missingcargo-nextest.Visuals