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 public OIDC redirect URL must parse to an absolute http(s) URL whose path is exactly /api/system/oidc/callback, with no userinfo, query, or fragment; anything else cannot be matched against the fixed callback endpoint, so validatePublicOIDCRedirectURL rejects it.

Solutions

  1. Use the exact path /api/system/oidc/callback: https://<host>/api/system/oidc/callback
  2. Remove any query string, fragment, or userinfo from the URL
  3. Ensure the URL includes scheme and host (https://your-domain/...), not just a path

Example fix

// before
RedirectURL: "https://siyuan.example.com/oidc?brand=siyuan"
// after
RedirectURL: "https://siyuan.example.com/api/system/oidc/callback"
Defensive patterns

Strategy: validation

Validate before calling

function validPublicRedirect(u) {
  try {
    const p = new URL(u);
    return p.protocol === 'https:' && p.pathname === '/api/system/oidc/callback' && !p.search && !p.hash && !p.username && !p.password;
  } catch { return false; }
}

Try / catch

if err := validatePublicOIDCRedirectURL(cfg.RedirectURL); err != nil {
    // correct to https://<host>/api/system/oidc/callback and retry
}

Prevention

When it happens

Trigger: url.Parse fails, or parsed.Scheme/Host empty, parsed.Path != "/api/system/oidc/callback", or parsed.User/RawQuery/Fragment non-empty — for any configured RedirectURL passed via ValidateOIDCProviderConfiguration, ValidateOIDCConfigurationChange, oidcValidationRedirectURL, or effectiveOIDCRedirectURL.

Common situations: Entering only the domain or a trailing-slash path (e.g. https://x.com/oidc) instead of the full callback path; appending ?tenant=... parameters; forgetting https:// so Host parses as Path; copying a provider's own redirect format.

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/73cbc975f293b0ee. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/oidc.go:579

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 9f775e8a12)