siyuan-note/siyuan · error

OIDC issuer URL must use HTTPS unless it is a loopback…

Error message

OIDC issuer URL must use HTTPS unless it is a loopback address

What it means

When a Custom or Microsoft provider has an IssuerURL, it is strictly parsed and checked: it must parse, have a host, no userinfo, no query string, no fragment, and use https — unless the host is a local/loopback address allowed by util.IsLocalHostname. Any violation returns "OIDC issuer URL must use HTTPS unless it is a loopback address" to prevent leaking tokens over insecure or ambiguous URLs.

Solutions

  1. Serve the identity provider over HTTPS and set IssuerURL with the https scheme.
  2. For local development, run the IdP on a loopback host (localhost/127.0.0.1) so the IsLocalHostname exemption applies.
  3. Strip any query strings, fragments, and userinfo from the issuer URL; keep only scheme://host[:port]/path.
  4. Verify with url.Parse locally that the URL yields a non-empty Host before configuring it.

Example fix

// before
IssuerURL: "http://idp.internal.example.com?realm=main"
// after
IssuerURL: "https://idp.internal.example.com"
Defensive patterns

Strategy: validation

Validate before calling

// Go: mirror the kernel's issuer checks locally before configuring
u, err := url.Parse(cfg.IssuerURL)
if err != nil || u.Host == "" || u.User != nil || u.RawQuery != "" || u.Fragment != "" ||
	(u.Scheme != "https" && !util.IsLocalHostname(u.Hostname())) {
	return errors.New("issuer must be a plain https URL (loopback http allowed)")
}

Try / catch

// JavaScript caller
try {
  await saveOIDCSettings(cfg);
} catch (e) {
  if (e.msg.includes("must use HTTPS")) {
    showHint("Use https:// (or a localhost issuer for development) with no query/fragment");
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling ValidateOIDCConfiguration with an IssuerURL that uses plain http against a public host, contains a query (?tenant=...), a fragment (#...), userinfo (user@host), is missing a host, or is not parseable by url.Parse.

Common situations: Testing against a self-hosted IdP over http:// on a LAN address that is not in the loopback allowlist; appending query parameters to the issuer; pasting the full discovery URL (including /.well-known/openid-configuration?x=1) as the issuer; typos leaving the scheme off so parsing produces no host.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/f4e5ce3ed5cbb414. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/oidc.go:472

func ValidateOIDCConfiguration(config *conf.OIDC) error {
	if config == nil || !config.Enabled {
		return errors.New("OIDC login is not enabled")
	}
	if config.ClientID == "" {
		return errors.New("OIDC client ID is required")
	}
	if config.Provider == conf.OIDCProviderGitHub && config.ClientSecret == "" {
		return errors.New("GitHub OAuth client secret is required")
	}
	if (config.Provider == conf.OIDCProviderCustom || config.Provider == conf.OIDCProviderMicrosoft) && config.IssuerURL == "" {
		return errors.New("OIDC issuer URL is required")
	}
	if (config.Provider == conf.OIDCProviderCustom || config.Provider == conf.OIDCProviderMicrosoft) && config.IssuerURL != "" {
		issuer, err := url.Parse(config.IssuerURL)
		if err != nil || issuer.Host == "" || issuer.User != nil || issuer.RawQuery != "" || issuer.Fragment != "" ||
			(issuer.Scheme != "https" && !util.IsLocalHostname(issuer.Hostname())) {
			return errors.New("OIDC issuer URL must use HTTPS unless it is a loopback address")
		}
	}
	if config.Provider != conf.OIDCProviderCustom && config.Provider != conf.OIDCProviderGoogle &&
		config.Provider != conf.OIDCProviderMicrosoft && config.Provider != conf.OIDCProviderGitHub {
		return errors.New("Unsupported OIDC provider")
	}
	if !config.AllowAll && len(config.ClaimRules) == 0 {
		return errors.New("OIDC login requires at least one claim rule when Allow all users is disabled")
	}
	for _, rule := range config.ClaimRules {
		if rule == nil || rule.Claim == "" || len(rule.Values) == 0 {
			return errors.New("OIDC claim rules must include a claim and at least one value")
		}
		if rule.Operator != conf.OIDCClaimOperatorEquals && rule.Operator != conf.OIDCClaimOperatorContains {
			return errors.New("Unsupported OIDC claim rule operator")
		}
		for _, value := range rule.Values {
			if value == "" {

View on GitHub (pinned to 9f775e8a12)