{"record":{"id":"c7e87cf7920bc0ae","repo":"oauth2-proxy/oauth2-proxy","slug":"malformed-access-token-expected-3-parts-got-d","errorCode":null,"errorMessage":"malformed access token, expected 3 parts got %d","messagePattern":"malformed access token, expected 3 parts got (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"providers/keycloak_oidc.go","lineNumber":116,"sourceCode":"\tfor _, role := range roles {\n\t\ts.Groups = append(s.Groups, formatRole(role))\n\t}\n\treturn nil\n}\n\ntype realmAccess struct {\n\tRoles []string `json:\"roles\"`\n}\n\ntype accessClaims struct {\n\tRealmAccess    realmAccess            `json:\"realm_access\"`\n\tResourceAccess map[string]interface{} `json:\"resource_access\"`\n}\n\nfunc (p *KeycloakOIDCProvider) getAccessClaims(s *sessions.SessionState) (*accessClaims, error) {\n\tparts := strings.Split(s.AccessToken, \".\")\n\tif len(parts) < 2 {\n\t\treturn nil, fmt.Errorf(\"malformed access token, expected 3 parts got %d\", len(parts))\n\t}\n\n\tpayload, err := base64.RawURLEncoding.DecodeString(parts[1])\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"malformed access token, couldn't extract jwt payload: %v\", err)\n\t}\n\n\tvar claims accessClaims\n\tif err := json.Unmarshal(payload, &claims); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &claims, nil\n}\n\n// getClientRoles extracts client roles from the `resource_access` claim with\n// the format `client:role`.\n//\n// ResourceAccess format:","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/oauth2-proxy/oauth2-proxy/blob/33c2eb92dea78204f7a18bc2dfdbccc220f39257/providers/keycloak_oidc.go#L98-L134","documentation":"getAccessClaims splits the session's access token to read its JWT payload and count the dot-separated parts. If fewer than 2 parts are found, the token is not a usable JWT and this error is thrown. Despite the message mentioning 3 parts, the check rejects tokens with fewer than 2 parts.","triggerScenarios":"extractRoles calls getAccessClaims with a session whose AccessToken is empty, opaque, or otherwise lacks at least two dot-separated segments.","commonSituations":"Keycloak client configured without 'Accept OIDC ID tokens' so only opaque access tokens are issued; access token empty because the session was created from a refresh flow without tokens; a non-JWT bearer token supplied to CreateSessionFromToken.","solutions":["Ensure the Keycloak client issues JWT access tokens (check 'Access Token' format settings in the Keycloak client config)","Verify the session has a non-empty AccessToken before the refresh/extraction flow","Re-authenticate to get a fresh token set rather than relying on a partially populated session","If tokens are opaque by design, disable Keycloak-specific role extraction from the access token"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"parts := strings.Split(s.AccessToken, \".\")\nif len(parts) < 3 {\n    return errors.New(\"access token is not a JWT; skipping role extraction\")\n}","typeGuard":"func isJWTLike(token string) bool {\n    return strings.Count(token, \".\") >= 2\n}","tryCatchPattern":null,"preventionTips":["Ensure the Keycloak client issues JWT-format access tokens","Never store empty/opaque tokens in sessions expected to carry JWTs","Log token shape (segment count) at debug level to catch format drift","Skip role extraction gracefully when tokens are opaque"],"tags":["jwt","keycloak","malformed-token"],"backgroundTag":"invalid-argument-format","analyzedSha":"33c2eb92dea78204f7a18bc2dfdbccc220f39257","analyzedAt":"2026-09-06T08:51:53.077Z","contentChangedAt":"2026-09-06T08:51:53.077Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}