ory/kratos · error
token audience didn't match allowed audiences: %+v
Error message
token audience didn't match allowed audiences: %+v %w
What it means
After the verifyToken audience loop completes without success, the last verification error is wrapped as "token audience didn't match allowed audiences: %+v %w", listing all allowed audiences (tokenAudiences) and the underlying verification error. It is the user-facing aggregate of a failed ID-token verification across every configured audience.
Solutions
- Inspect the wrapped %w cause to distinguish the real reason: audience mismatch, token expired, invalid signature, or issuer mismatch.
- Verify issuer_url in the Kratos OIDC config matches the provider's issuer exactly (scheme, host, trailing slash).
- Ensure the token's aud matches config.ClientID or one of AdditionalIDTokenAudiences (add the right audience if needed).
- Check token expiry (exp/nbf claims) and provider clock skew; re-authenticate to obtain a fresh ID token.
Example fix
// before
cfg := &Configuration{IssuerURL: "https://provider.example.com/", ClientID: "a"}
// token aud: "b"
// after
cfg := &Configuration{IssuerURL: "https://provider.example.com/", ClientID: "b"}
// or: cfg.AdditionalIDTokenAudiences = []string{"b"} Defensive patterns
Strategy: try-catch
Validate before calling
const payload = JSON.parse(atob(idToken.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')))
if (payload.exp * 1000 < Date.now()) throw new Error('token expired before verification') Try / catch
if err != nil {
var oerr *oidc.Error
if errors.As(err, &oerr) {
switch oerr.ErrorType {
case oidc.ErrExpired:
// re-authenticate
default:
// check issuer/signature config
}
}
} Prevention
- Verify issuer_url matches the provider issuer exactly (including trailing slash)
- Check token exp/nbf and clock skew before debugging audience errors
- Refresh JWKS/provider metadata when keys rotate
- Read the wrapped cause (%w) — the aggregate message hides the real reason
When it happens
Trigger: Every oidc.Verifier.Verify call in verifyToken failed — either because aud did not match (see the audience error) or because the underlying verification failed (expired token, bad signature, wrong issuer). Raised from Verify whenever the loop's final err is non-nil.
Common situations: Expired or not-yet-valid ID tokens; signature verification failures from a stale/misconfigured JWKS or wrong issuer URL; audience mismatch as in error [35]; switching providers without updating issuer_url or client_id.
Related errors
- no audience matched the token's audience
- Private key is not ecdsa key
- verification requested for unknown address
- Issuer URL must be set to autodiscover PKCE support
- failed to decode PEM block containing private key
AI-assisted analysis of ory/kratos@b86338da04 (2026-09-07).
Data as JSON: /api/errors/813c84979ed232f7.
Report an issue: GitHub.
Appendix: source
Thrown at selfservice/strategy/oidc/token_verifier.go:40
verifier := oidc.NewVerifier(issuerURL, keySet, &oidc.Config{
ClientID: aud,
})
t0 := time.Now()
token, err = verifier.Verify(ctx, rawIDToken)
reqlog.AccumulateExternalLatency(ctx, time.Since(t0))
if err != nil && strings.Contains(err.Error(), "oidc: expected audience") {
// The audience is not the one we expect, try the next one
continue
} else if err != nil {
// Something else went wrong
return nil, err
}
// The token was verified successfully
break
}
if err != nil {
// None of the allowed audiences matched the audience in the token
return nil, fmt.Errorf("token audience didn't match allowed audiences: %+v %w", tokenAudiences, err)
}
claims := &Claims{}
var rawClaims map[string]any
if token == nil {
return nil, fmt.Errorf("token is nil")
}
if err := token.Claims(claims); err != nil {
return nil, err
}
if err = token.Claims(&rawClaims); err != nil {
return nil, err
}
claims.RawClaims = rawClaims
return claims, nil
}View on GitHub (pinned to b86338da04)