strapi-plugin-oidc 1.5.0 → 1.5.2

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/README.md CHANGED
@@ -39,26 +39,12 @@ module.exports = ({ env }) => ({
39
39
  // Optional — defaults shown
40
40
  OIDC_SCOPE: 'openid profile email',
41
41
  OIDC_GRANT_TYPE: 'authorization_code',
42
- OIDC_FAMILY_NAME_FIELD: 'family_name', // claim name for the user's last name
43
- OIDC_GIVEN_NAME_FIELD: 'given_name', // claim name for the user's first name
42
+ OIDC_FAMILY_NAME_FIELD: 'family_name',
43
+ OIDC_GIVEN_NAME_FIELD: 'given_name',
44
+ OIDC_END_SESSION_ENDPOINT: '', // Provider end-session URL; omit to redirect to Strapi login
44
45
  OIDC_SSO_BUTTON_TEXT: 'Login via SSO',
45
46
  OIDC_ENFORCE: null, // null = use Admin UI toggle; true/false = override in config
46
47
  REMEMBER_ME: false, // Persist session across browser restarts
47
-
48
- // Optional — RP-Initiated Logout (GET /strapi-plugin-oidc/logout)
49
- // Set OIDC_END_SESSION_ENDPOINT to redirect OIDC sessions to your provider's
50
- // end-session page on logout. Without it, logout only clears the local session.
51
- // Find this URL in your provider's /.well-known/openid-configuration as end_session_endpoint.
52
- OIDC_END_SESSION_ENDPOINT: env('OIDC_END_SESSION_ENDPOINT', ''),
53
- OIDC_POST_LOGOUT_REDIRECT_URI: env('OIDC_POST_LOGOUT_REDIRECT_URI', ''), // where to land after the provider logs the user out
54
-
55
- // Optional — Backchannel Logout (POST /strapi-plugin-oidc/logout)
56
- // When configured, your provider can notify Strapi when a user logs out elsewhere
57
- // (e.g. from another app or directly from the provider UI), revoking their Strapi session.
58
- // Set the logout URI in your provider to: https://your-strapi.com/strapi-plugin-oidc/logout
59
- // Both values are required together — find them in your provider's /.well-known/openid-configuration.
60
- OIDC_ISSUER: env('OIDC_ISSUER', ''), // validates the iss claim; required for backchannel logout
61
- OIDC_JWKS_URI: env('OIDC_JWKS_URI', ''), // verifies logout token signatures; required for backchannel logout
62
48
  },
63
49
  },
64
50
  });
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
3
  const node_crypto = require("node:crypto");
4
- const jose = require("jose");
5
4
  const pkceChallenge = require("pkce-challenge");
6
5
  const strapiUtils = require("@strapi/utils");
7
6
  const generator = require("generate-password");
@@ -170,12 +169,6 @@ const config = {
170
169
  OIDC_FAMILY_NAME_FIELD: "family_name",
171
170
  OIDC_GIVEN_NAME_FIELD: "given_name",
172
171
  OIDC_END_SESSION_ENDPOINT: "",
173
- OIDC_POST_LOGOUT_REDIRECT_URI: "",
174
- // Where to land after the provider has logged the user out (RP-Initiated Logout)
175
- OIDC_ISSUER: "",
176
- // Provider issuer URL — used to validate iss claim in backchannel logout tokens
177
- OIDC_JWKS_URI: "",
178
- // Provider JWKS endpoint — required for backchannel logout token signature verification
179
172
  OIDC_SSO_BUTTON_TEXT: "Login via SSO",
180
173
  OIDC_ENFORCE: null
181
174
  // null = use DB setting; true/false = override DB (useful for lockout recovery)
@@ -228,9 +221,7 @@ function getExpiredCookieOptions(strapi2, ctx) {
228
221
  function clearAuthCookies(strapi2, ctx) {
229
222
  const options2 = getExpiredCookieOptions(strapi2, ctx);
230
223
  ctx.cookies.set("strapi_admin_refresh", "", options2);
231
- const rootPathOptions = { ...options2, path: "/" };
232
- ctx.cookies.set("oidc_authenticated", "", rootPathOptions);
233
- ctx.cookies.set("oidc_id_token", "", rootPathOptions);
224
+ ctx.cookies.set("oidc_authenticated", "", { ...options2, path: "/" });
234
225
  }
235
226
  const REQUIRED_CONFIG_KEYS = [
236
227
  "OIDC_CLIENT_ID",
@@ -312,8 +303,7 @@ async function exchangeTokenAndFetchUserInfo(config2, params, expectedNonce) {
312
303
  if (!userResponse.ok) {
313
304
  throw new Error("Failed to fetch user info");
314
305
  }
315
- const userInfo = await userResponse.json();
316
- return { userInfo, idToken: tokenData.id_token };
306
+ return userResponse.json();
317
307
  }
318
308
  async function registerNewUser(userService, oauthService2, roleService2, email, userResponseData, whitelistUser, config2, ctx) {
319
309
  let roles2 = [];
@@ -379,11 +369,7 @@ async function oidcSignInCallback(ctx) {
379
369
  params.append("grant_type", config2.OIDC_GRANT_TYPE);
380
370
  params.append("code_verifier", codeVerifier ?? "");
381
371
  try {
382
- const { userInfo: userResponseData, idToken } = await exchangeTokenAndFetchUserInfo(
383
- config2,
384
- params,
385
- oidcNonce ?? ""
386
- );
372
+ const userResponseData = await exchangeTokenAndFetchUserInfo(config2, params, oidcNonce ?? "");
387
373
  const { activateUser, jwtToken } = await handleUserAuthentication(
388
374
  userService,
389
375
  oauthService2,
@@ -393,15 +379,6 @@ async function oidcSignInCallback(ctx) {
393
379
  config2,
394
380
  ctx
395
381
  );
396
- if (idToken) {
397
- const isProduction = strapi.config.get("environment") === "production";
398
- ctx.cookies.set("oidc_id_token", idToken, {
399
- httpOnly: true,
400
- secure: isProduction && ctx.request.secure,
401
- path: "/",
402
- sameSite: "lax"
403
- });
404
- }
405
382
  const nonce = node_crypto.randomUUID();
406
383
  const html = oauthService2.renderSignUpSuccess(jwtToken, activateUser, nonce);
407
384
  ctx.set("Content-Security-Policy", `script-src 'nonce-${nonce}'`);
@@ -415,89 +392,18 @@ async function logout(ctx) {
415
392
  const config2 = strapi.config.get("plugin::strapi-plugin-oidc");
416
393
  const logoutUrl = config2.OIDC_END_SESSION_ENDPOINT;
417
394
  const isOidcSession = !!ctx.cookies.get("oidc_authenticated");
418
- const idToken = ctx.cookies.get("oidc_id_token");
419
395
  clearAuthCookies(strapi, ctx);
420
396
  if (logoutUrl && isOidcSession) {
421
- const url = new URL(logoutUrl);
422
- if (idToken) url.searchParams.set("id_token_hint", idToken);
423
- if (config2.OIDC_POST_LOGOUT_REDIRECT_URI) {
424
- url.searchParams.set("post_logout_redirect_uri", config2.OIDC_POST_LOGOUT_REDIRECT_URI);
425
- }
426
- ctx.redirect(url.toString());
397
+ ctx.redirect(logoutUrl);
427
398
  } else {
428
399
  const adminPanelUrl = strapi.config.get("admin.url", "/admin");
429
400
  ctx.redirect(`${adminPanelUrl}/auth/login`);
430
401
  }
431
402
  }
432
- const jwksCache = /* @__PURE__ */ new Map();
433
- function getJWKS(uri) {
434
- const cached = jwksCache.get(uri);
435
- if (cached) return cached;
436
- const jwks = jose.createRemoteJWKSet(new URL(uri));
437
- jwksCache.set(uri, jwks);
438
- return jwks;
439
- }
440
- async function backchannelLogout(ctx) {
441
- const config2 = strapi.config.get("plugin::strapi-plugin-oidc");
442
- const logoutToken = ctx.request.body?.logout_token;
443
- if (!logoutToken) {
444
- ctx.status = 400;
445
- ctx.body = { error: "Missing logout_token" };
446
- return;
447
- }
448
- if (!config2.OIDC_JWKS_URI || !config2.OIDC_ISSUER) {
449
- ctx.status = 501;
450
- ctx.body = {
451
- error: "OIDC_JWKS_URI and OIDC_ISSUER must both be configured to enable backchannel logout"
452
- };
453
- return;
454
- }
455
- try {
456
- const JWKS = getJWKS(config2.OIDC_JWKS_URI);
457
- const verifyOptions = {
458
- issuer: config2.OIDC_ISSUER,
459
- audience: config2.OIDC_CLIENT_ID || void 0
460
- };
461
- const { payload } = await jose.jwtVerify(logoutToken, JWKS, verifyOptions);
462
- if ("nonce" in payload) {
463
- ctx.status = 400;
464
- ctx.body = { error: "logout_token must not contain nonce" };
465
- return;
466
- }
467
- const events = payload.events;
468
- if (!events?.["http://schemas.openid.net/event/backchannel-logout"]) {
469
- ctx.status = 400;
470
- ctx.body = { error: "logout_token missing backchannel-logout event" };
471
- return;
472
- }
473
- if (!payload.sub && !("sid" in payload)) {
474
- ctx.status = 400;
475
- ctx.body = { error: "logout_token must contain sub or sid" };
476
- return;
477
- }
478
- if (payload.sub) {
479
- const userService = strapi.service("admin::user");
480
- const user = await userService.findOneByEmail(payload.sub);
481
- if (user) {
482
- const sessionManager = strapi.sessionManager;
483
- if (sessionManager) {
484
- await sessionManager("admin").invalidateRefreshToken(String(user.id));
485
- }
486
- }
487
- }
488
- ctx.status = 200;
489
- ctx.body = "";
490
- } catch (e) {
491
- strapi.log.error("Backchannel logout failed:", e);
492
- ctx.status = 400;
493
- ctx.body = { error: "Invalid logout_token" };
494
- }
495
- }
496
403
  const oidc = {
497
404
  oidcSignIn,
498
405
  oidcSignInCallback,
499
- logout,
500
- backchannelLogout
406
+ logout
501
407
  };
502
408
  async function find(ctx) {
503
409
  const roleService2 = strapi.plugin("strapi-plugin-oidc").service("role");
@@ -754,12 +660,6 @@ const routes = {
754
660
  handler: "oidc.logout",
755
661
  config: { auth: false }
756
662
  },
757
- {
758
- method: "POST",
759
- path: "/logout",
760
- handler: "oidc.backchannelLogout",
761
- config: { auth: false }
762
- },
763
663
  {
764
664
  method: "GET",
765
665
  path: "/whitelist",
@@ -1,5 +1,4 @@
1
1
  import { randomUUID, randomBytes } from "node:crypto";
2
- import { jwtVerify, createRemoteJWKSet } from "jose";
3
2
  import pkceChallenge from "pkce-challenge";
4
3
  import strapiUtils from "@strapi/utils";
5
4
  import generator from "generate-password";
@@ -164,12 +163,6 @@ const config = {
164
163
  OIDC_FAMILY_NAME_FIELD: "family_name",
165
164
  OIDC_GIVEN_NAME_FIELD: "given_name",
166
165
  OIDC_END_SESSION_ENDPOINT: "",
167
- OIDC_POST_LOGOUT_REDIRECT_URI: "",
168
- // Where to land after the provider has logged the user out (RP-Initiated Logout)
169
- OIDC_ISSUER: "",
170
- // Provider issuer URL — used to validate iss claim in backchannel logout tokens
171
- OIDC_JWKS_URI: "",
172
- // Provider JWKS endpoint — required for backchannel logout token signature verification
173
166
  OIDC_SSO_BUTTON_TEXT: "Login via SSO",
174
167
  OIDC_ENFORCE: null
175
168
  // null = use DB setting; true/false = override DB (useful for lockout recovery)
@@ -222,9 +215,7 @@ function getExpiredCookieOptions(strapi2, ctx) {
222
215
  function clearAuthCookies(strapi2, ctx) {
223
216
  const options2 = getExpiredCookieOptions(strapi2, ctx);
224
217
  ctx.cookies.set("strapi_admin_refresh", "", options2);
225
- const rootPathOptions = { ...options2, path: "/" };
226
- ctx.cookies.set("oidc_authenticated", "", rootPathOptions);
227
- ctx.cookies.set("oidc_id_token", "", rootPathOptions);
218
+ ctx.cookies.set("oidc_authenticated", "", { ...options2, path: "/" });
228
219
  }
229
220
  const REQUIRED_CONFIG_KEYS = [
230
221
  "OIDC_CLIENT_ID",
@@ -306,8 +297,7 @@ async function exchangeTokenAndFetchUserInfo(config2, params, expectedNonce) {
306
297
  if (!userResponse.ok) {
307
298
  throw new Error("Failed to fetch user info");
308
299
  }
309
- const userInfo = await userResponse.json();
310
- return { userInfo, idToken: tokenData.id_token };
300
+ return userResponse.json();
311
301
  }
312
302
  async function registerNewUser(userService, oauthService2, roleService2, email, userResponseData, whitelistUser, config2, ctx) {
313
303
  let roles2 = [];
@@ -373,11 +363,7 @@ async function oidcSignInCallback(ctx) {
373
363
  params.append("grant_type", config2.OIDC_GRANT_TYPE);
374
364
  params.append("code_verifier", codeVerifier ?? "");
375
365
  try {
376
- const { userInfo: userResponseData, idToken } = await exchangeTokenAndFetchUserInfo(
377
- config2,
378
- params,
379
- oidcNonce ?? ""
380
- );
366
+ const userResponseData = await exchangeTokenAndFetchUserInfo(config2, params, oidcNonce ?? "");
381
367
  const { activateUser, jwtToken } = await handleUserAuthentication(
382
368
  userService,
383
369
  oauthService2,
@@ -387,15 +373,6 @@ async function oidcSignInCallback(ctx) {
387
373
  config2,
388
374
  ctx
389
375
  );
390
- if (idToken) {
391
- const isProduction = strapi.config.get("environment") === "production";
392
- ctx.cookies.set("oidc_id_token", idToken, {
393
- httpOnly: true,
394
- secure: isProduction && ctx.request.secure,
395
- path: "/",
396
- sameSite: "lax"
397
- });
398
- }
399
376
  const nonce = randomUUID();
400
377
  const html = oauthService2.renderSignUpSuccess(jwtToken, activateUser, nonce);
401
378
  ctx.set("Content-Security-Policy", `script-src 'nonce-${nonce}'`);
@@ -409,89 +386,18 @@ async function logout(ctx) {
409
386
  const config2 = strapi.config.get("plugin::strapi-plugin-oidc");
410
387
  const logoutUrl = config2.OIDC_END_SESSION_ENDPOINT;
411
388
  const isOidcSession = !!ctx.cookies.get("oidc_authenticated");
412
- const idToken = ctx.cookies.get("oidc_id_token");
413
389
  clearAuthCookies(strapi, ctx);
414
390
  if (logoutUrl && isOidcSession) {
415
- const url = new URL(logoutUrl);
416
- if (idToken) url.searchParams.set("id_token_hint", idToken);
417
- if (config2.OIDC_POST_LOGOUT_REDIRECT_URI) {
418
- url.searchParams.set("post_logout_redirect_uri", config2.OIDC_POST_LOGOUT_REDIRECT_URI);
419
- }
420
- ctx.redirect(url.toString());
391
+ ctx.redirect(logoutUrl);
421
392
  } else {
422
393
  const adminPanelUrl = strapi.config.get("admin.url", "/admin");
423
394
  ctx.redirect(`${adminPanelUrl}/auth/login`);
424
395
  }
425
396
  }
426
- const jwksCache = /* @__PURE__ */ new Map();
427
- function getJWKS(uri) {
428
- const cached = jwksCache.get(uri);
429
- if (cached) return cached;
430
- const jwks = createRemoteJWKSet(new URL(uri));
431
- jwksCache.set(uri, jwks);
432
- return jwks;
433
- }
434
- async function backchannelLogout(ctx) {
435
- const config2 = strapi.config.get("plugin::strapi-plugin-oidc");
436
- const logoutToken = ctx.request.body?.logout_token;
437
- if (!logoutToken) {
438
- ctx.status = 400;
439
- ctx.body = { error: "Missing logout_token" };
440
- return;
441
- }
442
- if (!config2.OIDC_JWKS_URI || !config2.OIDC_ISSUER) {
443
- ctx.status = 501;
444
- ctx.body = {
445
- error: "OIDC_JWKS_URI and OIDC_ISSUER must both be configured to enable backchannel logout"
446
- };
447
- return;
448
- }
449
- try {
450
- const JWKS = getJWKS(config2.OIDC_JWKS_URI);
451
- const verifyOptions = {
452
- issuer: config2.OIDC_ISSUER,
453
- audience: config2.OIDC_CLIENT_ID || void 0
454
- };
455
- const { payload } = await jwtVerify(logoutToken, JWKS, verifyOptions);
456
- if ("nonce" in payload) {
457
- ctx.status = 400;
458
- ctx.body = { error: "logout_token must not contain nonce" };
459
- return;
460
- }
461
- const events = payload.events;
462
- if (!events?.["http://schemas.openid.net/event/backchannel-logout"]) {
463
- ctx.status = 400;
464
- ctx.body = { error: "logout_token missing backchannel-logout event" };
465
- return;
466
- }
467
- if (!payload.sub && !("sid" in payload)) {
468
- ctx.status = 400;
469
- ctx.body = { error: "logout_token must contain sub or sid" };
470
- return;
471
- }
472
- if (payload.sub) {
473
- const userService = strapi.service("admin::user");
474
- const user = await userService.findOneByEmail(payload.sub);
475
- if (user) {
476
- const sessionManager = strapi.sessionManager;
477
- if (sessionManager) {
478
- await sessionManager("admin").invalidateRefreshToken(String(user.id));
479
- }
480
- }
481
- }
482
- ctx.status = 200;
483
- ctx.body = "";
484
- } catch (e) {
485
- strapi.log.error("Backchannel logout failed:", e);
486
- ctx.status = 400;
487
- ctx.body = { error: "Invalid logout_token" };
488
- }
489
- }
490
397
  const oidc = {
491
398
  oidcSignIn,
492
399
  oidcSignInCallback,
493
- logout,
494
- backchannelLogout
400
+ logout
495
401
  };
496
402
  async function find(ctx) {
497
403
  const roleService2 = strapi.plugin("strapi-plugin-oidc").service("role");
@@ -748,12 +654,6 @@ const routes = {
748
654
  handler: "oidc.logout",
749
655
  config: { auth: false }
750
656
  },
751
- {
752
- method: "POST",
753
- path: "/logout",
754
- handler: "oidc.backchannelLogout",
755
- config: { auth: false }
756
- },
757
657
  {
758
658
  method: "GET",
759
659
  path: "/whitelist",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-plugin-oidc",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "description": "A Strapi plugin that provides OpenID Connect (OIDC) authentication functionality for the Strapi Admin Panel.",
5
5
  "strapi": {
6
6
  "displayName": "OIDC Plugin",
@@ -50,7 +50,6 @@
50
50
  "@strapi/icons": "^2.2.0",
51
51
  "@strapi/utils": "^5.41.1",
52
52
  "generate-password": "^1.7.1",
53
- "jose": "^6.2.2",
54
53
  "pkce-challenge": "^6.0.0",
55
54
  "react-intl": "^6.8.9"
56
55
  },
@@ -82,8 +81,6 @@
82
81
  "@eslint/eslintrc": "^3.3.5",
83
82
  "@eslint/js": "^10.0.1",
84
83
  "@strapi/sdk-plugin": "^6.0.1",
85
- "@strapi/types": "^5.41.1",
86
- "@types/koa": "^2.16.4",
87
84
  "@types/node": "^25.5.2",
88
85
  "@types/supertest": "^7.2.0",
89
86
  "@vitest/coverage-v8": "^4.1.2",