Skip to content

fix: pass JSON Web Tokens (JWT) as a header rather than as a request parameter#4818

Open
iansergeant42 wants to merge 24 commits into
v3.x.xfrom
reboot/jwt-tokens-passed-in-headers
Open

fix: pass JSON Web Tokens (JWT) as a header rather than as a request parameter#4818
iansergeant42 wants to merge 24 commits into
v3.x.xfrom
reboot/jwt-tokens-passed-in-headers

Conversation

@iansergeant42

Copy link
Copy Markdown
Contributor

Description

Prior to this change, JWT are passed between components as request parameters. This change moves their passing to the Authorization header.

Linked to # (issue)
Part of the # (epic)

Type of change

Please delete options that are not relevant.

  • fix: Bug fix (non-breaking change which fixes an issue)

Checklist:

  • My code follows the style guidelines of this project
  • PR title conforms to commit message guideline ## Commit Message Structure Guideline
  • I have commented my code, particularly in hard-to-understand areas. In JS I did provide JSDoc
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • The java tests in the area I was working on leverage @nested annotations
  • Any dependent changes have been merged and published in downstream modules

For more details about how should the code look like read the Contributing guideline

iansergeant and others added 13 commits July 13, 2026 16:03
Signed-off-by: Ian Sergeant <ian.sergeant@broadcom.com>
Co-authored-by: Andrew Jandacek <andrew.jandacek@broadcom.com>
Signed-off-by: Andrew Jandacek <andrew.jandacek@broadcom.com>
Co-authored-by: Andrew Jandacek <andrew.jandacek@broadcom.com>
Signed-off-by: iansergeant42 <129340641+iansergeant42@users.noreply.github.com>
Signed-off-by: iansergeant42 <129340641+iansergeant42@users.noreply.github.com>
…/CompoundAuthProvider.java

Signed-off-by: iansergeant42 <129340641+iansergeant42@users.noreply.github.com>
…/CompoundAuthProvider.java

Signed-off-by: iansergeant42 <129340641+iansergeant42@users.noreply.github.com>
Signed-off-by: Ian Sergeant <ian.sergeant@broadcom.com>
Signed-off-by: Ian Sergeant <ian.sergeant@broadcom.com>
@github-actions github-actions Bot added the Sensitive Sensitive change that requires peer review label Jul 15, 2026
Signed-off-by: Ian Sergeant <ian.sergeant@broadcom.com>
Signed-off-by: Ian Sergeant <ian.sergeant@broadcom.com>
Signed-off-by: Ian Sergeant <ian.sergeant@broadcom.com>
@iansergeant42
iansergeant42 marked this pull request as ready for review July 15, 2026 15:59
@DeleteMapping(path = "/invalidate/{token}")
public Mono<ResponseEntity<Void>> invalidateJwtToken(@PathVariable String token) {
@DeleteMapping(path = "/invalidate")
public Mono<ResponseEntity<Void>> invalidateJwtToken(ServerHttpRequest request) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use annotation @RequestHeader("Authorization")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, and it simplifies the code dramatically.

given()
.when()
.get(URI.create(basePath + INVALIDATE_JWT_ENDPOINT + "/" + token))
.get(URI.create(basePath + INVALIDATE_JWT_ENDPOINT))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given statement should have .header(AUTHORIZATION, "Baerer " + token)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added .header(AUTHORIZATION, "Bearer " + token)

.config(SslContext.clientCertApiml)
.when()
.delete(URI.create(basePath + INVALIDATE_JWT_ENDPOINT + "/" + token))
.header("Authorization", "Bearer " + token)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please, use instead org.springframework.http.HttpHeaders#AUTHORIZATION, probably not onlyhere

Suggested change
.header("Authorization", "Bearer " + token)
.header(AUTHORIZATION, "Bearer " + token)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

var invalidated = authenticationService.invalidateJwtTokenGateway(token, false, app);
final var authHeader = request.getHeaders(). getFirst("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return Mono.just(ResponseEntity.status(SC_UNAUTHORIZED).build());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The request is auhteticated by x509 and the token is just payload. If it is missing, it id 400 instead of 401.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to SC_BAD_REQUEST

return Mono.just(ResponseEntity.status(SC_UNAUTHORIZED).build());
}
final var jwtToken = authHeader.substring(7); //Starts with "Bearer "
var invalidated = authenticationService.invalidateJwtTokenGateway(jwtToken, false, app);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the token is missing?

@iansergeant42 iansergeant42 Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added an isEmpty() check. The token cannot be null at this stage as "Bearer ".substring(7) is empty. I'll add a test for this.

EDIT - use of @RequestHeader("Authorization") means that you see SC_BAD_REQUEST automatically. There's a test for this.

Comment on lines +118 to +123
final var authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
final var jwtToken = authHeader.substring(7);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comments as for the reactive alternative

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Largely the same fixes applied.

@richard-salac

Copy link
Copy Markdown
Contributor

forgotten, needs an update:

var url = EurekaUtils.getUrl(instanceInfo) + AuthController.CONTROLLER_PATH + "/invalidate/{}";

@richard-salac

Copy link
Copy Markdown
Contributor

@pavel-jares-bcm
Could we remove the logic from modulith when infinispan is the only supported caching mode?

Currently, there is a discrepancy in modulith that defines api/v1/auth/invalidate endpoint, but also api/v1/auth/invalidate/ in security chain definition and in ModulithAuthentication service. And yet no tests fails as the code is not used anymore and even before we did not test other caching storage modes.

@pavel-jares-bcm

Copy link
Copy Markdown
Contributor

@pavel-jares-bcm Could we remove the logic from modulith when infinispan is the only supported caching mode?

Currently, there is a discrepancy in modulith that defines api/v1/auth/invalidate endpoint, but also api/v1/auth/invalidate/ in security chain definition and in ModulithAuthentication service. And yet no tests fails as the code is not used anymore and even before we did not test other caching storage modes.

This code is valid for the cases when infinispan is not in use. It means in HA when caching storage is one of inMemory, redis, or VSAM. So we should avoid calling this endpoint just in case modulith and infinispan are active.

Signed-off-by: Ian Sergeant <ian.sergeant@broadcom.com>
Signed-off-by: Ian Sergeant <ian.sergeant@broadcom.com>
Signed-off-by: Ian Sergeant <ian.sergeant@broadcom.com>
Signed-off-by: Ian Sergeant <ian.sergeant@broadcom.com>
@sonarqubecloud

Copy link
Copy Markdown

}

@Test
void whenNoHeader_withCert_then401() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo in name 401 -> 400

Comment on lines +123 to +125
if (jwtToken.isEmpty()) { //it cannot be null, as "Bearer".substring(7) is empty, not null
response.setStatus(SC_BAD_REQUEST);
return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rely on @RequestHeader like in modulith?

try {
restTemplate.delete(url);
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + jwtToken);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's use org.apache.http.HttpHeaders.AUTHORIZATION whenever possible, not only in tests

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Sensitive Sensitive change that requires peer review size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants