Skip to content

Commit d578fb9

Browse files
committed
feat: add support for assertion framework
1 parent fa4474a commit d578fb9

2 files changed

Lines changed: 51 additions & 13 deletions

File tree

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
@@ -115,25 +115,36 @@ class TokenHandler {
115115
const grantType = request.body.grant_type;
116116
const codeVerifier = request.body.code_verifier;
117117
const isPkce = pkce.isPKCERequest({ grantType, codeVerifier });
118+
const isAssertion = this.isClientAssertionRequest(request);
118119

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

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

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

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

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

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

174188
getClientCredentials (request) {
@@ -184,6 +198,10 @@ class TokenHandler {
184198
return { clientId: request.body.client_id, clientSecret: request.body.client_secret };
185199
}
186200

201+
if (this.isClientAssertionRequest(request)) {
202+
return { clientId: request.body.client_id, clientAssertion: request.body.client_assertion, clientAssertionType: request.body.client_assertion_type };
203+
}
204+
187205
if (pkce.isPKCERequest({ grantType, codeVerifier })) {
188206
if(request.body.client_id) {
189207
return { clientId: request.body.client_id };
@@ -291,6 +309,10 @@ class TokenHandler {
291309
response.status = error.code;
292310
}
293311

312+
isClientAssertionRequest({ body }) {
313+
return body.client_assertion && body.client_assertion_type;
314+
}
315+
294316
/**
295317
* Given a grant type, check if client authentication is required
296318
*/

0 commit comments

Comments
 (0)