prestodb/presto · error · ParseException

/userinfo response missing principal field %s

Error message

/userinfo response missing principal field %s

What it means

NimbusOAuth2Client's custom /userinfo response parser requires the configured principal field (oauth2.principal-field, default 'sub') to be present and non-null in the JSON body; otherwise it throws a ParseException.

Source

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

    }

    // Using this parsing method for our /userinfo response from the IdP in order to allow for different principal
    // fields as defined, and in the absence of the `sub` claim. This is a "hack" solution to alter the claims
    // present in the response before calling the parser provided by the oidc sdk, which fails hard if the
    // `sub` claim is missing.
    public UserInfoResponse parse(HTTPResponse httpResponse)
            throws ParseException
    {
        // Check status code first and only process payload if successful
        if (httpResponse.getStatusCode() != 200) {
            return UserInfoErrorResponse.parse(httpResponse);
        }

        JSONObject body = httpResponse.getBodyAsJSONObject();

        String principal = (String) body.get(principalField);
        if (principal == null) {
            throw new ParseException(String.format("/userinfo response missing principal field %s", principalField));
        }

        if (!principalField.equals("sub") && body.get("sub") == null) {
            body.put("sub", principal);
            httpResponse.setBody(body.toJSONString());
        }

        Object audClaim = body.get("aud");
        // only validate aud claim if it exists
        if (audClaim != null) {
            List<String> audiences;

            if (audClaim instanceof String) {
                audiences = List.of((String) audClaim);
            }
            else if (audClaim instanceof List<?>) {
                audiences = ((List<?>) audClaim).stream()
                        .filter(String.class::isInstance)

View on GitHub (pinned to 55bb57d202)

Solutions

  1. Set oauth2.principal-field to a claim actually present in the /userinfo response (often 'sub')
  2. Request the required scopes (email, profile) in oauth2.scopes so optional fields are populated
  3. Log/inspect the raw /userinfo JSON from the IdP to see available claims
  4. If using a custom claim, confirm the IdP maps it into the userinfo response

Example fix

// before
<property name="oauth2.principal-field">email</property> // email scope not granted
// after
<property name="oauth2.principal-field">sub</property>
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check config: the principal-field must be a claim the IdP userinfo response actually returns
String body = fetchUserInfoJson();
if (!body.contains("\"" + principalField + "\"")) { throw new IllegalStateException("principal-field '" + principalField + "' absent from /userinfo response"); }

Type guard

boolean hasPrincipalField(org.json.JSONObject body, String principalField) { return body != null && body.opt(principalField) instanceof String && !((String) body.opt(principalField)).isEmpty(); }

Try / catch

try { return parseUserInfoResponse(httpResponse); } catch (com.nimbusds.oauth2.sdk.ParseException e) { LOG.error("/userinfo missing principal field; check oauth2.principal-field and scopes"); throw e; }

Prevention

When it happens

Trigger: The IdP's /userinfo response lacks the field named by principalField — e.g. principal-field configured as 'email' but the token lacks the email scope so the response omits it, or a custom field name that the IdP never returns.

Common situations: Missing OIDC scopes (email/profile) at the IdP so optional claim fields are omitted, typo in principal-field config, IdP returning different claim names than configured, custom IdPs that only return 'sub'.

Related errors


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