Repository: badgateway/oauth2-client
Pull request #116 (2023-07-28 13:24:51)

create a way to add some additional params to redirect url
Hi! I very like you library for lightweight. But custom auth server in my company wants to have some specific user info form redirect URL. Like deviceId, analytics systems id and other. And i don't see possibility do that with this lib.



Patch:
diff --git a/src/client/authorization-code.ts b/src/client/authorization-code.ts
index 618a33d..ada05dd 100644
--- a/src/client/authorization-code.ts
+++ b/src/client/authorization-code.ts
@@ -26,6 +26,11 @@ type GetAuthorizeUrlParams = {
    * List of scopes.
    */
   scope?: string[];
+
+  /**
+   * Any parameters listed here will be added to the query string for the authorization server endpoint.
+   */
+  extraParams?: Record<string, string>;
 }
 
 type ValidateResponseResult = {
@@ -66,7 +71,7 @@ export class OAuth2AuthorizationCodeClient {
       this.client.getEndpoint('authorizationEndpoint')
     ]);
 
-    const query: AuthorizationQueryParams = {
+    let query: AuthorizationQueryParams = {
       client_id: this.client.settings.clientId,
       response_type: 'code',
       redirect_uri: params.redirectUri,
@@ -80,6 +85,15 @@ export class OAuth2AuthorizationCodeClient {
       query.scope = params.scope.join(' ');
     }
 
+    const disallowed = Object.keys(query);
+
+    if (params?.extraParams && Object.keys(params.extraParams).filter((key) => disallowed.includes(key)).length > 0) {
+      throw new Error(`The following extraParams are disallowed: '${disallowed.join("', '")}'`);
+    }
+
+    query = {...query, ...params?.extraParams};
+
+
     return authorizationEndpoint + '?' + generateQueryString(query);
 
   }
diff --git a/src/messages.ts b/src/messages.ts
index ee04295..3d4636c 100644
--- a/src/messages.ts
+++ b/src/messages.ts
@@ -47,6 +47,7 @@ export type AuthorizationQueryParams = {
   scope?: string;
   code_challenge_method?: 'plain' | 'S256';
   code_challenge?: string;
+  [key: string]: string | undefined;
 }
 
 /**


Test patch:
diff --git a/test/authorization-code.ts b/test/authorization-code.ts
index ddb7808..4477e60 100644
--- a/test/authorization-code.ts
+++ b/test/authorization-code.ts
@@ -35,6 +35,75 @@ describe('authorization-code', () => {
         })
       ).to.equal(server.url + '/authorize?' + params.toString());
 
+    });
+    it('should support extraparams', async() => {
+
+      const server = testServer();
+      const client = new OAuth2Client({
+        server: server.url,
+        authorizationEndpoint: '/authorize',
+        clientId: 'test-client-id',
+      });
+
+      const redirectUri = 'http://my-app.example/redirect';
+
+      const params = new URLSearchParams({
+        client_id: 'test-client-id',
+        response_type: 'code',
+        redirect_uri: redirectUri,
+        scope: 'a b',
+        foo: 'bar',
+      });
+
+      expect(
+        await client.authorizationCode.getAuthorizeUri({
+          redirectUri,
+          scope: ['a', 'b'],
+          extraParams: {
+            foo: 'bar'
+          }
+        })
+      ).to.equal(server.url + '/authorize?' + params.toString());
+
+    });
+    it('should throw error when user rewrote params by extraparams', async() => {
+
+      const server = testServer();
+      const client = new OAuth2Client({
+        server: server.url,
+        authorizationEndpoint: '/authorize',
+        clientId: 'test-client-id',
+      });
+
+      const redirectUri = 'http://my-app.example/redirect';
+
+      const params  = {
+        redirectUri,
+        scope: ['a', 'b'],
+        state: 'some-state'
+
+      };
+
+      const extraParams = {
+        foo: 'bar',
+        scope: 'accidentally rewrote core parameter'
+      };
+
+      try {
+        await client.authorizationCode.getAuthorizeUri({
+          ...params,
+          extraParams
+        });
+      } catch (error: any) {
+        expect(error.message).to.equal(
+          'The following extraParams are disallowed: \'client_id\', \'response_type\', \'redirect_uri\', ' +
+             '\'code_challenge_method\', \'code_challenge\', \'state\', \'scope\''
+        );
+        return;
+      }
+
+      expect.fail('Should have thrown');
+
     });
     it('should support PKCE', async() => {