Repository navigation
fix(server)!: admit only confidential clients to client_credentials - #83
Merged
Merged
Conversation
RFC 6749 section 4.4: the client credentials grant type MUST only be used by confidential clients. ClientCredentialsGrant now refuses a token request that presents no client secret with 401 invalid_client. BREAKING CHANGE: register machine clients as confidential and authenticate them with their client secret. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Also document that a client service must pass runClientServiceContractTests for the grant to admit only confidential clients. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Member
Author
|
test(server): pin an empty Basic password over a body client_secret — adds a test that a public client sending Basic |
KyleJune
pushed a commit
that referenced
this pull request
Oct 7, 2026
## [0.13.0](0.12.2...0.13.0) (2026-10-07) ### ⚠ BREAKING CHANGES * **server:** `ClientCredentialsGrant` refuses a token request that presents no client secret with 401 `invalid_client`. Register machine clients as confidential and authenticate them with their client secret, by HTTP Basic or `client_secret` in the body. A client service written before 0.9.2 must pass `runClientServiceContractTests`, or `client_credentials` is not limited to confidential clients. ### Bug Fixes * **server:** admit only confidential clients to client_credentials ([#83](#83)) ([638123e](638123e))
|
🎉 This PR is included in version 0.13.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
RFC 6749 §4.4: "The client credentials grant type MUST only be used by confidential clients."
ClientCredentialsGrantissued a token to any client thatgetAuthenticatedresolved, and that interface resolves a public client presenting no secret, so a public client registered forclient_credentialsgot a machine token. The fake tenant in@udibo/oauth2/testingalready refuses that request with 401invalid_client; the real grant now does the same.The grant decides from the credentials, not from a client field:
ClientInterfacecarries no confidentiality flag.getAuthenticatedClientnow refuses a request whose credentials (read throughgetClientCredentials, HTTP Basic or body) carry no non-emptyclient_secretbefore any lookup, then authenticates throughclientService.getAuthenticatedas before. That interface already refuses a public client presenting a secret and a confidential client with a missing or wrong one (runClientServiceContractTestspins both), so a request passes only when a confidential client authenticated with its secret. An empty secret, including a Basic header ofclient_id:, counts as none.getAuthenticatedClientandgetClientCredentialsstay overridable. A subclass that callssuper.getAuthenticatedClientkeeps the check; one that authenticates another way (a signed client assertion) replaces the method and owns the guarantee, as the JSDoc says. The provider and local-IdP guides now state the rule.Verification
src/server/grants/client-credentials.test.ts→ "client authentication" drivesAuthorizationServer.handleTokenRequest. Against the unchanged grant, four refusals failed by issuing a 200 token: a public client presenting onlyclient_id, an emptyclient_secret, Basic with an empty password, and a request with no secret where the client service would resolve the client anyway. All pass now. The confidential-client-without-its-secret and public-client-with-a-stray-secret cases pass before and after (the client service refuses them), and a confidential client with its secret still gets a token by body and by Basic. "refuses a public client whose Basic password is empty even when the body carries a client_secret" pins that the check reads the same credentialsgetAuthenticatedreceives (Basic wins over the body): it fails with a 200 token when the check also accepts a bodyclient_secret, and passes at this change.authorization-server.test.ts→ "answers invalid_client when a public client presents a secret it was never issued" used a public client's 200 onclient_credentialsas its control. It now usesrefresh_token, where a public client authenticating withnoneis still accepted (400invalid_grantfor the unknown token), so it still separates authenticating withnonefrom presenting an unissued secret.deno task checkexit 0;deno task test:allexit 0 (package 239 passed / 2998 steps, plus scripts, examples, templates).Risk
Door: two-way in code; the release it cuts is published. A deployment that serves
client_credentialsto clients without a secret gets 401invalid_clientafter upgrading. Confidential clients and every other grant are unchanged.Closes
Nothing in this repository.
BREAKING CHANGE:
ClientCredentialsGrantrefuses a token request that presents no client secret with 401invalid_client. Register machine clients as confidential and authenticate them with their client secret, by HTTP Basic orclient_secretin the body. A client service written before 0.9.2 must passrunClientServiceContractTests, orclient_credentialsis not limited to confidential clients.🤖 Generated with Claude Code