Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for passing json values for header and payload #643

Merged
merged 10 commits into from
Mar 27, 2023
Merged
2 changes: 1 addition & 1 deletion lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ javadoc {
}

dependencies {
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.4.2'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.0'

testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70'
testImplementation 'junit:junit:4.13.2'
Expand Down
48 changes: 48 additions & 0 deletions lib/src/main/java/com/auth0/jwt/JWTCreator.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,27 @@ public Builder withHeader(Map<String, Object> headerClaims) {
return this;
}

/**
* Add specific Claims to set as the Header.
* If provided json is null then nothing is changed
*
* @param headerClaimsJson the values to use as Claims in the token's Header.
* @return this same Builder instance.
* @throws IllegalArgumentException if json value has invalid structure
*/
public Builder withHeader(String headerClaimsJson) throws IllegalArgumentException {
if (headerClaimsJson == null) {
return this;
}

try {
Map<String, Object> headerClaims = mapper.readValue(headerClaimsJson, HashMap.class);
return withHeader(headerClaims);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Invalid header JSON", e);
}
}

/**
* Add a specific Key Id ("kid") claim to the Header.
* If the {@link Algorithm} used to sign this token was instantiated with a KeyProvider,
Expand Down Expand Up @@ -467,6 +488,33 @@ public Builder withPayload(Map<String, ?> payloadClaims) throws IllegalArgumentE
return this;
}

/**
* Add specific Claims to set as the Payload. If the provided json is null then
* nothing is changed.
*
* <p>
* If any of the claims are invalid, none will be added.
* </p>
*
* @param payloadClaimsJson the values to use as Claims in the token's payload.
* @return this same Builder instance.
* @throws IllegalArgumentException if any of the claim keys or null,
* or if the values are not of a supported type,
* or if json value has invalid structure.
*/
public Builder withPayload(String payloadClaimsJson) throws IllegalArgumentException {
if (payloadClaimsJson == null) {
return this;
}

try {
Map<String, Object> payloadClaims = mapper.readValue(payloadClaimsJson, HashMap.class);
return withPayload(payloadClaims);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Invalid payload JSON", e);
}
}

private boolean validatePayload(Map<String, ?> payload) {
for (Map.Entry<String, ?> entry : payload.entrySet()) {
String key = entry.getKey();
Expand Down
78 changes: 75 additions & 3 deletions lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.ECDSAKeyProvider;
import com.auth0.jwt.interfaces.RSAKeyProvider;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Rule;
import org.junit.Test;
Expand Down Expand Up @@ -82,13 +83,48 @@ public void shouldAddHeaderClaim() {

@Test
public void shouldReturnBuilderIfNullMapIsProvided() {
Map<String, Object> nullMap = null;
String nullString = null;
String signed = JWTCreator.init()
.withHeader(null)
.withHeader(nullMap)
.withHeader(nullString)
.sign(Algorithm.HMAC256("secret"));

assertThat(signed, is(notNullValue()));
}

@Test
public void shouldSupportJsonValueHeaderWithNestedDataStructure() {
String stringClaim = "someClaim";
Integer intClaim = 1;
List<String> nestedListClaims = Arrays.asList("1", "2");
String claimsJson = "{\"stringClaim\": \"someClaim\", \"intClaim\": 1, \"nestedClaim\": { \"listClaim\": [ \"1\", \"2\" ]}}";

String jwt = JWTCreator.init()
.withHeader(claimsJson)
.sign(Algorithm.HMAC256("secret"));

assertThat(jwt, is(notNullValue()));
String[] parts = jwt.split("\\.");
String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8);

assertThat(headerJson, JsonMatcher.hasEntry("stringClaim", stringClaim));
assertThat(headerJson, JsonMatcher.hasEntry("intClaim", intClaim));
assertThat(headerJson, JsonMatcher.hasEntry("listClaim", nestedListClaims));
}

@Test
public void shouldFailWithIllegalArgumentExceptionForInvalidJsonForHeaderClaims() {
String invalidJson = "{ invalidJson }";

exception.expect(IllegalArgumentException.class);
exception.expectMessage("Invalid header JSON");

JWTCreator.init()
.withHeader(invalidJson)
.sign(Algorithm.HMAC256("secret"));
}

@Test
public void shouldOverwriteExistingHeaderIfHeaderMapContainsTheSameKey() {
Map<String, Object> header = new HashMap<>();
Expand All @@ -105,6 +141,7 @@ public void shouldOverwriteExistingHeaderIfHeaderMapContainsTheSameKey() {
assertThat(headerJson, JsonMatcher.hasEntry(HeaderParams.KEY_ID, "xyz"));
}


@Test
public void shouldOverwriteExistingHeadersWhenSettingSameHeaderKey() {
Map<String, Object> header = new HashMap<>();
Expand Down Expand Up @@ -715,8 +752,11 @@ public void withPayloadShouldAddBasicClaim() {

@Test
public void withPayloadShouldCreateJwtWithEmptyBodyIfPayloadNull() {
Map<String, Object> nullMap = null;
String nullString = null;
String jwt = JWTCreator.init()
.withPayload(null)
.withPayload(nullMap)
.withPayload(nullString)
.sign(Algorithm.HMAC256("secret"));

assertThat(jwt, is(notNullValue()));
Expand Down Expand Up @@ -921,10 +961,42 @@ public void withPayloadShouldSupportNullValuesEverywhere() {
assertThat(headerJson, JsonMatcher.hasEntry("objClaim", objClaim));
}

@Test
public void withPayloadShouldSupportJsonValueWithNestedDataStructure() {
String stringClaim = "someClaim";
Integer intClaim = 1;
List<String> nestedListClaims = Arrays.asList("1", "2");
String claimsJson = "{\"stringClaim\": \"someClaim\", \"intClaim\": 1, \"nestedClaim\": { \"listClaim\": [ \"1\", \"2\" ]}}";

String jwt = JWTCreator.init()
.withPayload(claimsJson)
.sign(Algorithm.HMAC256("secret"));

assertThat(jwt, is(notNullValue()));
String[] parts = jwt.split("\\.");
String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8);

assertThat(payloadJson, JsonMatcher.hasEntry("stringClaim", stringClaim));
assertThat(payloadJson, JsonMatcher.hasEntry("intClaim", intClaim));
assertThat(payloadJson, JsonMatcher.hasEntry("listClaim", nestedListClaims));
}

@Test
public void shouldFailWithIllegalArgumentExceptionForInvalidJsonForPayloadClaims() {
String invalidJson = "{ invalidJson }";

exception.expect(IllegalArgumentException.class);
exception.expectMessage("Invalid payload JSON");

JWTCreator.init()
.withPayload(invalidJson)
.sign(Algorithm.HMAC256("secret"));
}

@Test
public void shouldCreatePayloadWithNullForMap() {
String jwt = JWTCreator.init()
.withClaim("name", (Map<String,?>) null)
.withClaim("name", (Map<String, ?>) null)
.sign(Algorithm.HMAC256("secret"));
assertThat(jwt, is(notNullValue()));
assertTrue(JWT.decode(jwt).getClaim("name").isNull());
Expand Down