Skip to content

Commit 7a070b9

Browse files
committed
feat: add support for assertion framework
1 parent f18f4b2 commit 7a070b9

3 files changed

Lines changed: 58 additions & 14 deletions

File tree

docs/api/handlers/token-handler.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,14 @@ Get client credentials.
5353
The client credentials may be sent using the HTTP Basic authentication scheme or, alternatively,
5454
the `client_id` and `client_secret` can be embedded in the body.
5555

56+
Also support the assertion framework for client authentication.
57+
5658
**Kind**: instance method of [<code>TokenHandler</code>](#TokenHandler)
57-
**See**: https://tools.ietf.org/html/rfc6749#section-2.3.1
59+
**See**
60+
61+
- https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1
62+
- https://datatracker.ietf.org/doc/html/rfc7521
63+
5864
<a name="TokenHandler+handleGrantType"></a>
5965

6066
### tokenHandler.handleGrantType()

index.d.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,12 @@ declare namespace OAuth2Server {
242242
extendedGrantTypes?: Record<string, typeof AbstractGrantType>;
243243
}
244244

245+
interface AssertionCredential {
246+
clientAssertion: string;
247+
clientAssertionType: string;
248+
clientId?: string;
249+
}
250+
245251
/**
246252
* For returning falsey parameters in cases of failure
247253
*/
@@ -265,6 +271,16 @@ declare namespace OAuth2Server {
265271
*
266272
*/
267273
saveToken(token: Omit<Token, 'client' | 'user'>, client: Client, user: User): Promise<Token | Falsey>;
274+
275+
/**
276+
* Invoked to retrieve a client using a client assertion.
277+
*
278+
* It is for the model to decide if it supports the assertion framework and, if so, which
279+
* assertion frameworks are supported. The function can return null if no model is found or
280+
* throw an `InvalidClientError` if the assertion is invalid or not supported.
281+
*
282+
*/
283+
getClientFromAssertion?(assertion: AssertionCredential): Promise<Client | Falsey>;
268284
}
269285

270286
interface RequestAuthenticationModel {

lib/handlers/token-handler.js

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -117,25 +117,36 @@ class TokenHandler {
117117
const grantType = request.body.grant_type;
118118
const codeVerifier = request.body.code_verifier;
119119
const isPkce = pkce.isPKCERequest({ grantType, codeVerifier });
120+
const isAssertion = this.isClientAssertionRequest(request);
120121

121-
if (!credentials.clientId) {
122-
throw new InvalidRequestError('Missing parameter: `client_id`');
123-
}
122+
// @todo - if multiple authentication schemes exist, throw an error
123+
if (!isAssertion) {
124+
if (!credentials.clientId) {
125+
throw new InvalidRequestError('Missing parameter: `client_id`');
126+
}
124127

125-
if (this.isClientAuthenticationRequired(grantType) && !credentials.clientSecret && !isPkce) {
126-
throw new InvalidRequestError('Missing parameter: `client_secret`');
127-
}
128+
if (this.isClientAuthenticationRequired(grantType) && !credentials.clientSecret && !isPkce) {
129+
throw new InvalidRequestError('Missing parameter: `client_secret`');
130+
}
128131

129-
if (!isFormat.vschar(credentials.clientId)) {
130-
throw new InvalidRequestError('Invalid parameter: `client_id`');
131-
}
132+
if (!isFormat.vschar(credentials.clientId)) {
133+
throw new InvalidRequestError('Invalid parameter: `client_id`');
134+
}
132135

133-
if (credentials.clientSecret && !isFormat.vschar(credentials.clientSecret)) {
134-
throw new InvalidRequestError('Invalid parameter: `client_secret`');
136+
if (credentials.clientSecret && !isFormat.vschar(credentials.clientSecret)) {
137+
throw new InvalidRequestError('Invalid parameter: `client_secret`');
138+
}
139+
} else {
140+
if (!credentials.clientAssertion) {
141+
throw new InvalidClientError('Missing parameter: `client_assertion`');
142+
}
143+
if (!credentials.clientAssertionType) {
144+
throw new InvalidClientError('Missing parameter: `client_assertion_type`');
145+
}
135146
}
136147

137148
try {
138-
const client = await this.model.getClient(credentials.clientId, credentials.clientSecret);
149+
const client = await (isAssertion ? this.model.getClientFromAssertion?.(credentials) : this.model.getClient(credentials.clientId, credentials.clientSecret));
139150

140151
if (!client) {
141152
throw new InvalidClientError('Invalid client: client is invalid');
@@ -170,7 +181,10 @@ class TokenHandler {
170181
* The client credentials may be sent using the HTTP Basic authentication scheme or, alternatively,
171182
* the `client_id` and `client_secret` can be embedded in the body.
172183
*
173-
* @see https://tools.ietf.org/html/rfc6749#section-2.3.1
184+
* Also support the assertion framework for client authentication.
185+
*
186+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1
187+
* @see https://datatracker.ietf.org/doc/html/rfc7521
174188
*/
175189

176190
getClientCredentials(request) {
@@ -189,6 +203,10 @@ class TokenHandler {
189203
};
190204
}
191205

206+
if (this.isClientAssertionRequest(request)) {
207+
return { clientId: request.body.client_id, clientAssertion: request.body.client_assertion, clientAssertionType: request.body.client_assertion_type };
208+
}
209+
192210
if (pkce.isPKCERequest({ grantType, codeVerifier })) {
193211
if (request.body.client_id) {
194212
return { clientId: request.body.client_id };
@@ -302,6 +320,10 @@ class TokenHandler {
302320
response.status = error.code;
303321
}
304322

323+
isClientAssertionRequest({ body }) {
324+
return body.client_assertion && body.client_assertion_type;
325+
}
326+
305327
/**
306328
* Given a grant type, check if client authentication is required
307329
*/

0 commit comments

Comments
 (0)