From 4dcba3a1d894fa3a363bd81f1ce3a031dcd715d7 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Thu, 16 Jul 2026 10:15:42 +0100 Subject: [PATCH] Name the actual OIDC token verification failure on the login page Every ID token verification failure rendered the issuer-mismatch advice, sending users with audience or clock problems down the wrong path and telling users with a genuine issuer mismatch nothing they had not already checked (#1533). Map the distinct verification failures (issuer, audience, expiry) to their own error codes and give each accurate login-page copy pointing at the server log's got/want detail. --- frontend-modern/src/components/Login.tsx | 8 +++++++- internal/api/oidc_handlers.go | 14 +++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/frontend-modern/src/components/Login.tsx b/frontend-modern/src/components/Login.tsx index ff4c53845..c28970cfb 100644 --- a/frontend-modern/src/components/Login.tsx +++ b/frontend-modern/src/components/Login.tsx @@ -84,7 +84,13 @@ export const Login: Component = (props) => { case 'session_failed': return 'Login succeeded but we could not create a session. Try again.'; case 'invalid_id_token': - return 'ID token verification failed. Check that OIDC_ISSUER_URL matches the issuer claim in your provider tokens (check server logs for details).'; + return 'ID token verification failed. The server log records the exact reason for this login attempt.'; + case 'issuer_mismatch': + return 'The token was issued by a different issuer than the one configured. The server log shows the expected and received issuer values for this login attempt.'; + case 'audience_mismatch': + return 'The token was issued for a different client ID than the one configured in Pulse. Check the client ID against your provider application.'; + case 'token_expired': + return 'The identity provider returned an already-expired token. Check that the clocks on Pulse and the provider are in sync.'; case 'invalid_signature_alg': return "The identity provider is issuing HS256 tokens. Configure it to sign ID tokens with RS256 (see your IdP's OIDC settings)."; case 'invalid_nonce': diff --git a/internal/api/oidc_handlers.go b/internal/api/oidc_handlers.go index 7775bb666..f2a88ad5b 100644 --- a/internal/api/oidc_handlers.go +++ b/internal/api/oidc_handlers.go @@ -581,9 +581,21 @@ func (r *Router) handleSSOOIDCCallback(w http.ResponseWriter, req *http.Request) idToken, err := service.verifier.Verify(ctx, rawIDToken) if err != nil { + // Distinguish the verification failure modes so the login page can + // name the actual problem. The generic code used to render issuer + // advice for every failure, sending users with audience or clock + // problems down the wrong path (#1533). errorCode := "invalid_id_token" - if strings.Contains(err.Error(), "unexpected signature algorithm") { + errMsg := err.Error() + switch { + case strings.Contains(errMsg, "unexpected signature algorithm"): errorCode = "invalid_signature_alg" + case strings.Contains(errMsg, "issued by a different provider"): + errorCode = "issuer_mismatch" + case strings.Contains(errMsg, "expected audience"): + errorCode = "audience_mismatch" + case strings.Contains(errMsg, "token is expired"): + errorCode = "token_expired" } log.Error().Err(err).Str("provider_id", providerID).Msg("Failed to verify ID token") LogAuditEventForTenant(GetOrgID(req.Context()), "sso_oidc_login", "", GetClientIP(req), req.URL.Path, false, "ID token verification failed: "+err.Error())