medusajs/medusa · error · MedusaError

OIDC '${label}' must be a valid URL

Error message

OIDC '${label}' must be a valid URL

What it means

assertSecureUrl is used to validate the issuer and callback_url options. If new URL(value) throws (malformed string, empty, missing scheme), it raises INVALID_DATA saying the '<label>' must be a valid URL.

Source

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

import { isProduction, MedusaError } from "@medusajs/framework/utils"

/**
 * Asserts that a URL is `https`. Outside of production, `http` is allowed for
 * localhost so local development and testing remain possible.
 */
export const assertSecureUrl = (value: string, label: string): void => {
  let url: URL
  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. Prefix the scheme: issuer must look like https://auth.example.com (issuer is the base URL, no /.well-known/... path needed).
  2. Trim and sanitize env values; check for stray quotes from .env formatting.
  3. Log the resolved value at startup to catch undefined interpolation before it reaches the provider.

Example fix

// before
options: { issuer: "auth.example.com", ... }
// after
options: { issuer: "https://auth.example.com", ... }
Defensive patterns

Strategy: validation

Validate before calling

function isValidUrl(value: string): boolean {
  try { new URL(value); return true } catch { return false }
}
if (!isValidUrl(options.issuer)) throw new Error("issuer must be a valid URL including scheme")

Type guard

const isValidHttpUrl = (v: string): v is \`https://\${string}\` => { try { const u = new URL(v); return u.protocol === "https:" || u.protocol === "http:" } catch { return false } }

Try / catch

try { assertSecureUrl(value, "issuer") } catch (e) { if (e instanceof MedusaError && /must be a valid URL/.test(e.message)) { /* fix the string (add https://) */ } throw e }

Prevention

When it happens

Trigger: Passing issuer: "auth.example.com" (no scheme), issuer: "" , or issuer: undefined-as-string; a callback_url with unencoded spaces or a stray trailing character.

Common situations: Missing https:// prefix — the single most common cause; env var containing quotes or whitespace; concatenation bugs producing strings like "https://example.com/none" + undefined; copy-pasting the well-known path into issuer with a typo.

Related errors


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