{"record":{"id":"f4e5ce3ed5cbb414","repo":"siyuan-note/siyuan","slug":"oidc-issuer-url-must-use-https-unless-it-is-a-loop","errorCode":null,"errorMessage":"OIDC issuer URL must use HTTPS unless it is a loopback address","messagePattern":"OIDC issuer URL must use HTTPS unless it is a loopback address","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/oidc.go","lineNumber":528,"sourceCode":"\nfunc ValidateOIDCConfiguration(config *conf.OIDC) error {\n\tif config == nil || !config.Enabled {\n\t\treturn errors.New(\"OIDC login is not enabled\")\n\t}\n\tif config.ClientID == \"\" {\n\t\treturn errors.New(\"OIDC client ID is required\")\n\t}\n\tif config.Provider == conf.OIDCProviderGitHub && config.ClientSecret == \"\" {\n\t\treturn errors.New(\"GitHub OAuth client secret is required\")\n\t}\n\tif (config.Provider == conf.OIDCProviderCustom || config.Provider == conf.OIDCProviderMicrosoft) && config.IssuerURL == \"\" {\n\t\treturn errors.New(\"OIDC issuer URL is required\")\n\t}\n\tif (config.Provider == conf.OIDCProviderCustom || config.Provider == conf.OIDCProviderMicrosoft) && config.IssuerURL != \"\" {\n\t\tissuer, err := url.Parse(config.IssuerURL)\n\t\tif err != nil || issuer.Host == \"\" || issuer.User != nil || issuer.RawQuery != \"\" || issuer.Fragment != \"\" ||\n\t\t\t(issuer.Scheme != \"https\" && !util.IsLocalHostname(issuer.Hostname())) {\n\t\t\treturn errors.New(\"OIDC issuer URL must use HTTPS unless it is a loopback address\")\n\t\t}\n\t}\n\tif config.Provider != conf.OIDCProviderCustom && config.Provider != conf.OIDCProviderGoogle &&\n\t\tconfig.Provider != conf.OIDCProviderMicrosoft && config.Provider != conf.OIDCProviderGitHub {\n\t\treturn errors.New(\"Unsupported OIDC provider\")\n\t}\n\tif !config.AllowAll && len(config.ClaimRules) == 0 {\n\t\treturn errors.New(\"OIDC login requires at least one claim rule when Allow all users is disabled\")\n\t}\n\tfor _, rule := range config.ClaimRules {\n\t\tif rule == nil || rule.Claim == \"\" || len(rule.Values) == 0 {\n\t\t\treturn errors.New(\"OIDC claim rules must include a claim and at least one value\")\n\t\t}\n\t\tif rule.Operator != conf.OIDCClaimOperatorEquals && rule.Operator != conf.OIDCClaimOperatorContains {\n\t\t\treturn errors.New(\"Unsupported OIDC claim rule operator\")\n\t\t}\n\t\tfor _, value := range rule.Values {\n\t\t\tif value == \"\" {","sourceCodeStart":510,"sourceCodeEnd":546,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/oidc.go#L510-L546","documentation":"Fifth check in ValidateOIDCConfiguration (kernel/model/oidc.go:528): for Custom/Microsoft providers the parsed IssuerURL must have a host, no userinfo/query/fragment, and a scheme that is either 'https' or, if not https, a hostname that resolves to loopback via util.IsLocalHostname. Any deviation returns this error.","triggerScenarios":"Saving an issuer URL like http://keycloak.example.com (non-HTTPS public host), http://localhost:8080 with userinfo, a URL with a trailing query (?foo=bar), or a URL that fails to parse.","commonSituations":"Local dev pointing at a plain-HTTP Keycloak on a non-loopback hostname; copy-pasting a discovery URL with the .well-known suffix or a query string; using 'http://' against a public test IdP.","solutions":["Switch the issuer to HTTPS (terminate TLS at the IdP or a reverse proxy).","If you must use HTTP, run the IdP on localhost/127.0.0.1 so IsLocalHostname returns true.","Strip any query, fragment, or userinfo from the URL before saving; keep only scheme://host[:port]/path."],"exampleFix":"// before\ncfg.IssuerURL = \"http://keycloak.corp:8080/auth/realms/demo\"\n// after — TLS-terminate or use loopback\ncfg.IssuerURL = \"https://keycloak.corp/auth/realms/demo\"\n// or local dev only\ncfg.IssuerURL = \"http://localhost:8080/auth/realms/demo\"","handlingStrategy":"validation","validationCode":"u, err := url.Parse(cfg.IssuerURL)\nif err != nil || u.Host == \"\" || u.User != nil || u.RawQuery != \"\" || u.Fragment != \"\" {\n    return errors.New(\"issuer URL malformed\")\n}\nif u.Scheme != \"https\" && !util.IsLocalHostname(u.Hostname()) {\n    return errors.New(\"issuer must be HTTPS or loopback\")\n}","typeGuard":"func issuerURLSafe(raw string) bool {\n    u, err := url.Parse(raw)\n    if err != nil || u.Host == \"\" || u.User != nil || u.RawQuery != \"\" || u.Fragment != \"\" {\n        return false\n    }\n    return u.Scheme == \"https\" || util.IsLocalHostname(u.Hostname())\n}","tryCatchPattern":null,"preventionTips":["Always TLS-terminate OIDC issuers in production; reserve HTTP for true loopback dev.","Validate the URL client-side before saving the config to avoid round-tripping."],"tags":["oidc","issuer","tls","security","loopback"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}