From e31a49aba5bc213706c6a59f94669c40aa8b4aa5 Mon Sep 17 00:00:00 2001 From: drev74 Date: Mon, 3 Feb 2025 02:10:08 +0300 Subject: [PATCH 1/3] feat: add id token --- src/client.ts | 1 + src/messages.ts | 1 + src/token.ts | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/src/client.ts b/src/client.ts index 6ff21c4..026a97b 100644 --- a/src/client.ts +++ b/src/client.ts @@ -464,6 +464,7 @@ export class OAuth2Client { return { accessToken: body.access_token, + idToken: body.id_token ?? null, expiresAt: body.expires_in ? Date.now() + (body.expires_in * 1000) : null, refreshToken: body.refresh_token ?? null, }; diff --git a/src/messages.ts b/src/messages.ts index f05e955..1a5499f 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -72,6 +72,7 @@ export type TokenResponse = { token_type: string; expires_in?: number; refresh_token?: string; + id_token?: string; scope?: string; } diff --git a/src/token.ts b/src/token.ts index 8a55bb4..83af771 100644 --- a/src/token.ts +++ b/src/token.ts @@ -19,4 +19,9 @@ export type OAuth2Token = { * OAuth2 refresh token */ refreshToken: string | null; + + /** + * OAuth2 ID Token + */ + idToken: string | null; }; From 52eab8c46787eafc040a41cd662bd94ef9339e7e Mon Sep 17 00:00:00 2001 From: Marek Rusinowski Date: Sat, 8 Feb 2025 21:34:02 +0100 Subject: [PATCH 2/3] Fix encoding of client id and secret in HTTP Basic Per https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1 the parameters first need to be url encoded. --- src/client.ts | 5 ++++- test/client-credentials.ts | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/client.ts b/src/client.ts index 026a97b..4ad28c3 100644 --- a/src/client.ts +++ b/src/client.ts @@ -402,8 +402,11 @@ export class OAuth2Client { switch(authMethod) { case 'client_secret_basic' : + // Per RFC 6749 section 2.3.1, the client_id and client_secret need + // to be encoded using application/x-www-form-urlencoded for the + // basic auth. headers.Authorization = 'Basic ' + - btoa(this.settings.clientId + ':' + this.settings.clientSecret); + btoa(encodeURIComponent(this.settings.clientId) + ':' + encodeURIComponent(this.settings.clientSecret!)); break; case 'client_secret_post' : body.client_id = this.settings.clientId; diff --git a/test/client-credentials.ts b/test/client-credentials.ts index 83f6781..b6668fd 100644 --- a/test/client-credentials.ts +++ b/test/client-credentials.ts @@ -18,8 +18,8 @@ describe('client-credentials', () => { const client = new OAuth2Client({ server: server.url, tokenEndpoint: '/token', - clientId: 'test-client-id', - clientSecret: 'test-client-secret', + clientId: 'test-client-id:10', + clientSecret: 'test=client=secret', }); const result = await client.clientCredentials(); @@ -32,7 +32,7 @@ describe('client-credentials', () => { const request = server.lastRequest(); assert.equal( request.headers.get('Authorization'), - 'Basic ' + btoa('test-client-id:test-client-secret') + 'Basic ' + btoa('test-client-id%3A10:test%3Dclient%3Dsecret') ); assert.deepEqual(request.body, { From 46b71999d4956fd17a02b4968a2987d1eeda8aaa Mon Sep 17 00:00:00 2001 From: drev74 Date: Sun, 9 Feb 2025 22:06:54 +0300 Subject: [PATCH 3/3] test: add test for id token --- src/client.ts | 2 +- src/token.ts | 2 +- test/client.ts | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/client.ts b/src/client.ts index 4ad28c3..e930032 100644 --- a/src/client.ts +++ b/src/client.ts @@ -467,7 +467,7 @@ export class OAuth2Client { return { accessToken: body.access_token, - idToken: body.id_token ?? null, + idToken: body.id_token, expiresAt: body.expires_in ? Date.now() + (body.expires_in * 1000) : null, refreshToken: body.refresh_token ?? null, }; diff --git a/src/token.ts b/src/token.ts index 83af771..cca42a1 100644 --- a/src/token.ts +++ b/src/token.ts @@ -23,5 +23,5 @@ export type OAuth2Token = { /** * OAuth2 ID Token */ - idToken: string | null; + idToken?: string; }; diff --git a/test/client.ts b/test/client.ts index cfebb82..ffca7f1 100644 --- a/test/client.ts +++ b/test/client.ts @@ -16,11 +16,33 @@ describe('tokenResponseToOAuth2Token', () => { assert.deepEqual(token, { accessToken: 'foo-bar', + idToken: undefined, expiresAt: null, refreshToken: null, }); }); + it('should respond with all tokens', async () => { + const client = new OAuth2Client({ + clientId: 'foo', + }); + const token = await client.tokenResponseToOAuth2Token( + Promise.resolve({ + token_type: 'bearer', + access_token: 'foo', + id_token: 'bar', + refresh_token: 'baz', + }) + ); + + assert.deepEqual(token, { + accessToken: 'foo', + idToken: 'bar', + expiresAt: null, + refreshToken: 'baz', + }); + }); + it('should error when an invalid JSON object is passed', async () => { const client = new OAuth2Client({ clientId: 'foo',