siyuan-note/siyuan · error

OIDC redirect URL must end with /api/system/oidc/callback

Error message

OIDC redirect URL must end with /api/system/oidc/callback

What it means

The configured redirect URL parsed but failed structural validation: it must have a non-empty scheme, a non-empty host, a path exactly equal to /api/system/oidc/callback, and no userinfo, query string, or fragment. The message highlights the path because that is the most common failure, but any of those defects trigger it.

Source

Thrown at kernel/model/oidc.go:635

func oidcValidationRedirectURL(c *gin.Context, config *conf.OIDC, mobile bool) (string, error) {
	if mobile {
		return oidcMobileRedirectURL, nil
	}
	if config.RedirectURL != "" {
		return validatePublicOIDCRedirectURL(config.RedirectURL)
	}
	return effectiveOIDCRedirectURL(c, oidcFlowDesktop)
}

func validatePublicOIDCRedirectURL(redirectURL string) (string, error) {
	if redirectURL == "" {
		return "", errors.New("A public HTTPS OIDC redirect URL is required for remote access")
	}
	parsed, err := url.Parse(redirectURL)
	if err != nil || parsed.Scheme == "" || parsed.Host == "" || parsed.Path != "/api/system/oidc/callback" ||
		parsed.User != nil || parsed.RawQuery != "" || parsed.Fragment != "" {
		return "", errors.New("OIDC redirect URL must end with /api/system/oidc/callback")
	}
	if parsed.Scheme != "https" {
		return "", errors.New("Public OIDC redirect URL must use HTTPS")
	}
	return parsed.String(), nil
}

func getOIDCProvider(ctx context.Context, redirectURL string) (*oidc_provider.Provider, error) {
	version := oidcConfigurationVersion(Conf.GetOIDC())
	key := version + "\x00" + redirectURL
	oidcProviders.Lock()
	if oidcProviders.version != version {
		oidcProviders.version = version
		oidcProviders.items = map[string]*oidc_provider.Provider{}
	}
	if provider := oidcProviders.items[key]; provider != nil {
		oidcProviders.Unlock()
		return provider, nil

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Set the URL to exactly https://<host>/api/system/oidc/callback with no trailing slash, query, fragment, or userinfo.
  2. Run OIDC validation from the admin UI to confirm the value is accepted.
  3. Mirror the exact same string in the IdP's allowed redirect URIs.

Example fix

// before
RedirectURL: "https://notes.example.com/oidc/callback"
// after
RedirectURL: "https://notes.example.com/api/system/oidc/callback"
Defensive patterns

Strategy: validation

Validate before calling

// Validate redirect-URL structure exactly as the kernel does, before saving config.
func checkRedirectURL(s string) error {
    u, err := url.Parse(s)
    if err != nil || u.Scheme == "" || u.Host == "" ||
        u.Path != "/api/system/oidc/callback" ||
        u.User != nil || u.RawQuery != "" || u.Fragment != "" {
        return errors.New("redirect URL must be https://<host>/api/system/oidc/callback with no query/fragment")
    }
    return nil
}

Prevention

When it happens

Trigger: validatePublicOIDCRedirectURL receives a URL such as https://host/oidc/callback, https://host/api/system/oidc/callback/ (trailing slash), host/api/system/oidc/callback (no scheme), or https://host/api/system/oidc/callback?next=/ (query).

Common situations: Operator copies a redirect URL from another app; trailing slash; path typo; appending query parameters meant for the IdP rather than SiYuan.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/73cbc975f293b0ee. Report an issue: GitHub.