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
- Serve the identity provider over HTTPS and set IssuerURL with the https scheme.
- For local development, run the IdP on a loopback host (localhost/127.0.0.1) so the IsLocalHostname exemption applies.
- Strip any query strings, fragments, and userinfo from the issuer URL; keep only scheme://host[:port]/path.
- 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
- Always configure the bare https issuer without query or fragment
- Do not paste the full .well-known discovery URL as the issuer
- Put local dev IdPs on localhost so the loopback exemption applies
- Sanitize URLs (strip ?/#, userinfo) before saving them into config
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
- OIDC nonce does not match
- --remote requires HTTPS
- verify OIDC ID token failed
- A loopback OIDC redirect URL is required for local access
- A public HTTPS OIDC redirect URL is required for remote…
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)