argoproj/argo-workflows · error
failed to validate claims: %w
Error message
failed to validate claims: %w
What it means
The token decrypted successfully, but the claims failed validation against jwt.Expected{Issuer}: the `iss` claim does not match the configured OIDC issuer (or expiry/audience checks fail per Claims.Validate). Argo treats the session as untrusted.
Source
Thrown at server/auth/sso/sso.go:438
func isValidFinalRedirectURL(redirect string) bool {
// Copied from https://github.com/oauth2-proxy/oauth2-proxy/blob/ab448cf38e7c1f0740b3cc2448284775e39d9661/pkg/app/redirect/validator.go#L47
return strings.HasPrefix(redirect, "/") && !strings.HasPrefix(redirect, "//") && !invalidRedirectRegex.MatchString(redirect)
}
// authorize verifies a bearer token and pulls user information form the claims.
func (s *sso) Authorize(authorization string) (*types.Claims, error) {
tok, err := jwt.ParseEncrypted(strings.TrimPrefix(authorization, Prefix), []jose.KeyAlgorithm{jose.DIRECT}, []jose.ContentEncryption{jose.A256GCM})
if err != nil {
return nil, fmt.Errorf("failed to parse encrypted token: %w", err)
}
c := &types.Claims{}
if err := tok.Claims(s.encryptionKey, c); err != nil {
return nil, fmt.Errorf("failed to decrypt token: %w", err)
}
if err := c.Validate(jwt.Expected{Issuer: issuer}); err != nil {
return nil, fmt.Errorf("failed to validate claims: %w", err)
}
return c, nil
}
func (s *sso) getRedirectURL(r *http.Request) string {
if s.config.RedirectURL != "" {
return s.config.RedirectURL
}
proto := "http"
if r.URL.Scheme != "" {
proto = r.URL.Scheme
} else if s.secure {
proto = "https"
}
return fmt.Sprintf("%s://%s%soauth2/callback", proto, r.Host, s.baseHRef)View on GitHub (pinned to 35bff19146)
Solutions
- Re-login to obtain a token issued by the currently configured issuer
- Make sure `sso.issuer` matches the provider's actual issuer URL exactly (scheme, host, path)
- Check for clock skew between argo-server and the IdP (NTP)
- If using issuerAlias, keep it consistent with the value baked into existing tokens
Example fix
// before sso: issuer: http://dex:5556/dex # token iss is https://sso.example.com/dex // after sso: issuer: https://sso.example.com/dex
Defensive patterns
Strategy: validation
Validate before calling
// decode the JWE claims client-side and compare iss before calling
u := jwt.Claims{}
tok.Claims(key, &u)
if u.Issuer != expectedIssuer { return errors.New("issuer mismatch, re-login") } Try / catch
claims, err := sso.Authorize(auth)
if err != nil {
return status.Error(codes.Unauthenticated, "token rejected (issuer/expiry), re-login")
} Prevention
- Keep sso.issuer byte-exact with the IdP issuer (scheme/host/path)
- Avoid changing issuer or alias without forcing re-login
- Sync clocks with NTP to prevent exp failures
- Test the full login flow after any SSO config change
When it happens
Trigger: c.Validate(jwt.Expected{Issuer: issuer}) fails because the token's `iss` differs from the configured `sso.issuer` (issuer URL changed, http vs https, trailing slash mismatch, alias mismatch), or the token is expired.
Common situations: Changing the OIDC issuer/alias config while old tokens are still presented; clock skew causing `exp` validation failures; provider behind a proxy exposing a different host than configured.
Related errors
- failed to create JWT encrypter: %w
- failed to parse encrypted token: %w
- failed to decrypt token: %w
- failed to list SSO RBAC service accounts: %w
- failed to marshall claims: %w
AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03).
Data as JSON: /api/errors/3bde2c17e93d8d46.
Report an issue: GitHub.