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
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
42 changes: 42 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,24 @@ 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 JsonProcessingException if json value has invalid structure
*/
public Builder withHeader(String headerClaimsJson) throws JsonProcessingException {
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of throwing an exception from jackson, let's catch and throw an IllegalArgumentException with the nested cause, to be consistent with the other methods and not leak the jackson dependency.

if (headerClaimsJson == null) {
return this;
}

Map<String, Object> headerClaims = mapper.readValue(headerClaimsJson, HashMap.class);

return withHeader(headerClaims);
}

/**
* 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 +485,30 @@ 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.
* @throws JsonProcessingException if json value has invalid structure
*/
public Builder withPayload(String payloadClaimsJson) throws IllegalArgumentException, JsonProcessingException {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same thing here re. the jackson exception.

if (payloadClaimsJson == null) {
return this;
}

Map<String, Object> payloadClaims = mapper.readValue(payloadClaimsJson, HashMap.class);

return withPayload(payloadClaims);
}

private boolean validatePayload(Map<String, ?> payload) {
for (Map.Entry<String, ?> entry : payload.entrySet()) {
String key = entry.getKey();
Expand Down
56 changes: 52 additions & 4 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 @@ -81,14 +82,37 @@ public void shouldAddHeaderClaim() {
}

@Test
public void shouldReturnBuilderIfNullMapIsProvided() {
public void shouldReturnBuilderIfNullMapIsProvided() throws JsonProcessingException {
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() throws JsonProcessingException {
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 shouldOverwriteExistingHeaderIfHeaderMapContainsTheSameKey() {
Map<String, Object> header = new HashMap<>();
Expand All @@ -105,6 +129,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 @@ -714,9 +739,12 @@ public void withPayloadShouldAddBasicClaim() {
}

@Test
public void withPayloadShouldCreateJwtWithEmptyBodyIfPayloadNull() {
public void withPayloadShouldCreateJwtWithEmptyBodyIfPayloadNull() throws JsonProcessingException {
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,6 +949,26 @@ public void withPayloadShouldSupportNullValuesEverywhere() {
assertThat(headerJson, JsonMatcher.hasEntry("objClaim", objClaim));
}

@Test
public void withPayloadShouldSupportJsonValueWithNestedDataStructure() throws JsonProcessingException {
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 shouldCreatePayloadWithNullForMap() {
String jwt = JWTCreator.init()
Expand Down