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

  1. Inspect the wrapped %w cause to distinguish the real reason: audience mismatch, token expired, invalid signature, or issuer mismatch.
  2. Verify issuer_url in the Kratos OIDC config matches the provider's issuer exactly (scheme, host, trailing slash).
  3. Ensure the token's aud matches config.ClientID or one of AdditionalIDTokenAudiences (add the right audience if needed).
  4. 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

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


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)