{"record":{"id":"b8a839c852f17ca9","repo":"siyuan-note/siyuan","slug":"a-loopback-oidc-redirect-url-is-required-for-local","errorCode":null,"errorMessage":"A loopback OIDC redirect URL is required for local access","messagePattern":"A loopback OIDC redirect URL is required for local access","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/oidc.go","lineNumber":613,"sourceCode":"}\n\nfunc effectiveOIDCRedirectURL(c *gin.Context, flow string) (string, error) {\n\tif flow == oidcFlowMobile {\n\t\treturn oidcMobileRedirectURL, nil\n\t}\n\tif flow == oidcFlowWeb && !IsLocalRequest(c) {\n\t\treturn validatePublicOIDCRedirectURL(Conf.GetOIDC().RedirectURL)\n\t}\n\tif !IsLocalRequest(c) {\n\t\treturn \"\", errors.New(\"Desktop OIDC login requires a loopback listener\")\n\t}\n\tscheme := \"http\"\n\tif c.Request.TLS != nil || c.GetHeader(\"X-Forwarded-Proto\") == \"https\" {\n\t\tscheme = \"https\"\n\t}\n\thost := c.Request.Host\n\tif !util.IsLocalHost(host) {\n\t\treturn \"\", errors.New(\"A loopback OIDC redirect URL is required for local access\")\n\t}\n\treturn scheme + \"://\" + host + \"/api/system/oidc/callback\", nil\n}\n\nfunc oidcValidationRedirectURL(c *gin.Context, config *conf.OIDC, mobile bool) (string, error) {\n\tif mobile {\n\t\treturn oidcMobileRedirectURL, nil\n\t}\n\tif config.RedirectURL != \"\" {\n\t\treturn validatePublicOIDCRedirectURL(config.RedirectURL)\n\t}\n\treturn effectiveOIDCRedirectURL(c, oidcFlowDesktop)\n}\n\nfunc validatePublicOIDCRedirectURL(redirectURL string) (string, error) {\n\tif redirectURL == \"\" {\n\t\treturn \"\", errors.New(\"A public HTTPS OIDC redirect URL is required for remote access\")\n\t}","sourceCodeStart":595,"sourceCodeEnd":631,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/oidc.go#L595-L631","documentation":"Returned by effectiveOIDCRedirectURL (kernel/model/oidc.go:613) for the desktop flow when the request IS local (IsLocalRequest passed earlier) but c.Request.Host, after parsing via util.IsLocalHost, is not a loopback host. This catches proxied or Host-header-rewritten requests that look local to the connection but carry a non-loopback Host.","triggerScenarios":"A reverse proxy on the same machine forwards to SiYuan with a non-loopback Host header; a browser uses a custom Host mapping (e.g. via /etc/hosts to a non-localhost name) that still routes to 127.0.0.1.","commonSituations":"Local reverse proxy (Caddy/Nginx) in front of SiYuan preserving an external hostname; debugging with a custom domain that resolves to loopback.","solutions":["Access SiYuan directly via 127.0.0.1:6806 or localhost:6806 for the desktop OIDC flow so Host is loopback.","Configure the proxy to pass Host as 127.0.0.1:6806 for desktop login, or use the web flow with a public redirect URL.","Avoid /etc/hosts tricks with non-localhost names for desktop OIDC."],"exampleFix":"# before — proxy sets external Host\nproxy_set_header Host siyuan.corp;\n# after — for local desktop flow, access directly\nopen http://127.0.0.1:6806  # Host: 127.0.0.1:6806 -> ok","handlingStrategy":"validation","validationCode":"if !util.IsLocalHost(c.Request.Host) {\n    return errors.New(\"request Host is not loopback; use 127.0.0.1\")\n}","typeGuard":"func hostIsLoopback(host string) bool { return util.IsLocalHost(host) }","tryCatchPattern":null,"preventionTips":["For desktop OIDC, browse to SiYuan by 127.0.0.1, not a mapped hostname.","Reverse proxies should preserve a loopback Host for local flows, or route remote users to the web flow."],"tags":["oidc","desktop","loopback","host-header","proxy"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}