prestodb/presto · error · ChallengeFailedException

Missing nonce

Error message

Missing nonce

What it means

NimbusOAuth2Client's OIDC authentication code flow requires a nonce (for replay protection bound into the ID token). getOAuth2Response throws ChallengeFailedException when the caller presents the authorization code without the nonce captured at challenge time.

Source

Thrown at presto-main/src/main/java/com/facebook/presto/server/security/oauth2/NimbusOAuth2Client.java:342

        public Request createAuthorizationRequest(String state, URI callbackUri)
        {
            String nonce = new Nonce().getValue();
            return new Request(
                    new AuthenticationRequest.Builder(CODE, scope, clientId, callbackUri)
                            .endpointURI(authUrl)
                            .state(new State(state))
                            .nonce(new Nonce(hashNonce(nonce)))
                            .build()
                            .toURI(),
                    Optional.of(nonce));
        }

        @Override
        public Response getOAuth2Response(String code, URI callbackUri, Optional<String> nonce)
                throws ChallengeFailedException
        {
            if (!nonce.isPresent()) {
                throw new ChallengeFailedException("Missing nonce");
            }

            OIDCTokenResponse tokenResponse = getTokenResponse(code, callbackUri, OIDCTokenResponse::parse);
            OIDCTokens tokens = tokenResponse.getOIDCTokens();
            validateTokens(tokens, nonce);
            return toResponse(tokens, Optional.empty());
        }

        @Override
        public Response refreshTokens(String refreshToken)
                throws ChallengeFailedException
        {
            OIDCTokenResponse tokenResponse = getTokenResponse(refreshToken, OIDCTokenResponse::parse);
            OIDCTokens tokens = tokenResponse.getOIDCTokens();
            validateTokens(tokens);
            return toResponse(tokens, Optional.of(refreshToken));
        }

View on GitHub (pinned to 55bb57d202)

Solutions

  1. Ensure the challenge step (startChallenge) stores the nonce and the callback passes it back via getOAuth2Response(code, callbackUri, Optional.of(nonce))
  2. Use sticky sessions / shared state store when running multiple coordinators behind a load balancer
  3. Clear stale oauth2 state cookies and retry the login flow in a single browser tab
  4. Check that the HttpServer authentication config consistently uses the OIDC (nonce) path

Example fix

// before
client.getOAuth2Response(code, callbackUri, Optional.empty());
// after
client.getOAuth2Response(code, callbackUri, Optional.of(savedNonce)); // nonce captured at challenge time
Defensive patterns

Strategy: validation

Validate before calling

if (nonce == null || nonce.isEmpty()) { throw new IllegalStateException("OAuth2 challenge state missing nonce; restart login flow"); }

Type guard

boolean hasNonce(java.util.Optional<String> nonce) { return nonce != null && nonce.isPresent() && !nonce.get().isEmpty(); }

Try / catch

try { return client.getOAuth2Response(code, callbackUri, nonce); } catch (ChallengeFailedException e) { restartAuthenticationChallenge(); throw e; }

Prevention

When it happens

Trigger: Completing the OAuth2 redirect flow where the nonce Optional passed to getOAuth2Response is empty — the challenge state was lost, the browser flow was initiated with nonce but the callback lost it, or authentication was initiated with a nonce-less flow but validated against an OIDC client requiring it.

Common situations: Server restart between challenge and callback wiping the nonce from the state store, multiple browser tabs interfering with the oauth2 state cookie, misconfigured load balancer routing callback to a different node that lacks the nonce state, or OAuth2Client configuration toggled from oauth2 to OIDC mid-flow.

Related errors


AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04). Data as JSON: /api/errors/14af6def86281a5b. Report an issue: GitHub.