ory/kratos · error

Issuer URL must be set to autodiscover PKCE support

Error message

Issuer URL must be set to autodiscover PKCE support

What it means

discoverPKCE autodetects whether an OIDC provider supports PKCE by performing OIDC discovery against the provider's IssuerURL. If IssuerURL is empty there is nothing to discover against, so it errors. discoverPKCE is called by maybePKCE when the provider config does not explicitly set a PKCE mode.

Solutions

  1. Set `issuer_url` for the OIDC provider in selfservice.methods.oidc.config.providers[] (e.g. https://accounts.google.com)
  2. Or set `pkce` explicitly to `force` or `never` in the provider config so autodiscovery is skipped
  3. For non-OIDC/OAuth2-only providers without an issuer, use pkce: never (or force if the provider supports it)
  4. Verify the issuer_url matches the provider's published issuer exactly (discovery fetches {issuer_url}/.well-known/openid-configuration)

Example fix

// before (config.yml)
selfservice:
  methods:
    oidc:
      config:
        providers:
          - id: google
            client_id: ...
            client_secret: ...
// after
selfservice:
  methods:
    oidc:
      config:
        providers:
          - id: google
            client_id: ...
            client_secret: ...
            issuer_url: https://accounts.google.com
            # or: pkce: never  (skip autodiscovery)
Defensive patterns

Strategy: validation

Validate before calling

for _, p := range cfg.SelfService.OIDC.Providers {
  if (p.PKCE == "" || p.PKCE == "auto") && p.IssuerURL == "" {
    return errors.Errorf("provider %q: issuer_url required for PKCE autodiscovery", p.ID)
  }
}

Prevention

When it happens

Trigger: Configuring an OIDC social-signin provider without an `issuer_url` while `pkce` is set to `auto` (or left unset so it falls back to autodiscovery).

Common situations: Copy-pasted provider configs that set client_id/client_secret and auth_url/token_url manually but omit issuer_url; custom OAuth2 (non-OIDC) providers that have no issuer at all.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of ory/kratos@b86338da04 (2026-09-07). Data as JSON: /api/errors/03157df3d79b5bbf. Report an issue: GitHub.

Appendix: source

Thrown at selfservice/strategy/oidc/pkce.go:65

	if p.Config().PKCE != "force" {
		// autodiscover PKCE support
		pkceSupported, err := discoverPKCE(ctx, d, p)
		if err != nil {
			d.Logger().WithError(err).Warnf("Failed to autodiscover PKCE support for provider %q. Continuing without PKCE.", p.Config().ID)
			return ""
		}
		if !pkceSupported {
			d.Logger().Infof("Provider %q does not advertise support for PKCE. Continuing without PKCE.", p.Config().ID)
			return ""
		}
	}
	return oauth2.GenerateVerifier()
}

func discoverPKCE(ctx context.Context, d pkceDependencies, p OAuth2Provider) (pkceSupported bool, err error) {
	if p.Config().IssuerURL == "" {
		return false, errors.New("Issuer URL must be set to autodiscover PKCE support")
	}

	ctx = gooidc.ClientContext(ctx, d.HTTPClient(ctx).HTTPClient)
	gp, err := gooidc.NewProvider(ctx, p.Config().IssuerURL)
	if err != nil {
		return false, errors.Wrap(err, "failed to initialize provider")
	}
	var claims struct {
		CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
	}
	if err := gp.Claims(&claims); err != nil {
		return false, errors.Wrap(err, "failed to deserialize provider claims")
	}
	return slices.Contains(claims.CodeChallengeMethodsSupported, "S256"), nil
}

View on GitHub (pinned to b86338da04)