Skip to content

Commit 256e2dd

Browse files
authored
Merge branch 'master' into fix/invalid-argument-error-leak
2 parents fac13b8 + 7463db7 commit 256e2dd

6 files changed

Lines changed: 53 additions & 2 deletions

File tree

docs/api/model.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ This model function is **required** for all grant types.
8484
- `refresh_token` grant
8585
- `password` grant
8686

87+
**Remarks:**
88+
`clientSecret` is `null`/absent for public clients, and also for
89+
`authorization_code` requests that authenticate with PKCE (a `code_verifier`)
90+
instead of a secret — even if the client *has* a `client_secret`. PKCE is
91+
**not** a substitute for client authentication: your implementation must
92+
reject (return a falsy value) a confidential client that should have
93+
presented its `client_secret` but did not.
94+
8795
**Kind**: instance method of [<code>Model</code>](#Model)
8896
**Fulfil**: [<code>ClientData</code>](#ClientData) - An `Object` representing the client and associated data, or a falsy value if no such client could be found.
8997
**Reject**: <code>Error</code> - An Error type

docs/api/server.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ function authorizeHandler(options) {
158158
Retrieves a new token for an authorized token request.
159159
**Remarks:**
160160
If `options.allowExtendedTokenAttributes` is `true` any additional properties set on the object returned from `Model#saveToken() <Model#saveToken>` are copied to the token response sent to the client.
161-
By default, all grant types require the client to send it's `client_secret` with the token request. `options.requireClientAuthentication` can be used to disable this check for selected grants. If used, this server option must be an object containing properties set to `true` or `false`. Possible keys for the object include all supported values for the token request's `grant_type` field (`authorization_code`, `client_credentials`, `password` and `refresh_token`). Grants that are not specified default to `true` which enables verification of the `client_secret`.
161+
By default, all grant types require the client to send it's `client_secret` with the token request. `options.requireClientAuthentication` can be used to disable this check for selected grants. If used, this server option must be an object containing properties set to `true` or `false`. Possible keys for the object include all supported values for the token request's `grant_type` field (`authorization_code`, `client_credentials`, `password` and `refresh_token`). Grants that are not specified default to `true` which enables verification of the `client_secret`. Note: setting a grant to `false` disables only the *presence* check of the `client_secret` for that grant, and does so for **all** clients (not just public ones); it does not validate a secret that is sent. The same applies to `authorization_code` requests that use PKCE (a `code_verifier`), where the presence check is skipped. Per-client (public vs confidential) authentication must therefore be enforced in your `Model#getClient() <Model#getClient>` implementation, which should reject a confidential client that fails to present its `client_secret`.
162162
```js
163163
let options = {
164164
// ...

docs/guide/pkce.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,36 @@ The loaded code has to contain `codeChallenge` and `codeChallengeMethod`.
130130
If `model.saveAuthorizationCode` did not cover these values when saving the code then this step will deny the request.
131131

132132
See `Model#saveAuthorizationCode` and `Model#getAuthorizationCode`
133+
134+
## PKCE, client authentication and refresh tokens
135+
136+
PKCE only protects the `authorization_code` → token exchange. The `code_verifier`
137+
is sent and verified **once**, when the authorization code is redeemed (step 3
138+
above); it is **not** a parameter of the `refresh_token` grant and is ignored
139+
there. A client that obtained its tokens via PKCE refreshes them like any other
140+
client — by presenting its `refresh_token` (and, if it is a confidential client,
141+
its `client_secret`).
142+
143+
PKCE is **not** client authentication, and never a substitute for a `client_secret`:
144+
145+
- A **confidential** client (one issued a `client_secret`) must authenticate with
146+
its secret on **every** token request, including `refresh_token`. PKCE is
147+
additive. Your `model.getClient(clientId, clientSecret)` is responsible for
148+
rejecting (returning a falsy value) a confidential client that fails to present
149+
its secret.
150+
- A **public** client has no secret. If you choose to issue refresh tokens to
151+
public clients (weigh the security implications first — see
152+
[RFC 9700](https://www.rfc-editor.org/rfc/rfc9700)), relax client authentication
153+
for that grant:
154+
155+
const server = new OAuth2Server({
156+
model,
157+
requireClientAuthentication: { refresh_token: false } // allow refresh without a client_secret
158+
})
159+
160+
`requireClientAuthentication: { refresh_token: false }` disables the
161+
`client_secret` **presence** check for the `refresh_token` grant for **all**
162+
clients, not just public ones, so per-client (public vs confidential)
163+
enforcement must be done in your `model.getClient`. The library does not yet
164+
model the public/confidential distinction itself (tracked in
165+
[#81](https://github.com/node-oauth/node-oauth2-server/issues/81)).

index.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,9 @@ declare namespace OAuth2Server {
228228

229229
/**
230230
* Require a client secret. Defaults to true for all grant types.
231+
* Setting a grant to `false` disables the client_secret presence check for
232+
* that grant for ALL clients (not just public ones); per-client
233+
* (public vs confidential) enforcement must be done in model.getClient.
231234
*/
232235
requireClientAuthentication?: Record<string, boolean>;
233236

lib/model.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,13 @@ class Model {
9696
* - `refresh_token` grant
9797
* - `password` grant
9898
*
99+
* **Remarks:**
100+
* `clientSecret` is `null`/absent for public clients, and also for
101+
* `authorization_code` requests that authenticate with PKCE (a `code_verifier`)
102+
* instead of a secret — even if the client *has* a `client_secret`. PKCE is
103+
* **not** a substitute for client authentication: your implementation must
104+
* reject (return a falsy value) a confidential client that should have
105+
* presented its `client_secret` but did not.
99106
*
100107
* @async
101108
* @param clientId {string} The client id of the client to retrieve.

lib/server.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ class OAuth2Server {
183183
* Retrieves a new token for an authorized token request.
184184
* **Remarks:**
185185
* If `options.allowExtendedTokenAttributes` is `true` any additional properties set on the object returned from `Model#saveToken() <Model#saveToken>` are copied to the token response sent to the client.
186-
* By default, all grant types require the client to send it's `client_secret` with the token request. `options.requireClientAuthentication` can be used to disable this check for selected grants. If used, this server option must be an object containing properties set to `true` or `false`. Possible keys for the object include all supported values for the token request's `grant_type` field (`authorization_code`, `client_credentials`, `password` and `refresh_token`). Grants that are not specified default to `true` which enables verification of the `client_secret`.
186+
* By default, all grant types require the client to send it's `client_secret` with the token request. `options.requireClientAuthentication` can be used to disable this check for selected grants. If used, this server option must be an object containing properties set to `true` or `false`. Possible keys for the object include all supported values for the token request's `grant_type` field (`authorization_code`, `client_credentials`, `password` and `refresh_token`). Grants that are not specified default to `true` which enables verification of the `client_secret`. Note: setting a grant to `false` disables only the *presence* check of the `client_secret` for that grant, and does so for **all** clients (not just public ones); it does not validate a secret that is sent. The same applies to `authorization_code` requests that use PKCE (a `code_verifier`), where the presence check is skipped. Per-client (public vs confidential) authentication must therefore be enforced in your `Model#getClient() <Model#getClient>` implementation, which should reject a confidential client that fails to present its `client_secret`.
187187
* ```js
188188
* let options = {
189189
* // ...

0 commit comments

Comments
 (0)