Skip to content

Commit e33ed59

Browse files
committed
feat: add support for assertion framework
1 parent 7463db7 commit e33ed59

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
@@ -245,6 +245,12 @@ declare namespace OAuth2Server {
245245
extendedGrantTypes?: Record<string, typeof AbstractGrantType>;
246246
}
247247

248+
interface AssertionCredential {
249+
clientAssertion: string;
250+
clientAssertionType: string;
251+
clientId?: string;
252+
}
253+
248254
/**
249255
* For returning falsey parameters in cases of failure
250256
*/
@@ -268,6 +274,16 @@ declare namespace OAuth2Server {
268274
*
269275
*/
270276
saveToken(token: Omit<Token, 'client' | 'user'>, client: Client, user: User): Promise<Token | Falsey>;
277+
278+
/**
279+
* Invoked to retrieve a client using a client assertion.
280+
*
281+
* It is for the model to decide if it supports the assertion framework and, if so, which
282+
* assertion frameworks are supported. The function can return null if no model is found or
283+
* throw an `InvalidClientError` if the assertion is invalid or not supported.
284+
*
285+
*/
286+
getClientFromAssertion?(assertion: AssertionCredential): Promise<Client | Falsey>;
271287
}
272288

273289
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)