Tags: zitadel/oidc
Tags
fix: tracing key, doc rephrasing and guard slog.SetDefault against nil ( #935) - pkg/client/rp/relying_party.go:881 — EndSession tracing span was named "RefreshTokens"; now "EndSession". - pkg/op/token_request.go:89 — rewrote the AuthenticatedTokenRequestAuthMethodChecker doc comment to say what it does, that ParseAuthenticatedTokenRequest uses it to enforce RFC 6749 §2.3, and that requests not implementing it are skipped. - example/server/exampleop/op.go:38 — slog.SetDefault(logger) now guarded against a nil logger, with a note that replacing the process-wide logger is example-only behaviour. <!-- Please inform yourself about the contribution guidelines on submitting a PR here: https://github.com/zitadel/oidc/blob/main/CONTRIBUTING.md#submit-a-pull-request-pr. Take note of how PR/commit titles should be written and replace the template texts in the sections below. Don't remove any of the sections. It is important that the commit history clearly shows what is changed and why. --> # Which Problems Are Solved - pkg/client/rp/relying_party.go:881 — EndSession tracing span was named "RefreshTokens"; now "EndSession". - pkg/op/token_request.go:89 — rewrote the AuthenticatedTokenRequestAuthMethodChecker doc comment to say what it does, that ParseAuthenticatedTokenRequest uses it to enforce RFC 6749 §2.3, and that requests not implementing it are skipped. - example/server/exampleop/op.go:38 — slog.SetDefault(logger) now guarded against a nil logger, with a note that replacing the process-wide logger is example-only behaviour. # How the Problems Are Solved # Additional Changes # Additional Context
fix(op): reject requested_token_type that cannot be issued (#932) # Which Problems Are Solved A token exchange asking for `requested_token_type=urn:ietf:params:oauth:token-type:jwt` returns `200` with an empty token: {"access_token":"","issued_token_type":"urn:ietf:params:oauth:token-type:jwt","token_type":"","scope":"openid offline_access"} `ValidateTokenExchangeRequest` accepts the type, since `TokenType.IsSupported` covers all of `AllTokenTypes`. `CreateTokenExchangeResponse` has no case for it, and its `default:` branch builds the error but never returns it, so `token` and `tokenType` stay empty and are marshalled as a success. RFC 8693 §2.2.1 makes `access_token` and `token_type` REQUIRED in a successful response; a client that checks the status code takes the empty string as its token. # How the Problems Are Solved Returns the error that branch already constructs, matching the other error paths in the function. The response becomes `400 {"error":"invalid_request","error_description":"requested_token_type is invalid"}`. Behaviour is unchanged for `access_token`, `refresh_token` and `id_token`. # Additional Changes Adds a `TestRoutes` case for the jwt requested token type. I verified it fails without the one-line change (`expected: 400, actual: 200`) and passes with it. # Additional Context Compatibility: an omitted or empty `requested_token_type` reaches the same `default:` branch, so a `TokenExchangeStorage` that does not default it in `ValidateTokenExchangeRequest` — `pkg/op/storage.go:90` recommends it, nothing enforces it — moves from that same empty `200` to a `400`. Happy to default that case instead of rejecting it, if you would prefer. `go vet ./...` is clean; `go test -race ./pkg/...` passes apart from the pre-existing `"expires_in":299` assertions, which flake on darwin/arm64 here for reasons unrelated to this change. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Wim Van Laer <wim@wvl.app>
feat: support custom issuer verifier (#928) <!-- Please inform yourself about the contribution guidelines on submitting a PR here: https://github.com/zitadel/oidc/blob/main/CONTRIBUTING.md#submit-a-pull-request-pr. Take note of how PR/commit titles should be written and replace the template texts in the sections below. Don't remove any of the sections. It is important that the commit history clearly shows what is changed and why. --> # Which Problems Are Solved Azure split issuer by tenent id `https://login.microsoftonline.com/{tenantid}/v2.0` when we want support more general users, like both company and personal users, the issuer is dynamically different by the tenent. the discovery endpoint [https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration](https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration) shows the `{tenantid}` pattern as well. This PR support customized issuer verifier to support dynamic issuer. reference: https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc#find-your-apps-openid-configuration-document-uri # How the Problems Are Solved This PR support customized issuer verifier to support dynamic issuer. # Additional Changes change all the usage to use the new function `oidc.CheckIssuerWithISSVerifier(claims, v.Issuer, v.ISS)` it does not change current or default usage, just add option to support cusomized issuer verifier. # Additional Context
fix(oidc): callTokenEndpoint expiry conversion error (#929) <!-- Please inform yourself about the contribution guidelines on submitting a PR here: https://github.com/zitadel/oidc/blob/main/CONTRIBUTING.md#submit-a-pull-request-pr. Take note of how PR/commit titles should be written and replace the template texts in the sections below. Don't remove any of the sections. It is important that the commit history clearly shows what is changed and why. --> # Which Problems Are Solved A bug was introduced in the PR #821 which changes `exp` claim in `AccessTokenResponse` from a `int64` to an `oidc.Duration` but does not update `callTokenEndpoint`. Thus, `callTokenEndpoint` still treats it as int64 and and multiples it by `time.second`. This results in an incorrect Expiry value, depending on the `expiresIn` value it can be ahead by 30 years all the way to dates well exceeding the time period of Futurama. # How the Problems Are Solved We fix this bug by updating `callTokenEndpoint` to bring in line the intent of #821 From: `Expiry: time.Now().UTC().Add(time.Duration(tokenRes.ExpiresIn) * time.Second),` To: `Expiry: time.Now().UTC().Add(tokenRes.ExpiresIn.AsDuration()),` # Additional Changes We also add a simple regression test for this bug. # Additional Context Note this that only impacts v4 and v3 since this change from `int64` to `oidc.Duration` only exists in v4. We encountered this bug when preparing OpenPubkey connect to update from v3 to v4 openpubkey/openpubkey#392
fix(rp): use redirect host for CLI callback server (#926) # Which Problems Are Solved - The CLI authorization flow always opens `http://localhost:<port>/login`, regardless of the loopback host configured in the redirect URI. - The callback server always listens on `:<port>`, ignoring an explicit IPv4 or IPv6 loopback address in the redirect URI. - Consequently, callers cannot ensure that the callback server and browser use the same explicit loopback address. - Invalid or inconsistent callback configurations are not detected before the server starts. # How the Problems Are Solved - Derives both the callback listener address and the browser `/login` URL from the configured redirect URI. - Binds directly to explicit IPv4 and IPv6 loopback addresses, ensuring that the browser and callback server use the same address family. - Validates that the redirect URI: - uses HTTP - contains an explicit port matching the CLI port - uses `localhost` or a loopback IP address - contains the expected callback path - does not contain user information or a fragment - Preserves the existing `:<port>` listener behavior for callers using `localhost`, avoiding a backward-incompatible single-address-family change. # Additional Changes - Updates the client examples to use `127.0.0.1`, avoiding `localhost` address-family ambiguity. - Renames the `StartServer` parameter from `port` to `address` to reflect that it receives a complete listening address. - Adds tests covering IPv4, IPv6, existing `localhost` behavior, missing and mismatched ports, non-loopback hosts, hostname lookalikes, HTTPS, and callback path mismatches. # Additional Context Callers using an explicit loopback IP now get deterministic address-family behavior. Existing callers using `localhost` retain the previous wildcard listener behavior for compatibility. Chromium resolves `localhost` to both IPv6 and IPv4, placing the IPv6 loopback address first. This can be observed on macOS by starting separate IPv6 and IPv4 listeners. In the first terminal: ```console $ nc -k -6 -l 3000 ``` In the second terminal: ```console $ nc -k -4 -l 3000 ``` Both listeners can be confirmed with: ```console $ lsof -n -P -i:3000 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME nc 61660 suku 3u IPv4 0xaf57491278098f95 0t0 TCP *:3000 (LISTEN) nc 61696 suku 3u IPv6 0x86b12b3afee37ced 0t0 TCP *:3000 (LISTEN) ``` Opening `http://localhost:3000` in Chromium connects to the IPv6 listener. Because that connection succeeds, Chromium does not need to fall back to IPv4. This demonstrates that `localhost` is not equivalent to explicitly selecting `127.0.0.1`. Chromium's [`ResolveLocalHostname`](https://source.chromium.org/chromium/chromium/src/+/c70cbba2e0b73e622ff5899294e76974be0083f2:net/dns/host_resolver_manager.cc;l=332) adds the IPv6 loopback address before the IPv4 loopback address. This is a callback reliability and loopback-hardening change. It does not claim that every wildcard Go listener encounters an address-family failure.
feat(oidc): add client_id_metadata_document_supported discovery metad… …ata (#905) # Which Problems Are Solved A provider built on this library cannot advertise support for the OAuth Client ID Metadata Document (CIMD) spec in its discovery document, because `DiscoveryConfiguration` has no field for the standard `client_id_metadata_document_supported` Authorization Server Metadata property. # How the Problems Are Solved - Add an additive boolean field `ClientIDMetadataDocumentSupported` to `DiscoveryConfiguration` in `pkg/oidc/discovery.go`, serialized as `client_id_metadata_document_supported` with `omitempty`. - Extend `TestDiscover` to cover JSON serialization of the new field. # Additional Changes - None. No change to the `op.Server` interface, no handler, no CIMD resolution logic. The flag defaults to false and is omitted from existing discovery output via `omitempty`, so there is no behavior change for current providers. # Additional Context - Spec: draft-ietf-oauth-client-id-metadata-document (OAuth WG), Section 5, field `client_id_metadata_document_supported`: https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/ - Context: RFC 8414 (Authorization Server Metadata), OpenID Connect Discovery 1.0. - Use case: MCP client identification, SEP-991: modelcontextprotocol/modelcontextprotocol#991 - Closes #904 - Complementary to #782 (dynamic client registration); additive, no interface change. --------- Co-authored-by: Livio Spring <9405495+livio-a@users.noreply.github.com>
fix(op): update stale v3 import in token_request_test.go (#920) # Which Problems Are Solved - `pkg/op/token_request_test.go` imported `github.com/zitadel/oidc/v3/...` while the module itself is `github.com/zitadel/oidc/v4`, causing the test file to reference a different major version than the rest of the codebase. # How the Problems Are Solved - Updated the two imports (`pkg/oidc` and `pkg/op`) in `pkg/op/token_request_test.go` from `v3` to `v4`. # Additional Changes None. # Additional Context None. --- _Generated by [Claude Code](https://claude.ai/code/session_01EbHcRepm3aWad8tsRE3ZhT)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
fix: only allow one authenticate method (#858) according to the RFC 6749 §2.3 `The client MUST NOT use more than one authentication method in each request.` this PR resolve issue #857 1. add the logic to only have one authenticate method in client side 2. add the restrict on the OP side to only allow one authenticate method. 3. deprecated interface `ClientSecretBasicAuthRequest` and the Auth() function 4. add interface `AuthenticatedTokenRequestAuthMethodChecker` and added the only one auth method check for input tokens that implemented such checker interface. NOTE: the Auth() function is no longer invoked after this PR, but we keep the public API. test: added united test ### Definition of Ready - [x] I am happy with the code - [x] Short description of the feature/issue is added in the pr description ~~- [ ] PR is linked to the corresponding user story~~ - [ ] Acceptance criteria are met ~~- [ ] All open todos and follow ups are defined in a new ticket and justified~~ ~~- [ ] Deviations from the acceptance criteria and design are agreed with the PO and documented.~~ - [x] No debug or dead code - [x] My code has no repetitions - [x] Critical parts are tested automatically ~~- [ ] Where possible E2E tests are implemented~~ ~~- [ ] Documentation/examples are up-to-date~~ - [ ] All non-functional requirements are met ~~- [ ] Functionality of the acceptance criteria is checked manually on the dev system.~~ --------- Co-authored-by: Wim Van Laer <wim@wvl.app> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
fix(op): add client ownership check to LegacyServer CodeExchange (#902) LegacyServer.CodeExchange looks up the auth request by code only, without checking that the authenticated client owns it. The modern path in ValidateAccessTokenRequest (token_code.go:59) checks: if client.GetID() != authReq.GetClientID() { return error } This adds the same check to LegacyServer.CodeExchange. --------- Co-authored-by: Wim Van Laer <wim@wvl.app>
fix(op): add client ownership check to LegacyServer CodeExchange (#902) LegacyServer.CodeExchange looks up the auth request by code only, without checking that the authenticated client owns it. The modern path in ValidateAccessTokenRequest (token_code.go:59) checks: if client.GetID() != authReq.GetClientID() { return error } This adds the same check to LegacyServer.CodeExchange. --------- Co-authored-by: Wim Van Laer <wim@wvl.app>
PreviousNext