Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@
import static org.apache.commons.text.StringEscapeUtils.*;

/**
* @description Validates a JWT on requests (signature via JWKS, required exp/sub) and exposes claims in exchange properties ("jwt").
* @yaml
* <pre><code>
* @description Validates a JWT on requests (signature via JWKS, required
* exp/sub) and exposes claims in exchange properties ("jwt").
* @yaml <pre><code>
* jwtAuth:
* expectedAud: my-audience
* expectedTid: 67c859d3-0cd4-4a99-86db-088bed1a9601
Expand Down Expand Up @@ -65,7 +65,6 @@ public static String ERROR_JWT_VALUE_NOT_PRESENT(String key) {
String expectedAud;
String expectedTid;


public JwtAuthInterceptor() {
name = "jwt checker.";
setAppliedFlow(of(REQUEST));
Expand All @@ -74,8 +73,9 @@ public JwtAuthInterceptor() {
@Override
public void init() {
super.init();
if(jwtRetriever == null)
jwtRetriever = new HeaderJwtRetriever("Authorization","Bearer");
if (jwtRetriever == null) {
jwtRetriever = new HeaderJwtRetriever("Authorization", "Bearer");
}

jwks.init(router);
}
Expand All @@ -101,12 +101,14 @@ public Outcome handleRequest(Exchange exc) {
.buildAndSetResponse(exc);
return RETURN;
} catch (InvalidJwtException e) {
log.error("JWT validation failed: {}", e.getMessage(), e);
Copy link
Copy Markdown
Member

@predic8 predic8 May 19, 2026

Choose a reason for hiding this comment

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

Use level info. That wrong input is sent is normal operation. INFO is default log level, so it appears in the log.

ProblemDetails.security(router.getConfiguration().isProduction(), "jwt-auth")
.detail(ERROR_VALIDATION_FAILED)
.detail(e.getMessage())
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

.detail("JWT validation failed: " +e.getMessage())

.addSubSee(ERROR_VALIDATION_FAILED_ID)
.stacktrace(false)
.status(400)
.buildAndSetResponse(exc);

return RETURN;
} catch (Exception e) {
ProblemDetails.security(router.getConfiguration().isProduction(), "jwt-auth")
Expand All @@ -120,8 +122,9 @@ public Outcome handleRequest(Exchange exc) {
}

public Outcome handleJwt(Exchange exc, String jwt) throws JWTException, JsonProcessingException, InvalidJwtException {
if (jwt == null)
if (jwt == null) {
throw new JWTException(ERROR_JWT_NOT_FOUND, ERROR_JWT_NOT_FOUND_ID);
}

var decodedJwt = new JsonWebToken(jwt);
var kid = decodedJwt.getHeader().kid();
Expand All @@ -132,7 +135,7 @@ public Outcome handleJwt(Exchange exc, String jwt) throws JWTException, JsonProc

Map<String, Object> jwtClaims = createValidator(key).processToClaims(jwt).getClaimsMap();

exc.getProperties().put("jwt",jwtClaims);
exc.getProperties().put("jwt", jwtClaims);

new JWTSecurityScheme(jwtClaims).add(exc);

Expand All @@ -146,17 +149,19 @@ private JwtConsumer createValidator(RsaJsonWebKey key) {
.setRequireSubject()
.setVerificationKey(key.getRsaPublicKey());

if (acceptAnyAud())
jwtConsumerBuilder.setSkipDefaultAudienceValidation();
else {
if (expectedAud != null && !expectedAud.isEmpty())
if (acceptAnyAud()) {
jwtConsumerBuilder.setSkipDefaultAudienceValidation();
}else {
if (expectedAud != null && !expectedAud.isEmpty()) {
jwtConsumerBuilder
.setExpectedAudience(expectedAud);
}
}

if (expectedTid != null && !expectedTid.isEmpty())
if (expectedTid != null && !expectedTid.isEmpty()) {
jwtConsumerBuilder
.registerValidator(new TidValidator(expectedTid));
}

return jwtConsumerBuilder.build();
}
Expand Down Expand Up @@ -193,8 +198,11 @@ public String getExpectedTid() {

/**
* @description
* <p>Expected audience ('aud') value of the token.</p>
* <p>Use "any!!" to allow any audience value. This is strongly discouraged.</p>
* <p>
* Expected audience ('aud') value of the token.</p>
* <p>
* Use "any!!" to allow any audience value. This is strongly
* discouraged.</p>
*/
@MCAttribute
public void setExpectedAud(String expectedAud) {
Expand All @@ -203,7 +211,8 @@ public void setExpectedAud(String expectedAud) {

/**
* @description
* <p>Expected tenant ID ('tid') value of the token.</p>
* <p>
* Expected tenant ID ('tid') value of the token.</p>
* @default not set
* @example 67c869d3-0cd4-4a99-86db-088bed1a9601
*/
Expand All @@ -219,11 +228,11 @@ public String getShortDescription() {

@Override
public String getLongDescription() {
return "Checks for a valid JWT.<br/>" +
(acceptAnyAud() ?
"Accepts any value for the <font style=\"font-family: monospace\">aud</font> field. <b>THIS IS STRONGLY DISCOURAGED!</b><br/>" :
"Accepts <font style=\"font-family: monospace\">" + escapeHtml4(expectedAud) + "</font> as valid value for the <font style=\"font-family: monospace\">aud</font> payload entry.<br/>") +
(jwks != null ? "Validates the JWT signature against " + jwks.getLongDescription() + " ." : "");
return "Checks for a valid JWT.<br/>"
+ (acceptAnyAud()
? "Accepts any value for the <font style=\"font-family: monospace\">aud</font> field. <b>THIS IS STRONGLY DISCOURAGED!</b><br/>"
: "Accepts <font style=\"font-family: monospace\">" + escapeHtml4(expectedAud) + "</font> as valid value for the <font style=\"font-family: monospace\">aud</font> payload entry.<br/>")
+ (jwks != null ? "Validates the JWT signature against " + jwks.getLongDescription() + " ." : "");
}

}