strapi-plugin-oidc 1.10.2 → 1.10.4

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
@@ -35,10 +35,10 @@ module.exports = ({ env }) => ({
35
35
  enabled: true,
36
36
  config: {
37
37
  // Required
38
+ OIDC_PUBLIC_URL: env('PUBLIC_URL', 'https://strapi.example.com'), // origin only — we append /strapi-plugin-oidc/oidc/callback
38
39
  OIDC_ISSUER: env('OIDC_ISSUER'), // https://your-provider or https://your-provider/realms/your-realm
39
40
  OIDC_CLIENT_ID: env('OIDC_CLIENT_ID'),
40
41
  OIDC_CLIENT_SECRET: env('OIDC_CLIENT_SECRET'),
41
- OIDC_REDIRECT_URI: env('OIDC_REDIRECT_URI'), // https://your-strapi.com/strapi-plugin-oidc/oidc/callback
42
42
 
43
43
  // Optional — defaults shown
44
44
  OIDC_SCOPE: 'openid profile email', // space-separated scopes
@@ -59,6 +59,8 @@ module.exports = ({ env }) => ({
59
59
  });
60
60
  ```
61
61
 
62
+ `OIDC_PUBLIC_URL` is your Strapi instance's origin (e.g. `https://myapp.com`). The plugin appends `/strapi-plugin-oidc/oidc/callback` to form the full OIDC redirect URI. If unset, falls back to the `PUBLIC_URL` environment variable. Only provide the scheme + host + port — no trailing slash or path.
63
+
62
64
  `OIDC_ISSUER` is your provider's issuer URL (e.g. `https://auth.example.com` or `https://auth.example.com/realms/myrealm`). The plugin appends `/.well-known/openid-configuration` automatically if not present, and fetches the discovery document at startup to configure all endpoints, JWKS URI, and canonical issuer.
63
65
 
64
66
  ### Security features
@@ -98,17 +100,6 @@ Only headers that CDN/proxy vendors guarantee to strip from inbound client reque
98
100
 
99
101
  Navigate to `/strapi-plugin-oidc/oidc` to start the OIDC flow, or click the **Login via SSO** button injected into the Strapi login page.
100
102
 
101
- ## Skip Login Page
102
-
103
- Set `OIDC_SKIP_LOGIN_PAGE: true` to prevent users from ever seeing the Strapi admin login page. Unauthenticated requests are redirected directly to the OIDC provider. This is typically used alongside `OIDC_ENFORCE: true`.
104
-
105
- Two independent mechanisms cover both entry points:
106
-
107
- - **Server-side** — A Koa middleware intercepts unauthenticated GET requests to `/admin` and redirects to `/strapi-plugin-oidc/oidc` before the SPA is served. Excluded from this redirect: API routes (`/admin/login`, `/admin/logout`, `/admin/init`, etc.), static assets (`.js`, `.css`, `.png`, etc.), and POST requests.
108
- - **Client-side** — A DOM `MutationObserver` watches for React Router navigations to `/auth/login` (triggered by session expiry or 401 responses) and redirects before the login form renders.
109
-
110
- After logout without a provider `end_session_endpoint`, the fallback URL becomes `/strapi-plugin-oidc/oidc` instead of `/admin/auth/login`, ensuring the user lands on the provider's own page.
111
-
112
103
  ## Logout
113
104
 
114
105
  When the discovery document includes an `end_session_endpoint`, clicking logout redirects to the provider's end-session URL (RP-initiated logout). If the provider session has already expired, Strapi skips the redirect and goes straight to the login page.
@@ -212,30 +203,30 @@ Duplicate emails within the payload and emails already in the whitelist are sile
212
203
  ```bash
213
204
  # List
214
205
  curl -H "Authorization: Bearer <token>" \
215
- http://localhost:1337/api/strapi-plugin-oidc/whitelist
206
+ https://strapi.example.com/api/strapi-plugin-oidc/whitelist
216
207
 
217
208
  # Export
218
209
  curl -H "Authorization: Bearer <token>" \
219
- http://localhost:1337/api/strapi-plugin-oidc/whitelist/export \
210
+ https://strapi.example.com/api/strapi-plugin-oidc/whitelist/export \
220
211
  -o whitelist.json
221
212
 
222
213
  # Add
223
214
  curl -X POST -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
224
215
  -d '{"email": "user@example.com"}' \
225
- http://localhost:1337/api/strapi-plugin-oidc/whitelist
216
+ https://strapi.example.com/api/strapi-plugin-oidc/whitelist
226
217
 
227
218
  # Bulk import
228
219
  curl -X POST -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
229
220
  -d '{"users": [{"email": "a@example.com"}, {"email": "b@example.com"}]}' \
230
- http://localhost:1337/api/strapi-plugin-oidc/whitelist/import
221
+ https://strapi.example.com/api/strapi-plugin-oidc/whitelist/import
231
222
 
232
223
  # Delete one (by email)
233
224
  curl -X DELETE -H "Authorization: Bearer <token>" \
234
- "http://localhost:1337/api/strapi-plugin-oidc/whitelist/user%40example.com"
225
+ "https://strapi.example.com/api/strapi-plugin-oidc/whitelist/user%40example.com"
235
226
 
236
227
  # Delete all
237
228
  curl -X DELETE -H "Authorization: Bearer <token>" \
238
- http://localhost:1337/api/strapi-plugin-oidc/whitelist
229
+ https://strapi.example.com/api/strapi-plugin-oidc/whitelist
239
230
  ```
240
231
 
241
232
  ## Audit Log API
@@ -303,7 +294,7 @@ curl -H "Authorization: Bearer <token>" -G \
303
294
  --data-urlencode 'filters[action][$eq]=login_failure' \
304
295
  --data-urlencode 'filters[createdAt][$gte]=2026-04-08T00:00:00.000Z' \
305
296
  --data-urlencode 'filters[createdAt][$lt]=2026-04-09T00:00:00.000Z' \
306
- http://localhost:1337/api/strapi-plugin-oidc/audit-logs
297
+ https://strapi.example.com/api/strapi-plugin-oidc/audit-logs
307
298
  ```
308
299
 
309
300
  ### Recorded actions
@@ -330,11 +321,11 @@ Each event is also emitted on Strapi's internal eventHub as `strapi-plugin-oidc:
330
321
  ```bash
331
322
  # Paginated list
332
323
  curl -H "Authorization: Bearer <token>" \
333
- "http://localhost:1337/api/strapi-plugin-oidc/audit-logs?page=1&pageSize=50"
324
+ "https://strapi.example.com/api/strapi-plugin-oidc/audit-logs?page=1&pageSize=50"
334
325
 
335
326
  # NDJSON export
336
327
  curl -H "Authorization: Bearer <token>" \
337
- http://localhost:1337/api/strapi-plugin-oidc/audit-logs/export \
328
+ https://strapi.example.com/api/strapi-plugin-oidc/audit-logs/export \
338
329
  -o oidc-audit-log.ndjson
339
330
  ```
340
331
 
@@ -198,10 +198,25 @@ const AUDIT_LOG_DEFAULTS = {
198
198
  ADMIN_PAGE_SIZE: 10
199
199
  };
200
200
  const OIDC_SIGN_IN_PATH = "/strapi-plugin-oidc/oidc";
201
+ const AUTH_ROUTES = [
202
+ "login",
203
+ "register",
204
+ "register-admin",
205
+ "forgot-password",
206
+ "reset-password"
207
+ ];
208
+ const JWT_TOKEN_KEY = "jwtToken";
201
209
  const UI_DEFAULTS = {
202
210
  MIN_SPINNER_MS: 400
203
211
  };
204
212
  const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
213
+ function shouldRedirectToOidc(params) {
214
+ const isServerBounce = params.search.includes("oidc_redirect=1");
215
+ if (isServerBounce) return false;
216
+ const hasToken = params.localStorage.getItem(JWT_TOKEN_KEY) || params.cookies.split(";").some((c) => c.trim().startsWith(`${JWT_TOKEN_KEY}=`));
217
+ if (hasToken) return false;
218
+ return true;
219
+ }
205
220
  const name = pluginPkg.strapi.displayName;
206
221
  const index = {
207
222
  register(app) {
@@ -212,7 +227,7 @@ const index = {
212
227
  id: "settings.configuration",
213
228
  defaultMessage: "Configuration"
214
229
  },
215
- Component: () => Promise.resolve().then(() => require("./index-DN2ccKqO.js")),
230
+ Component: () => Promise.resolve().then(() => require("./index-DgrNKY9Y.js")),
216
231
  permissions: [{ action: PERMISSIONS.READ, subject: null }]
217
232
  };
218
233
  app.addSettingsLink(
@@ -232,10 +247,19 @@ const index = {
232
247
  });
233
248
  },
234
249
  bootstrap() {
235
- const isAuthRoute = (path) => /\/auth\/(login|register|forgot-password|reset-password)/.test(path);
236
- const isServerBounce = window.location.search.includes("oidc_redirect=1");
237
- if (!isServerBounce && isAuthRoute(window.location.pathname)) {
250
+ const authRouteNames = AUTH_ROUTES.filter((r) => r !== "register-admin");
251
+ const authRoutePattern = new RegExp(`/auth/(${authRouteNames.join("|")})`);
252
+ const isAuthRoute = (path) => authRoutePattern.test(path);
253
+ if (shouldRedirectToOidc({
254
+ search: window.location.search,
255
+ localStorage: window.localStorage,
256
+ cookies: document.cookie
257
+ })) {
258
+ document.documentElement.innerHTML = "";
238
259
  window.location.replace(OIDC_SIGN_IN_PATH);
260
+ setTimeout(() => {
261
+ window.location.href = OIDC_SIGN_IN_PATH;
262
+ }, 2e3);
239
263
  return;
240
264
  }
241
265
  const overlayContainer = document.createElement("div");
@@ -317,16 +341,16 @@ const index = {
317
341
  const applySettings = async () => {
318
342
  try {
319
343
  const response = await window.fetch("/strapi-plugin-oidc/settings/public");
320
- if (response.ok) {
321
- const data = await response.json();
322
- if (data.skipLoginPage) {
323
- startSkipLoginRedirect();
324
- return;
325
- }
326
- startLoginObserver(data.ssoButtonText || defaultButtonText, !!data.enforceOIDC);
327
- } else {
344
+ if (!response.ok) {
328
345
  startLoginObserver(defaultButtonText, false);
346
+ return;
347
+ }
348
+ const data = await response.json();
349
+ if (data.skipLoginPage) {
350
+ startSkipLoginRedirect();
351
+ return;
329
352
  }
353
+ startLoginObserver(data.ssoButtonText || defaultButtonText, !!data.enforceOIDC);
330
354
  } catch (error) {
331
355
  startLoginObserver(defaultButtonText, false);
332
356
  console.error("Failed to fetch OIDC settings:", error);
@@ -341,12 +365,12 @@ const index = {
341
365
  const isLogout = url?.endsWith("/admin/logout") && args[1]?.method?.toUpperCase() === "POST";
342
366
  if (isLogout) {
343
367
  window.dispatchEvent(new CustomEvent(LOGOUT_EVENT));
344
- window.localStorage.removeItem("jwtToken");
368
+ window.localStorage.removeItem(JWT_TOKEN_KEY);
345
369
  window.localStorage.removeItem("isLoggedIn");
346
- window.sessionStorage.removeItem("jwtToken");
370
+ window.sessionStorage.removeItem(JWT_TOKEN_KEY);
347
371
  window.sessionStorage.removeItem("isLoggedIn");
348
- document.cookie = "jwtToken=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/";
349
- document.cookie = "jwtToken=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/admin";
372
+ document.cookie = `${JWT_TOKEN_KEY}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/`;
373
+ document.cookie = `${JWT_TOKEN_KEY}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/admin`;
350
374
  originalFetch(...args).catch(() => {
351
375
  });
352
376
  window.location.href = "/strapi-plugin-oidc/logout";
@@ -195,10 +195,25 @@ const AUDIT_LOG_DEFAULTS = {
195
195
  ADMIN_PAGE_SIZE: 10
196
196
  };
197
197
  const OIDC_SIGN_IN_PATH = "/strapi-plugin-oidc/oidc";
198
+ const AUTH_ROUTES = [
199
+ "login",
200
+ "register",
201
+ "register-admin",
202
+ "forgot-password",
203
+ "reset-password"
204
+ ];
205
+ const JWT_TOKEN_KEY = "jwtToken";
198
206
  const UI_DEFAULTS = {
199
207
  MIN_SPINNER_MS: 400
200
208
  };
201
209
  const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
210
+ function shouldRedirectToOidc(params) {
211
+ const isServerBounce = params.search.includes("oidc_redirect=1");
212
+ if (isServerBounce) return false;
213
+ const hasToken = params.localStorage.getItem(JWT_TOKEN_KEY) || params.cookies.split(";").some((c) => c.trim().startsWith(`${JWT_TOKEN_KEY}=`));
214
+ if (hasToken) return false;
215
+ return true;
216
+ }
202
217
  const name = pluginPkg.strapi.displayName;
203
218
  const index = {
204
219
  register(app) {
@@ -209,7 +224,7 @@ const index = {
209
224
  id: "settings.configuration",
210
225
  defaultMessage: "Configuration"
211
226
  },
212
- Component: () => import("./index-Dn8QUbkK.mjs"),
227
+ Component: () => import("./index-SjMPr_u2.mjs"),
213
228
  permissions: [{ action: PERMISSIONS.READ, subject: null }]
214
229
  };
215
230
  app.addSettingsLink(
@@ -229,10 +244,19 @@ const index = {
229
244
  });
230
245
  },
231
246
  bootstrap() {
232
- const isAuthRoute = (path) => /\/auth\/(login|register|forgot-password|reset-password)/.test(path);
233
- const isServerBounce = window.location.search.includes("oidc_redirect=1");
234
- if (!isServerBounce && isAuthRoute(window.location.pathname)) {
247
+ const authRouteNames = AUTH_ROUTES.filter((r) => r !== "register-admin");
248
+ const authRoutePattern = new RegExp(`/auth/(${authRouteNames.join("|")})`);
249
+ const isAuthRoute = (path) => authRoutePattern.test(path);
250
+ if (shouldRedirectToOidc({
251
+ search: window.location.search,
252
+ localStorage: window.localStorage,
253
+ cookies: document.cookie
254
+ })) {
255
+ document.documentElement.innerHTML = "";
235
256
  window.location.replace(OIDC_SIGN_IN_PATH);
257
+ setTimeout(() => {
258
+ window.location.href = OIDC_SIGN_IN_PATH;
259
+ }, 2e3);
236
260
  return;
237
261
  }
238
262
  const overlayContainer = document.createElement("div");
@@ -314,16 +338,16 @@ const index = {
314
338
  const applySettings = async () => {
315
339
  try {
316
340
  const response = await window.fetch("/strapi-plugin-oidc/settings/public");
317
- if (response.ok) {
318
- const data = await response.json();
319
- if (data.skipLoginPage) {
320
- startSkipLoginRedirect();
321
- return;
322
- }
323
- startLoginObserver(data.ssoButtonText || defaultButtonText, !!data.enforceOIDC);
324
- } else {
341
+ if (!response.ok) {
325
342
  startLoginObserver(defaultButtonText, false);
343
+ return;
344
+ }
345
+ const data = await response.json();
346
+ if (data.skipLoginPage) {
347
+ startSkipLoginRedirect();
348
+ return;
326
349
  }
350
+ startLoginObserver(data.ssoButtonText || defaultButtonText, !!data.enforceOIDC);
327
351
  } catch (error) {
328
352
  startLoginObserver(defaultButtonText, false);
329
353
  console.error("Failed to fetch OIDC settings:", error);
@@ -338,12 +362,12 @@ const index = {
338
362
  const isLogout = url?.endsWith("/admin/logout") && args[1]?.method?.toUpperCase() === "POST";
339
363
  if (isLogout) {
340
364
  window.dispatchEvent(new CustomEvent(LOGOUT_EVENT));
341
- window.localStorage.removeItem("jwtToken");
365
+ window.localStorage.removeItem(JWT_TOKEN_KEY);
342
366
  window.localStorage.removeItem("isLoggedIn");
343
- window.sessionStorage.removeItem("jwtToken");
367
+ window.sessionStorage.removeItem(JWT_TOKEN_KEY);
344
368
  window.sessionStorage.removeItem("isLoggedIn");
345
- document.cookie = "jwtToken=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/";
346
- document.cookie = "jwtToken=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/admin";
369
+ document.cookie = `${JWT_TOKEN_KEY}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/`;
370
+ document.cookie = `${JWT_TOKEN_KEY}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/admin`;
347
371
  originalFetch(...args).catch(() => {
348
372
  });
349
373
  window.location.href = "/strapi-plugin-oidc/logout";
@@ -7,7 +7,7 @@ const React = require("react");
7
7
  const designSystem = require("@strapi/design-system");
8
8
  const icons = require("@strapi/icons");
9
9
  const reactIntl = require("react-intl");
10
- const index = require("./index-B-sTTO3a.js");
10
+ const index = require("./index-COhAwRD-.js");
11
11
  const styled = require("styled-components");
12
12
  const lucideReact = require("lucide-react");
13
13
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
@@ -5,7 +5,7 @@ import { useState, useRef, useId, useEffect, useCallback, useReducer, useMemo, m
5
5
  import { Typography, Flex, Box, MultiSelect, MultiSelectOption, Button, Dialog, Table, Pagination, PreviousLink, NextLink, PageLink, Field, Divider, Thead, Tr, Th, Tbody, Td, IconButton, Loader, Tooltip, Alert } from "@strapi/design-system";
6
6
  import { Cross, WarningCircle, Plus, Download, Upload, Trash, Calendar, Mail, Information } from "@strapi/icons";
7
7
  import { useIntl } from "react-intl";
8
- import { g as getTrad, E as EMAIL_REGEX, e as en, A as AUDIT_LOG_DEFAULTS, U as UI_DEFAULTS } from "./index-DnhzQm30.mjs";
8
+ import { g as getTrad, E as EMAIL_REGEX, e as en, A as AUDIT_LOG_DEFAULTS, U as UI_DEFAULTS } from "./index-D0Q_r3J6.mjs";
9
9
  import styled from "styled-components";
10
10
  import { Filter, ClipboardList, Server } from "lucide-react";
11
11
  function Role({ oidcRoles, roles, onChangeRole }) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
- const index = require("./index-B-sTTO3a.js");
3
+ const index = require("./index-COhAwRD-.js");
4
4
  require("react");
5
5
  require("react-dom/client");
6
6
  exports.default = index.index;
@@ -1,4 +1,4 @@
1
- import { i } from "./index-DnhzQm30.mjs";
1
+ import { i } from "./index-D0Q_r3J6.mjs";
2
2
  import "react";
3
3
  import "react-dom/client";
4
4
  export {
@@ -103,7 +103,7 @@ const coerceBoolNullable = zod.z.preprocess(
103
103
  );
104
104
  const pluginConfigSchema = zod.z.object({
105
105
  REMEMBER_ME: coerceBool(false),
106
- OIDC_REDIRECT_URI: zod.z.string().default(""),
106
+ OIDC_PUBLIC_URL: zod.z.string().default(""),
107
107
  OIDC_CLIENT_ID: zod.z.string().default(""),
108
108
  OIDC_CLIENT_SECRET: zod.z.string().default(""),
109
109
  OIDC_SCOPE: zod.z.string().default("openid profile email"),
@@ -175,6 +175,14 @@ const DAY_MS = 864e5;
175
175
  const DISCOVERY_TIMEOUT_MS = 5e3;
176
176
  const OIDC_DISCOVERY_PATH = "/.well-known/openid-configuration";
177
177
  const OIDC_SIGN_IN_PATH = "/strapi-plugin-oidc/oidc";
178
+ const OIDC_CALLBACK_PATH = "/strapi-plugin-oidc/oidc/callback";
179
+ const AUTH_ROUTES = [
180
+ "login",
181
+ "register",
182
+ "register-admin",
183
+ "forgot-password",
184
+ "reset-password"
185
+ ];
178
186
  const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
179
187
  function getPluginConfig() {
180
188
  return pluginConfigSchema.parse(strapi.config.get("plugin::strapi-plugin-oidc") ?? {});
@@ -285,11 +293,11 @@ function clearAuthCookies(strapi2, ctx) {
285
293
  ctx.cookies.set(COOKIE_NAMES.accessToken, "", rootPathOptions);
286
294
  ctx.cookies.set(COOKIE_NAMES.userEmail, "", rootPathOptions);
287
295
  }
288
- const AUTH_ROUTES = ["login", "register", "register-admin", "forgot-password", "reset-password"];
289
296
  const STATIC_EXTENSIONS = [".js", ".css", ".png", ".svg", ".ico", ".woff2", ".json", ".map"];
290
297
  async function bootstrap({ strapi: strapi2 }) {
291
298
  await applyDiscovery(strapi2);
292
- const adminUrl = strapi2.config.get("admin.url", "/admin");
299
+ const rawAdminUrl = strapi2.config.get("admin.url");
300
+ const adminUrl = typeof rawAdminUrl === "string" && rawAdminUrl.length > 0 ? rawAdminUrl : "/admin";
293
301
  const tokenRefreshPath = `${adminUrl}/token/refresh`;
294
302
  const EXCLUDED_ADMIN_PATHS = [
295
303
  `${adminUrl}/login`,
@@ -415,7 +423,6 @@ function destroy() {
415
423
  const config = {
416
424
  default: {
417
425
  REMEMBER_ME: false,
418
- OIDC_REDIRECT_URI: "http://localhost:1337/strapi-plugin-oidc/oidc/callback",
419
426
  OIDC_CLIENT_ID: "",
420
427
  OIDC_CLIENT_SECRET: "",
421
428
  OIDC_SCOPE: "openid profile email",
@@ -555,7 +562,6 @@ const REQUIRED_CONFIG_KEYS = [
555
562
  "OIDC_ISSUER",
556
563
  "OIDC_CLIENT_ID",
557
564
  "OIDC_CLIENT_SECRET",
558
- "OIDC_REDIRECT_URI",
559
565
  "OIDC_SCOPE",
560
566
  "OIDC_FAMILY_NAME_FIELD",
561
567
  "OIDC_GIVEN_NAME_FIELD",
@@ -564,6 +570,15 @@ const REQUIRED_CONFIG_KEYS = [
564
570
  "OIDC_USERINFO_ENDPOINT",
565
571
  "OIDC_AUTHORIZATION_ENDPOINT"
566
572
  ];
573
+ function resolveRedirectUri(config2) {
574
+ const publicUrl = config2.OIDC_PUBLIC_URL || process.env.PUBLIC_URL || (process.env.NODE_ENV !== "production" ? "http://localhost:1337" : "");
575
+ if (!publicUrl) {
576
+ throw new Error(
577
+ "OIDC_PUBLIC_URL or PUBLIC_URL must be set in production. Provide your Strapi origin (e.g. https://myapp.com)."
578
+ );
579
+ }
580
+ return `${publicUrl}${OIDC_CALLBACK_PATH}`;
581
+ }
567
582
  const jwksCache = /* @__PURE__ */ new Map();
568
583
  let jwksDisabledWarned = false;
569
584
  function getJwks(uri) {
@@ -3609,11 +3624,12 @@ async function oidcSignIn(ctx) {
3609
3624
  try {
3610
3625
  const config2 = configValidation();
3611
3626
  if (!config2.OIDC_SKIP_LOGIN_PAGE) {
3612
- const adminUrl = strapi.config.get("admin.url", "/admin");
3627
+ const raw = strapi.config.get("admin.url");
3628
+ const adminUrl = typeof raw === "string" && raw.length > 0 ? raw : "/admin";
3613
3629
  ctx.redirect(`${adminUrl}/auth/login?oidc_redirect=1`);
3614
3630
  return;
3615
3631
  }
3616
- const { OIDC_CLIENT_ID, OIDC_REDIRECT_URI, OIDC_SCOPE, OIDC_AUTHORIZATION_ENDPOINT } = config2;
3632
+ const { OIDC_CLIENT_ID, OIDC_SCOPE, OIDC_AUTHORIZATION_ENDPOINT } = config2;
3617
3633
  const { code_verifier: codeVerifier, code_challenge: codeChallenge } = await pkceChallenge__default.default();
3618
3634
  const state = node_crypto.randomBytes(32).toString("base64url");
3619
3635
  const nonce = node_crypto.randomBytes(32).toString("base64url");
@@ -3626,10 +3642,11 @@ async function oidcSignIn(ctx) {
3626
3642
  ctx.cookies.set(COOKIE_NAMES.codeVerifier, codeVerifier, cookieOptions);
3627
3643
  ctx.cookies.set(COOKIE_NAMES.state, state, cookieOptions);
3628
3644
  ctx.cookies.set(COOKIE_NAMES.nonce, nonce, cookieOptions);
3645
+ const redirectUri = resolveRedirectUri(config2);
3629
3646
  const params = new URLSearchParams({
3630
3647
  response_type: "code",
3631
3648
  client_id: OIDC_CLIENT_ID,
3632
- redirect_uri: OIDC_REDIRECT_URI,
3649
+ redirect_uri: redirectUri,
3633
3650
  scope: OIDC_SCOPE,
3634
3651
  code_challenge: codeChallenge,
3635
3652
  code_challenge_method: "S256",
@@ -3974,7 +3991,7 @@ async function oidcSignInCallback(ctx) {
3974
3991
  code: String(ctx.query.code),
3975
3992
  client_id: config2.OIDC_CLIENT_ID,
3976
3993
  client_secret: config2.OIDC_CLIENT_SECRET,
3977
- redirect_uri: config2.OIDC_REDIRECT_URI,
3994
+ redirect_uri: resolveRedirectUri(config2),
3978
3995
  grant_type: "authorization_code",
3979
3996
  code_verifier: codeVerifier ?? ""
3980
3997
  });
@@ -97,7 +97,7 @@ const coerceBoolNullable = z.preprocess(
97
97
  );
98
98
  const pluginConfigSchema = z.object({
99
99
  REMEMBER_ME: coerceBool(false),
100
- OIDC_REDIRECT_URI: z.string().default(""),
100
+ OIDC_PUBLIC_URL: z.string().default(""),
101
101
  OIDC_CLIENT_ID: z.string().default(""),
102
102
  OIDC_CLIENT_SECRET: z.string().default(""),
103
103
  OIDC_SCOPE: z.string().default("openid profile email"),
@@ -169,6 +169,14 @@ const DAY_MS = 864e5;
169
169
  const DISCOVERY_TIMEOUT_MS = 5e3;
170
170
  const OIDC_DISCOVERY_PATH = "/.well-known/openid-configuration";
171
171
  const OIDC_SIGN_IN_PATH = "/strapi-plugin-oidc/oidc";
172
+ const OIDC_CALLBACK_PATH = "/strapi-plugin-oidc/oidc/callback";
173
+ const AUTH_ROUTES = [
174
+ "login",
175
+ "register",
176
+ "register-admin",
177
+ "forgot-password",
178
+ "reset-password"
179
+ ];
172
180
  const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
173
181
  function getPluginConfig() {
174
182
  return pluginConfigSchema.parse(strapi.config.get("plugin::strapi-plugin-oidc") ?? {});
@@ -279,11 +287,11 @@ function clearAuthCookies(strapi2, ctx) {
279
287
  ctx.cookies.set(COOKIE_NAMES.accessToken, "", rootPathOptions);
280
288
  ctx.cookies.set(COOKIE_NAMES.userEmail, "", rootPathOptions);
281
289
  }
282
- const AUTH_ROUTES = ["login", "register", "register-admin", "forgot-password", "reset-password"];
283
290
  const STATIC_EXTENSIONS = [".js", ".css", ".png", ".svg", ".ico", ".woff2", ".json", ".map"];
284
291
  async function bootstrap({ strapi: strapi2 }) {
285
292
  await applyDiscovery(strapi2);
286
- const adminUrl = strapi2.config.get("admin.url", "/admin");
293
+ const rawAdminUrl = strapi2.config.get("admin.url");
294
+ const adminUrl = typeof rawAdminUrl === "string" && rawAdminUrl.length > 0 ? rawAdminUrl : "/admin";
287
295
  const tokenRefreshPath = `${adminUrl}/token/refresh`;
288
296
  const EXCLUDED_ADMIN_PATHS = [
289
297
  `${adminUrl}/login`,
@@ -409,7 +417,6 @@ function destroy() {
409
417
  const config = {
410
418
  default: {
411
419
  REMEMBER_ME: false,
412
- OIDC_REDIRECT_URI: "http://localhost:1337/strapi-plugin-oidc/oidc/callback",
413
420
  OIDC_CLIENT_ID: "",
414
421
  OIDC_CLIENT_SECRET: "",
415
422
  OIDC_SCOPE: "openid profile email",
@@ -549,7 +556,6 @@ const REQUIRED_CONFIG_KEYS = [
549
556
  "OIDC_ISSUER",
550
557
  "OIDC_CLIENT_ID",
551
558
  "OIDC_CLIENT_SECRET",
552
- "OIDC_REDIRECT_URI",
553
559
  "OIDC_SCOPE",
554
560
  "OIDC_FAMILY_NAME_FIELD",
555
561
  "OIDC_GIVEN_NAME_FIELD",
@@ -558,6 +564,15 @@ const REQUIRED_CONFIG_KEYS = [
558
564
  "OIDC_USERINFO_ENDPOINT",
559
565
  "OIDC_AUTHORIZATION_ENDPOINT"
560
566
  ];
567
+ function resolveRedirectUri(config2) {
568
+ const publicUrl = config2.OIDC_PUBLIC_URL || process.env.PUBLIC_URL || (process.env.NODE_ENV !== "production" ? "http://localhost:1337" : "");
569
+ if (!publicUrl) {
570
+ throw new Error(
571
+ "OIDC_PUBLIC_URL or PUBLIC_URL must be set in production. Provide your Strapi origin (e.g. https://myapp.com)."
572
+ );
573
+ }
574
+ return `${publicUrl}${OIDC_CALLBACK_PATH}`;
575
+ }
561
576
  const jwksCache = /* @__PURE__ */ new Map();
562
577
  let jwksDisabledWarned = false;
563
578
  function getJwks(uri) {
@@ -3603,11 +3618,12 @@ async function oidcSignIn(ctx) {
3603
3618
  try {
3604
3619
  const config2 = configValidation();
3605
3620
  if (!config2.OIDC_SKIP_LOGIN_PAGE) {
3606
- const adminUrl = strapi.config.get("admin.url", "/admin");
3621
+ const raw = strapi.config.get("admin.url");
3622
+ const adminUrl = typeof raw === "string" && raw.length > 0 ? raw : "/admin";
3607
3623
  ctx.redirect(`${adminUrl}/auth/login?oidc_redirect=1`);
3608
3624
  return;
3609
3625
  }
3610
- const { OIDC_CLIENT_ID, OIDC_REDIRECT_URI, OIDC_SCOPE, OIDC_AUTHORIZATION_ENDPOINT } = config2;
3626
+ const { OIDC_CLIENT_ID, OIDC_SCOPE, OIDC_AUTHORIZATION_ENDPOINT } = config2;
3611
3627
  const { code_verifier: codeVerifier, code_challenge: codeChallenge } = await pkceChallenge();
3612
3628
  const state = randomBytes(32).toString("base64url");
3613
3629
  const nonce = randomBytes(32).toString("base64url");
@@ -3620,10 +3636,11 @@ async function oidcSignIn(ctx) {
3620
3636
  ctx.cookies.set(COOKIE_NAMES.codeVerifier, codeVerifier, cookieOptions);
3621
3637
  ctx.cookies.set(COOKIE_NAMES.state, state, cookieOptions);
3622
3638
  ctx.cookies.set(COOKIE_NAMES.nonce, nonce, cookieOptions);
3639
+ const redirectUri = resolveRedirectUri(config2);
3623
3640
  const params = new URLSearchParams({
3624
3641
  response_type: "code",
3625
3642
  client_id: OIDC_CLIENT_ID,
3626
- redirect_uri: OIDC_REDIRECT_URI,
3643
+ redirect_uri: redirectUri,
3627
3644
  scope: OIDC_SCOPE,
3628
3645
  code_challenge: codeChallenge,
3629
3646
  code_challenge_method: "S256",
@@ -3968,7 +3985,7 @@ async function oidcSignInCallback(ctx) {
3968
3985
  code: String(ctx.query.code),
3969
3986
  client_id: config2.OIDC_CLIENT_ID,
3970
3987
  client_secret: config2.OIDC_CLIENT_SECRET,
3971
- redirect_uri: config2.OIDC_REDIRECT_URI,
3988
+ redirect_uri: resolveRedirectUri(config2),
3972
3989
  grant_type: "authorization_code",
3973
3990
  code_verifier: codeVerifier ?? ""
3974
3991
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-plugin-oidc",
3
- "version": "1.10.2",
3
+ "version": "1.10.4",
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",