strapi-plugin-oidc 1.10.3 → 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
 
@@ -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,7 @@ 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";
178
179
  const AUTH_ROUTES = [
179
180
  "login",
180
181
  "register",
@@ -422,7 +423,6 @@ function destroy() {
422
423
  const config = {
423
424
  default: {
424
425
  REMEMBER_ME: false,
425
- OIDC_REDIRECT_URI: "http://localhost:1337/strapi-plugin-oidc/oidc/callback",
426
426
  OIDC_CLIENT_ID: "",
427
427
  OIDC_CLIENT_SECRET: "",
428
428
  OIDC_SCOPE: "openid profile email",
@@ -562,7 +562,6 @@ const REQUIRED_CONFIG_KEYS = [
562
562
  "OIDC_ISSUER",
563
563
  "OIDC_CLIENT_ID",
564
564
  "OIDC_CLIENT_SECRET",
565
- "OIDC_REDIRECT_URI",
566
565
  "OIDC_SCOPE",
567
566
  "OIDC_FAMILY_NAME_FIELD",
568
567
  "OIDC_GIVEN_NAME_FIELD",
@@ -571,6 +570,15 @@ const REQUIRED_CONFIG_KEYS = [
571
570
  "OIDC_USERINFO_ENDPOINT",
572
571
  "OIDC_AUTHORIZATION_ENDPOINT"
573
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
+ }
574
582
  const jwksCache = /* @__PURE__ */ new Map();
575
583
  let jwksDisabledWarned = false;
576
584
  function getJwks(uri) {
@@ -3621,7 +3629,7 @@ async function oidcSignIn(ctx) {
3621
3629
  ctx.redirect(`${adminUrl}/auth/login?oidc_redirect=1`);
3622
3630
  return;
3623
3631
  }
3624
- const { OIDC_CLIENT_ID, OIDC_REDIRECT_URI, OIDC_SCOPE, OIDC_AUTHORIZATION_ENDPOINT } = config2;
3632
+ const { OIDC_CLIENT_ID, OIDC_SCOPE, OIDC_AUTHORIZATION_ENDPOINT } = config2;
3625
3633
  const { code_verifier: codeVerifier, code_challenge: codeChallenge } = await pkceChallenge__default.default();
3626
3634
  const state = node_crypto.randomBytes(32).toString("base64url");
3627
3635
  const nonce = node_crypto.randomBytes(32).toString("base64url");
@@ -3634,10 +3642,11 @@ async function oidcSignIn(ctx) {
3634
3642
  ctx.cookies.set(COOKIE_NAMES.codeVerifier, codeVerifier, cookieOptions);
3635
3643
  ctx.cookies.set(COOKIE_NAMES.state, state, cookieOptions);
3636
3644
  ctx.cookies.set(COOKIE_NAMES.nonce, nonce, cookieOptions);
3645
+ const redirectUri = resolveRedirectUri(config2);
3637
3646
  const params = new URLSearchParams({
3638
3647
  response_type: "code",
3639
3648
  client_id: OIDC_CLIENT_ID,
3640
- redirect_uri: OIDC_REDIRECT_URI,
3649
+ redirect_uri: redirectUri,
3641
3650
  scope: OIDC_SCOPE,
3642
3651
  code_challenge: codeChallenge,
3643
3652
  code_challenge_method: "S256",
@@ -3982,7 +3991,7 @@ async function oidcSignInCallback(ctx) {
3982
3991
  code: String(ctx.query.code),
3983
3992
  client_id: config2.OIDC_CLIENT_ID,
3984
3993
  client_secret: config2.OIDC_CLIENT_SECRET,
3985
- redirect_uri: config2.OIDC_REDIRECT_URI,
3994
+ redirect_uri: resolveRedirectUri(config2),
3986
3995
  grant_type: "authorization_code",
3987
3996
  code_verifier: codeVerifier ?? ""
3988
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,7 @@ 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";
172
173
  const AUTH_ROUTES = [
173
174
  "login",
174
175
  "register",
@@ -416,7 +417,6 @@ function destroy() {
416
417
  const config = {
417
418
  default: {
418
419
  REMEMBER_ME: false,
419
- OIDC_REDIRECT_URI: "http://localhost:1337/strapi-plugin-oidc/oidc/callback",
420
420
  OIDC_CLIENT_ID: "",
421
421
  OIDC_CLIENT_SECRET: "",
422
422
  OIDC_SCOPE: "openid profile email",
@@ -556,7 +556,6 @@ const REQUIRED_CONFIG_KEYS = [
556
556
  "OIDC_ISSUER",
557
557
  "OIDC_CLIENT_ID",
558
558
  "OIDC_CLIENT_SECRET",
559
- "OIDC_REDIRECT_URI",
560
559
  "OIDC_SCOPE",
561
560
  "OIDC_FAMILY_NAME_FIELD",
562
561
  "OIDC_GIVEN_NAME_FIELD",
@@ -565,6 +564,15 @@ const REQUIRED_CONFIG_KEYS = [
565
564
  "OIDC_USERINFO_ENDPOINT",
566
565
  "OIDC_AUTHORIZATION_ENDPOINT"
567
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
+ }
568
576
  const jwksCache = /* @__PURE__ */ new Map();
569
577
  let jwksDisabledWarned = false;
570
578
  function getJwks(uri) {
@@ -3615,7 +3623,7 @@ async function oidcSignIn(ctx) {
3615
3623
  ctx.redirect(`${adminUrl}/auth/login?oidc_redirect=1`);
3616
3624
  return;
3617
3625
  }
3618
- const { OIDC_CLIENT_ID, OIDC_REDIRECT_URI, OIDC_SCOPE, OIDC_AUTHORIZATION_ENDPOINT } = config2;
3626
+ const { OIDC_CLIENT_ID, OIDC_SCOPE, OIDC_AUTHORIZATION_ENDPOINT } = config2;
3619
3627
  const { code_verifier: codeVerifier, code_challenge: codeChallenge } = await pkceChallenge();
3620
3628
  const state = randomBytes(32).toString("base64url");
3621
3629
  const nonce = randomBytes(32).toString("base64url");
@@ -3628,10 +3636,11 @@ async function oidcSignIn(ctx) {
3628
3636
  ctx.cookies.set(COOKIE_NAMES.codeVerifier, codeVerifier, cookieOptions);
3629
3637
  ctx.cookies.set(COOKIE_NAMES.state, state, cookieOptions);
3630
3638
  ctx.cookies.set(COOKIE_NAMES.nonce, nonce, cookieOptions);
3639
+ const redirectUri = resolveRedirectUri(config2);
3631
3640
  const params = new URLSearchParams({
3632
3641
  response_type: "code",
3633
3642
  client_id: OIDC_CLIENT_ID,
3634
- redirect_uri: OIDC_REDIRECT_URI,
3643
+ redirect_uri: redirectUri,
3635
3644
  scope: OIDC_SCOPE,
3636
3645
  code_challenge: codeChallenge,
3637
3646
  code_challenge_method: "S256",
@@ -3976,7 +3985,7 @@ async function oidcSignInCallback(ctx) {
3976
3985
  code: String(ctx.query.code),
3977
3986
  client_id: config2.OIDC_CLIENT_ID,
3978
3987
  client_secret: config2.OIDC_CLIENT_SECRET,
3979
- redirect_uri: config2.OIDC_REDIRECT_URI,
3988
+ redirect_uri: resolveRedirectUri(config2),
3980
3989
  grant_type: "authorization_code",
3981
3990
  code_verifier: codeVerifier ?? ""
3982
3991
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-plugin-oidc",
3
- "version": "1.10.3",
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",