medusajs/medusa · error · MedusaError

OIDC '${label}' must use https (http is only allowed for loc

Error message

OIDC '${label}' must use https (http is only allowed for localhost outside of production)

What it means

assertSecureUrl requires https for both issuer and callback_url. Plain http is tolerated only when the hostname is localhost/127.0.0.1/::1 AND the process is not running in production (NODE_ENV=production); otherwise it throws INVALID_DATA.

Source

Thrown at packages/modules/providers/auth-oidc/src/utils/assert-secure-url.ts:27

  try {
    url = new URL(value)
  } catch (e) {
    throw new MedusaError(
      MedusaError.Types.INVALID_DATA,
      `OIDC '${label}' must be a valid URL`
    )
  }

  const isLocalhost =
    url.hostname === "localhost" ||
    url.hostname === "127.0.0.1" ||
    url.hostname === "::1" ||
    url.hostname === "[::1]"

  const allowsHttp = isLocalhost && !isProduction()

  if (url.protocol !== "https:" && !(url.protocol === "http:" && allowsHttp)) {
    throw new MedusaError(
      MedusaError.Types.INVALID_DATA,
      `OIDC '${label}' must use https (http is only allowed for localhost outside of production)`
    )
  }
}

View on GitHub (pinned to 5e06e544a2)

Solutions

  1. Use https URLs for issuer and callback_url in production (e.g. put your domain behind a TLS proxy/CDN).
  2. Derive callback_url from the forwarded public origin (e.g. trust X-Forwarded-Proto) instead of the internal request host.
  3. For non-production testing on non-localhost hosts, either add a hosts mapping to localhost or run NODE_ENV != production.

Example fix

// before
options: { issuer: "http://auth.internal:8080", callback_url: "http://app.internal:9000/auth/customer/oidc/callback" } // NODE_ENV=production -> throws
// after
options: { issuer: "https://auth.example.com", callback_url: "https://app.example.com/auth/customer/oidc/callback" }
Defensive patterns

Strategy: validation

Validate before calling

const u = new URL(options.issuer)
const isLocal = ["localhost","127.0.0.1","::1"].includes(u.hostname)
if (process.env.NODE_ENV === "production" && u.protocol !== "https:") {
  throw new Error("production OIDC URLs must use https")
}

Type guard

const isSecureEnoughUrl = (v: string): boolean => { try { const u = new URL(v); return u.protocol === "https:" || (["localhost","127.0.0.1","::1"].includes(u.hostname) && process.env.NODE_ENV !== "production") } catch { return false } }

Try / catch

try { assertSecureUrl(value, label) } catch (e) { if (e instanceof MedusaError && /must use https/.test(e.message)) { /* switch to https or non-production localhost */ } throw e }

Prevention

When it happens

Trigger: Deploying with issuer or callback_url on http:// while NODE_ENV=production; using an http staging host that is not localhost; http callback through a non-localhost docker hostname in prod mode.

Common situations: Config built for local dev (http://localhost:8000) promoted to production where the public URL must be https; sitting behind a TLS-terminating proxy but the app constructs the callback from an internal http base URL; NODE_ENV left as production during a docker-compose test using http service names.

Related errors


AI-assisted analysis of medusajs/medusa@5e06e544a2 (2026-08-27). Data as JSON: /api/errors/3738366d6d9fa73a. Report an issue: GitHub.