@flink-app/oidc-plugin 2.0.0-alpha.114 → 2.0.0-alpha.115

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # @flink-app/oidc-plugin
2
2
 
3
+ ## 2.0.0-alpha.115
4
+
5
+ ### Patch Changes
6
+
7
+ - 12fe6f5: fix: forward the RFC 9207 `iss` authorization response parameter to openid-client
8
+
9
+ IdPs that advertise `authorization_response_iss_parameter_supported` in their
10
+ discovery document (Google does) return an `iss` parameter on the authorization
11
+ redirect. The callback handler destructured only `code`, `state`, `error`,
12
+ `error_description` and `response_type` from the query, and `exchangeCodeForToken`
13
+ rebuilt the params object as `{ code, state }` — so `iss` was dropped before it
14
+ reached `client.callback()`.
15
+
16
+ openid-client then rejected every callback from such an IdP with
17
+ `RPError: iss missing from the response`, failing the login client-side before the
18
+ token endpoint was ever contacted. This made Google unusable as an OIDC provider.
19
+
20
+ `iss` is now read from the callback query and threaded through to openid-client,
21
+ which validates it against the discovered issuer (the mix-up attack defence RFC 9207
22
+ is there to provide).
23
+
24
+ The parameter is spread conditionally rather than always set, because openid-client
25
+ keys its check on `'iss' in params` — an explicit `iss: undefined` is read as a
26
+ mismatch and would break IdPs that do not send the parameter at all (Entra ID,
27
+ Okta, Auth0 and Keycloak do not advertise it by default).
28
+
29
+ - @flink-app/flink@2.0.0-alpha.115
30
+ - @flink-app/jwt-auth-plugin@2.0.0-alpha.115
31
+
3
32
  ## 2.0.0-alpha.114
4
33
 
5
34
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"CallbackOidc.d.ts","sourceRoot":"","sources":["../../src/handlers/CallbackOidc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,UAAU,EAAc,UAAU,EAAwC,MAAM,kBAAkB,CAAC;AAC5G,OAAO,eAAe,MAAM,4BAA4B,CAAC;AAOzD;;GAEG;AACH,UAAU,UAAU;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,eAAO,MAAM,KAAK,EAAE,UAGnB,CAAC;AAEF;;;;;;GAMG;AACH,QAAA,MAAM,YAAY,EAAE,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,eAAe,CA4QnE,CAAC;AAEF,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"CallbackOidc.d.ts","sourceRoot":"","sources":["../../src/handlers/CallbackOidc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,UAAU,EAAc,UAAU,EAAwC,MAAM,kBAAkB,CAAC;AAC5G,OAAO,eAAe,MAAM,4BAA4B,CAAC;AAOzD;;GAEG;AACH,UAAU,UAAU;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,eAAO,MAAM,KAAK,EAAE,UAGnB,CAAC;AAEF;;;;;;GAMG;AACH,QAAA,MAAM,YAAY,EAAE,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,eAAe,CA6QnE,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -38,7 +38,7 @@ exports.Route = {
38
38
  */
39
39
  const CallbackOidc = async ({ ctx, req }) => {
40
40
  const { provider } = req.params;
41
- const { code, state, error: oidcError, error_description, response_type } = req.query;
41
+ const { code, state, iss, error: oidcError, error_description, response_type } = req.query;
42
42
  // Track session outside try/catch so redirectUri and metadata are available in error handling
43
43
  let sessionRedirectUri;
44
44
  let sessionMetadata = {};
@@ -119,6 +119,7 @@ const CallbackOidc = async ({ ctx, req }) => {
119
119
  codeVerifier: session.codeVerifier,
120
120
  state: session.state,
121
121
  nonce: session.nonce,
122
+ iss,
122
123
  });
123
124
  log_1.oidcLog.debug(`Callback: token exchange successful`, `sub="${tokenSet.claims.sub}"`, `iss="${tokenSet.claims.iss}"`, `email="${tokenSet.claims.email || "(none)"}"`, `hasRefreshToken=${!!tokenSet.refreshToken}`, `expiresIn=${tokenSet.expiresIn ?? "(none)"}s`);
124
125
  // Build user profile from ID token and (optionally) UserInfo
@@ -52,6 +52,7 @@ export declare class OidcProvider {
52
52
  codeVerifier: string;
53
53
  state: string;
54
54
  nonce: string;
55
+ iss?: string;
55
56
  }): Promise<OidcTokenSet>;
56
57
  /**
57
58
  * Get user profile from UserInfo endpoint
@@ -1 +1 @@
1
- {"version":3,"file":"OidcProvider.d.ts","sourceRoot":"","sources":["../../src/providers/OidcProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwD,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACvG,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,WAAW,MAAM,wBAAwB,CAAC;AACjD,OAAO,YAAY,MAAM,yBAAyB,CAAC;AAKnD;;;;;;;;;;GAUG;AACH,qBAAa,YAAY;IACrB,SAAgB,MAAM,EAAE,kBAAkB,CAAC;IAC3C,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,WAAW,CAAkB;gBAEzB,MAAM,EAAE,kBAAkB;IAItC;;;;;;;OAOG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAsEjC;;;;;OAKG;IACG,mBAAmB,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAiB1G;;;;;;;OAOG;IACG,oBAAoB,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,CAAC;IAoD/H;;;;;;;;OAQG;IACG,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAsBjE;;;;;;;;;OASG;IACG,YAAY,CAAC,QAAQ,EAAE,YAAY,EAAE,eAAe,GAAE,OAAc,GAAG,OAAO,CAAC,WAAW,CAAC;IAgCjG;;;;OAIG;YACW,iBAAiB;IAU/B;;;;OAIG;IACH,iBAAiB,IAAI,GAAG;CAM3B"}
1
+ {"version":3,"file":"OidcProvider.d.ts","sourceRoot":"","sources":["../../src/providers/OidcProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwD,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACvG,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,WAAW,MAAM,wBAAwB,CAAC;AACjD,OAAO,YAAY,MAAM,yBAAyB,CAAC;AAKnD;;;;;;;;;;GAUG;AACH,qBAAa,YAAY;IACrB,SAAgB,MAAM,EAAE,kBAAkB,CAAC;IAC3C,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,WAAW,CAAkB;gBAEzB,MAAM,EAAE,kBAAkB;IAItC;;;;;;;OAOG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAsEjC;;;;;OAKG;IACG,mBAAmB,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAiB1G;;;;;;;OAOG;IACG,oBAAoB,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,CAAC;IA4D7I;;;;;;;;OAQG;IACG,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAsBjE;;;;;;;;;OASG;IACG,YAAY,CAAC,QAAQ,EAAE,YAAY,EAAE,eAAe,GAAE,OAAc,GAAG,OAAO,CAAC,WAAW,CAAC;IAgCjG;;;;OAIG;YACW,iBAAiB;IAU/B;;;;OAIG;IACH,iBAAiB,IAAI,GAAG;CAM3B"}
@@ -116,6 +116,14 @@ class OidcProvider {
116
116
  const tokenSet = await this.client.callback(this.config.callbackUrl, {
117
117
  code: params.code,
118
118
  state: params.state,
119
+ // RFC 9207: IdPs that advertise
120
+ // `authorization_response_iss_parameter_supported` (Google does)
121
+ // return `iss` on the authorization redirect, and openid-client
122
+ // rejects the callback with "iss missing from the response" unless
123
+ // it is forwarded here. Spread conditionally — openid-client keys
124
+ // its check on `'iss' in params`, so an explicit `iss: undefined`
125
+ // would be read as a mismatch and break IdPs that never send it.
126
+ ...(params.iss ? { iss: params.iss } : {}),
119
127
  }, {
120
128
  code_verifier: params.codeVerifier,
121
129
  state: params.state,
@@ -14,6 +14,12 @@ type CallbackRequest = Record<string, string | string[]> & {
14
14
  * Must match the state stored in the session
15
15
  */
16
16
  state?: string;
17
+ /**
18
+ * Issuer identifier returned on the authorization response (RFC 9207)
19
+ * Sent by IdPs advertising `authorization_response_iss_parameter_supported`
20
+ * Forwarded to openid-client, which validates it against the discovered issuer
21
+ */
22
+ iss?: string;
17
23
  /**
18
24
  * Error code from the IdP (if authorization failed)
19
25
  * e.g., "access_denied" if user cancelled
@@ -1 +1 @@
1
- {"version":3,"file":"CallbackRequest.d.ts","sourceRoot":"","sources":["../../src/schemas/CallbackRequest.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,KAAK,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG;IACvD;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CAE1B,CAAC;AAEF,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"CallbackRequest.d.ts","sourceRoot":"","sources":["../../src/schemas/CallbackRequest.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,KAAK,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG;IACvD;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CAE1B,CAAC;AAEF,eAAe,eAAe,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flink-app/oidc-plugin",
3
- "version": "2.0.0-alpha.114",
3
+ "version": "2.0.0-alpha.115",
4
4
  "description": "Flink plugin for OIDC authentication with generic IdP support",
5
5
  "author": "joel@frost.se",
6
6
  "license": "MIT",
@@ -11,10 +11,10 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "openid-client": "^5.7.0",
14
- "@flink-app/jwt-auth-plugin": "2.0.0-alpha.114"
14
+ "@flink-app/jwt-auth-plugin": "2.0.0-alpha.115"
15
15
  },
16
16
  "peerDependencies": {
17
- "@flink-app/flink": ">=2.0.0-alpha.114",
17
+ "@flink-app/flink": ">=2.0.0-alpha.115",
18
18
  "mongodb": "^6.15.0"
19
19
  },
20
20
  "peerDependenciesMeta": {
@@ -27,9 +27,9 @@
27
27
  "@types/node": "22.13.10",
28
28
  "ts-node": "^10.9.2",
29
29
  "tsc-watch": "^4.2.9",
30
- "@flink-app/flink": "2.0.0-alpha.114",
31
- "@flink-app/jwt-auth-plugin": "2.0.0-alpha.114",
32
- "@flink-app/test-utils": "2.0.0-alpha.114"
30
+ "@flink-app/flink": "2.0.0-alpha.115",
31
+ "@flink-app/jwt-auth-plugin": "2.0.0-alpha.115",
32
+ "@flink-app/test-utils": "2.0.0-alpha.115"
33
33
  },
34
34
  "scripts": {
35
35
  "test": "jasmine-ts --config=./spec/support/jasmine.json",
@@ -47,7 +47,7 @@ export const Route: RouteProps = {
47
47
  */
48
48
  const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({ ctx, req }) => {
49
49
  const { provider } = req.params;
50
- const { code, state, error: oidcError, error_description, response_type } = req.query;
50
+ const { code, state, iss, error: oidcError, error_description, response_type } = req.query;
51
51
 
52
52
  // Track session outside try/catch so redirectUri and metadata are available in error handling
53
53
  let sessionRedirectUri: string | undefined;
@@ -147,6 +147,7 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
147
147
  codeVerifier: session.codeVerifier,
148
148
  state: session.state,
149
149
  nonce: session.nonce,
150
+ iss,
150
151
  });
151
152
 
152
153
  oidcLog.debug(
@@ -136,7 +136,7 @@ export class OidcProvider {
136
136
  * @param params - Token exchange parameters
137
137
  * @returns Token set with access token, ID token, and claims
138
138
  */
139
- async exchangeCodeForToken(params: { code: string; codeVerifier: string; state: string; nonce: string }): Promise<OidcTokenSet> {
139
+ async exchangeCodeForToken(params: { code: string; codeVerifier: string; state: string; nonce: string; iss?: string }): Promise<OidcTokenSet> {
140
140
  await this.ensureInitialized();
141
141
 
142
142
  try {
@@ -145,6 +145,14 @@ export class OidcProvider {
145
145
  {
146
146
  code: params.code,
147
147
  state: params.state,
148
+ // RFC 9207: IdPs that advertise
149
+ // `authorization_response_iss_parameter_supported` (Google does)
150
+ // return `iss` on the authorization redirect, and openid-client
151
+ // rejects the callback with "iss missing from the response" unless
152
+ // it is forwarded here. Spread conditionally — openid-client keys
153
+ // its check on `'iss' in params`, so an explicit `iss: undefined`
154
+ // would be read as a mismatch and break IdPs that never send it.
155
+ ...(params.iss ? { iss: params.iss } : {}),
148
156
  },
149
157
  {
150
158
  code_verifier: params.codeVerifier,
@@ -16,6 +16,13 @@ type CallbackRequest = Record<string, string | string[]> & {
16
16
  */
17
17
  state?: string;
18
18
 
19
+ /**
20
+ * Issuer identifier returned on the authorization response (RFC 9207)
21
+ * Sent by IdPs advertising `authorization_response_iss_parameter_supported`
22
+ * Forwarded to openid-client, which validates it against the discovered issuer
23
+ */
24
+ iss?: string;
25
+
19
26
  /**
20
27
  * Error code from the IdP (if authorization failed)
21
28
  * e.g., "access_denied" if user cancelled