Skip to content

Commit cfc0b62

Browse files
committed
feat: add support for assertion framework
1 parent 0aafc36 commit cfc0b62

File tree

2 files changed

+51
-13
lines changed

2 files changed

+51
-13
lines changed

Diff for: index.d.ts

+16
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,12 @@ declare namespace OAuth2Server {
235235
extendedGrantTypes?: Record<string, typeof AbstractGrantType>;
236236
}
237237

238+
interface AssertionCredential {
239+
clientAssertion: string;
240+
clientAssertionType: string;
241+
clientId?: string;
242+
}
243+
238244
/**
239245
* For returning falsey parameters in cases of failure
240246
*/
@@ -258,6 +264,16 @@ declare namespace OAuth2Server {
258264
*
259265
*/
260266
saveToken(token: Token, client: Client, user: User): Promise<Token | Falsey>;
267+
268+
/**
269+
* Invoked to retrieve a client using a client assertion.
270+
*
271+
* It is for the model to decide if it supports the assertion framework and, if so, which
272+
* assertion frameworks are supported. The function can return null if no model is found or
273+
* throw an `InvalidClientError` if the assertion is invalid or not supported.
274+
*
275+
*/
276+
getClientFromAssertion?(assertion: AssertionCredential): Promise<Client | Falsey>;
261277
}
262278

263279
interface RequestAuthenticationModel {

Diff for: lib/handlers/token-handler.js

+35-13
Original file line numberDiff line numberDiff line change
@@ -114,25 +114,36 @@ class TokenHandler {
114114
const grantType = request.body.grant_type;
115115
const codeVerifier = request.body.code_verifier;
116116
const isPkce = pkce.isPKCERequest({ grantType, codeVerifier });
117+
const isAssertion = this.isClientAssertionRequest(request);
117118

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

122-
if (this.isClientAuthenticationRequired(grantType) && !credentials.clientSecret && !isPkce) {
123-
throw new InvalidRequestError('Missing parameter: `client_secret`');
124-
}
125+
if (this.isClientAuthenticationRequired(grantType) && !credentials.clientSecret && !isPkce) {
126+
throw new InvalidRequestError('Missing parameter: `client_secret`');
127+
}
125128

126-
if (!isFormat.vschar(credentials.clientId)) {
127-
throw new InvalidRequestError('Invalid parameter: `client_id`');
128-
}
129+
if (!isFormat.vschar(credentials.clientId)) {
130+
throw new InvalidRequestError('Invalid parameter: `client_id`');
131+
}
129132

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

134145
try {
135-
const client = await this.model.getClient(credentials.clientId, credentials.clientSecret);
146+
const client = await (isAssertion ? this.model.getClientFromAssertion?.(credentials) : this.model.getClient(credentials.clientId, credentials.clientSecret));
136147

137148
if (!client) {
138149
throw new InvalidClientError('Invalid client: client is invalid');
@@ -167,7 +178,10 @@ class TokenHandler {
167178
* The client credentials may be sent using the HTTP Basic authentication scheme or, alternatively,
168179
* the `client_id` and `client_secret` can be embedded in the body.
169180
*
170-
* @see https://tools.ietf.org/html/rfc6749#section-2.3.1
181+
* Also support the assertion framework for client authentication.
182+
*
183+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1
184+
* @see https://datatracker.ietf.org/doc/html/rfc7521
171185
*/
172186

173187
getClientCredentials (request) {
@@ -183,6 +197,10 @@ class TokenHandler {
183197
return { clientId: request.body.client_id, clientSecret: request.body.client_secret };
184198
}
185199

200+
if (this.isClientAssertionRequest(request)) {
201+
return { clientId: request.body.client_id, clientAssertion: request.body.client_assertion, clientAssertionType: request.body.client_assertion_type };
202+
}
203+
186204
if (pkce.isPKCERequest({ grantType, codeVerifier })) {
187205
if(request.body.client_id) {
188206
return { clientId: request.body.client_id };
@@ -289,6 +307,10 @@ class TokenHandler {
289307
response.status = error.code;
290308
}
291309

310+
isClientAssertionRequest({ body }) {
311+
return body.client_assertion && body.client_assertion_type;
312+
}
313+
292314
/**
293315
* Given a grant type, check if client authentication is required
294316
*/

0 commit comments

Comments
 (0)