Respond with 400 status code and invalid_request for malformed content types where specifications require it - #51410
Conversation
michalvavrik
commented
Aug 3, 2026
- closes: Malformed Content-Type header causes HTTP 500 on token endpoints instead of RFC 6749 §5.2 compliant 400 #49964
There was a problem hiding this comment.
Pull request overview
Ensures malformed OAuth/OIDC Content-Type headers produce specification-compliant 400 invalid_request responses instead of HTTP 500 errors.
Changes:
- Adds reusable content-type validation across affected endpoints.
- Adds device-endpoint exception handling and resilient security-header processing.
- Adds integration coverage for seven OAuth/OIDC endpoints.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
tests/base/src/test/java/org/keycloak/tests/oauth/MalformedContentTypeTest.java |
Tests malformed headers across endpoints. |
services/src/main/java/org/keycloak/protocol/oidc/utils/ContentTypeValidationUtil.java |
Adds media-type validation utility. |
services/src/main/java/org/keycloak/protocol/oidc/par/endpoints/ParEndpoint.java |
Validates PAR content types. |
services/src/main/java/org/keycloak/protocol/oidc/grants/device/endpoints/DeviceEndpoint.java |
Maps unsupported media errors to OAuth responses. |
services/src/main/java/org/keycloak/protocol/oidc/grants/ciba/endpoints/BackchannelAuthenticationEndpoint.java |
Validates CIBA content types. |
services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenRevocationEndpoint.java |
Validates revocation content types. |
services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java |
Validates logout content types. |
services/src/main/java/org/keycloak/headers/DefaultSecurityHeadersProvider.java |
Handles malformed request media types safely. |
Suppressed comments (1)
tests/base/src/test/java/org/keycloak/tests/oauth/MalformedContentTypeTest.java:93
- This uses a refresh token where the endpoint requires an OIDC logout token. If the malformed header is accidentally accepted, logout-token validation still returns the same
400 invalid_request, so this test can pass without exercising the behavior under test; use a valid logout token or otherwise distinguish the content-type error from token-validation errors.
BackchannelLogoutResponse response = oauth.backchannelLogoutRequest(tokenResponse.getRefreshToken())
c46f1a7 to
2a4f231
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (2)
services/src/main/java/org/keycloak/protocol/oidc/utils/ContentTypeValidationUtil.java:25
- This hard-coded
ErrorResponseExceptiondrops CORS headers. PAR, token revocation, and legacy logout useCorsErrorResponseExceptionfor normal failures, but they invoke this helper before their CORS-aware error handling, so browsers cannot read the new malformed-header response; let CORS-enabled callers build a CORS-awareinvalid_requestresponse.
} catch (IllegalArgumentException e) {
throw new ErrorResponseException(Errors.INVALID_REQUEST, "The content-type header value did not correspond to a valid media type", Response.Status.BAD_REQUEST);
}
if (!requestMediaType.isCompatible(requiredMediaType)) {
throw new ErrorResponseException(Errors.INVALID_REQUEST, "The content-type header value does not match consumed media type " + requiredMediaType, Response.Status.BAD_REQUEST);
tests/base/src/test/java/org/keycloak/tests/oauth/MalformedContentTypeTest.java:129
- This added test does not exercise the incompatible-media branch in
ContentTypeValidationUtil:DeviceEndpointnever calls that utility, and its new exception mapper handles the@Consumesrejection before endpoint dispatch. Add a direct utility test (or remove the unreachable branch) rather than treating this as coverage for lines 24–25.
.header("Content-Type", MediaType.TEXT_HTML)
.send();
assertOAuthInvalidRequest(response);
2a4f231 to
8dbaf53
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (2)
services/src/main/java/org/keycloak/protocol/oidc/utils/ContentTypeValidationUtil.java:26
MediaType.isCompatibletreats wildcard values such as*/*andapplication/*as compatible, so these unsupportedContent-Typevalues bypass the new validation even though the endpoints require form encoding. Reject wildcard media types before applying compatibility.
if (!requestMediaType.isCompatible(requiredMediaType)) {
throw new ErrorResponseException(Errors.INVALID_REQUEST, "The content-type header value does not match consumed media type " + requiredMediaType, Response.Status.BAD_REQUEST);
services/src/main/java/org/keycloak/protocol/oidc/utils/ContentTypeValidationUtil.java:25
@Consumes(APPLICATION_FORM_URLENCODED)rejects a valid incompatible type during resource matching, before these endpoint methods call this utility, so this branch cannot turntext/htmlinto400 invalid_requestfor PAR, CIBA, revocation, or logout. The added incompatible-type test exercises onlyDeviceEndpoint's separate exception mapper; handleNotSupportedExceptionat each applicable resource boundary and cover a utility-backed endpoint as well.
if (!requestMediaType.isCompatible(requiredMediaType)) {
throw new ErrorResponseException(Errors.INVALID_REQUEST, "The content-type header value does not match consumed media type " + requiredMediaType, Response.Status.BAD_REQUEST);
8dbaf53 to
2accd2f
Compare
closes: keycloak#49964 Signed-off-by: Michal Vavřík <dev@michalvavrik.net>
2accd2f to
1efc84d
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (1)
tests/base/src/test/java/org/keycloak/tests/oauth/MalformedContentTypeTest.java:123
- This test exercises
DeviceEndpoint's separateNotSupportedExceptionmapper, not the compatibility branch inContentTypeValidationUtil; every utility call site is still tested only with values that fail during parsing. Add a valid incompatible case against PAR, CIBA, logout, or revocation, or remove that branch if it is not intended to be reachable.
void deviceAuthorizationEndpointIncompatibleType() {
Unreported flaky test detectedIf the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR. org.keycloak.testsuite.federation.ldap.LDAPGroupMapperTest#test01_ldapOnlyGroupMappings |
|
@mposolda not sure if you are the right person, if you know about more suitable review, just change it, thanks |
shawkins
left a comment
There was a problem hiding this comment.
So this gets pretty straight-forward once quarkusio/quarkus#55676 is available to us.
LGTM, thanks @michalvavrik
vmuzikar
left a comment
There was a problem hiding this comment.
As far as I can tell, LGTM. I think it's saafe to merge as it's quite straightforward.