{"record":{"id":"2d7417b6bc949419","repo":"prestodb/presto","slug":"userinfo-response-missing-principal-field-s","errorCode":null,"errorMessage":"/userinfo response missing principal field %s","messagePattern":"/userinfo response missing principal field (.+?)","errorType":"http","errorClass":"ParseException","httpStatus":401,"severity":"error","filePath":"presto-main/src/main/java/com/facebook/presto/server/security/oauth2/NimbusOAuth2Client.java","lineNumber":598,"sourceCode":"    }\n\n    // Using this parsing method for our /userinfo response from the IdP in order to allow for different principal\n    // fields as defined, and in the absence of the `sub` claim. This is a \"hack\" solution to alter the claims\n    // present in the response before calling the parser provided by the oidc sdk, which fails hard if the\n    // `sub` claim is missing.\n    public UserInfoResponse parse(HTTPResponse httpResponse)\n            throws ParseException\n    {\n        // Check status code first and only process payload if successful\n        if (httpResponse.getStatusCode() != 200) {\n            return UserInfoErrorResponse.parse(httpResponse);\n        }\n\n        JSONObject body = httpResponse.getBodyAsJSONObject();\n\n        String principal = (String) body.get(principalField);\n        if (principal == null) {\n            throw new ParseException(String.format(\"/userinfo response missing principal field %s\", principalField));\n        }\n\n        if (!principalField.equals(\"sub\") && body.get(\"sub\") == null) {\n            body.put(\"sub\", principal);\n            httpResponse.setBody(body.toJSONString());\n        }\n\n        Object audClaim = body.get(\"aud\");\n        // only validate aud claim if it exists\n        if (audClaim != null) {\n            List<String> audiences;\n\n            if (audClaim instanceof String) {\n                audiences = List.of((String) audClaim);\n            }\n            else if (audClaim instanceof List<?>) {\n                audiences = ((List<?>) audClaim).stream()\n                        .filter(String.class::isInstance)","sourceCodeStart":580,"sourceCodeEnd":616,"githubUrl":"https://github.com/prestodb/presto/blob/55bb57d202de3b926896fa966c2c4a44c779634e/presto-main/src/main/java/com/facebook/presto/server/security/oauth2/NimbusOAuth2Client.java#L580-L616","documentation":"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.","triggerScenarios":"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.","commonSituations":"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'.","solutions":["Set oauth2.principal-field to a claim actually present in the /userinfo response (often 'sub')","Request the required scopes (email, profile) in oauth2.scopes so optional fields are populated","Log/inspect the raw /userinfo JSON from the IdP to see available claims","If using a custom claim, confirm the IdP maps it into the userinfo response"],"exampleFix":"// before\n<property name=\"oauth2.principal-field\">email</property> // email scope not granted\n// after\n<property name=\"oauth2.principal-field\">sub</property>","handlingStrategy":"validation","validationCode":"// Pre-check config: the principal-field must be a claim the IdP userinfo response actually returns\nString body = fetchUserInfoJson();\nif (!body.contains(\"\\\"\" + principalField + \"\\\"\")) { throw new IllegalStateException(\"principal-field '\" + principalField + \"' absent from /userinfo response\"); }","typeGuard":"boolean hasPrincipalField(org.json.JSONObject body, String principalField) { return body != null && body.opt(principalField) instanceof String && !((String) body.opt(principalField)).isEmpty(); }","tryCatchPattern":"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; }","preventionTips":["Set oauth2.principal-field to 'sub' unless you are certain the IdP returns the custom claim","Request the scopes (email/profile) that populate the configured principal field","Dump the raw /userinfo JSON once during setup to confirm claim names","Re-validate config after IdP upgrades that change claim sets"],"tags":["oauth2","userinfo","parse-error","configuration"],"backgroundTag":"missing-claim-in-token","analyzedSha":"55bb57d202de3b926896fa966c2c4a44c779634e","analyzedAt":"2026-09-04T12:50:26.162Z","contentChangedAt":"2026-09-04T12:50:26.162Z","schemaVersion":2},"datasetVersion":"2026-09-11T21:17:09.523Z"}