Skip to content
Merged
Show file tree
Hide file tree
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
52 changes: 33 additions & 19 deletions src/main/java/com/auth0/json/auth/TokenHolder.java
Original file line number Diff line number Diff line change
@@ -1,36 +1,43 @@
package com.auth0.json.auth;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

import java.util.Date;

/**
* Class that contains the Tokens obtained after a call to the {@link com.auth0.client.auth.AuthAPI} methods.
*/
@SuppressWarnings("unused")
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonDeserialize(using = TokenHolderDeserializer.class)
public class TokenHolder {

@JsonProperty("access_token")
private String accessToken;
@JsonProperty("id_token")
private String idToken;
@JsonProperty("refresh_token")
private String refreshToken;
@JsonProperty("token_type")
private String tokenType;
@JsonProperty("expires_in")
private long expiresIn;
@JsonProperty("scope")
private String scope;
private Date expiresAt;

// We need to maintain a default constructor for backwards-compatibility
@SuppressWarnings("unused")
public TokenHolder() {}
Comment thread
lbalmaceda marked this conversation as resolved.

public TokenHolder(String accessToken, String idToken, String refreshToken, String tokenType, long expiresIn, String scope, Date expiresAt) {
this.accessToken = accessToken;
this.idToken = idToken;
this.refreshToken = refreshToken;
this.tokenType = tokenType;
this.expiresIn = expiresIn;
this.scope = scope;
this.expiresAt = expiresAt;
}

/**
* Getter for the Auth0's access token.
*
* @return the access token or null if missing.
*/
@JsonProperty("access_token")
public String getAccessToken() {
return accessToken;
}
Expand All @@ -40,7 +47,6 @@ public String getAccessToken() {
*
* @return the id token or null if missing.
*/
@JsonProperty("id_token")
public String getIdToken() {
return idToken;
}
Expand All @@ -50,7 +56,6 @@ public String getIdToken() {
*
* @return the refresh token or null if missing.
*/
@JsonProperty("refresh_token")
public String getRefreshToken() {
return refreshToken;
}
Expand All @@ -60,28 +65,37 @@ public String getRefreshToken() {
*
* @return the token type or null if missing.
*/
@JsonProperty("token_type")
public String getTokenType() {
return tokenType;
}

/**
* Getter for the duration of this token in seconds since it was issued.
* Getter for the duration of the Access Token token in seconds from the time it was issued. If no Access Token was
* present the value will be zero.
*
* @return the number of seconds in which this token will expire, since the time it was issued.
* @return the number of seconds in which the Access Token token will expire, from the time it was issued.
*/
@JsonProperty("expires_in")
public long getExpiresIn() {
return expiresIn;
}

/**
* Get the expiration date of the Access Token. This value is <strong>not</strong> part of the actual token response from the
* API, but rather is calculated and provided for convenience.
*
* @return the date of the Access Token's expiration. If no Access Token was present on the response, returns {@code null}
*/
public Date getExpiresAt() {
return expiresAt;
}

/**
* Gets the granted scope value for this token.
*
* @return a space-delimited string of the granted scopes of this token.
*/
@JsonProperty
public String getScope() {
return scope;
}

}
51 changes: 51 additions & 0 deletions src/main/java/com/auth0/json/auth/TokenHolderDeserializer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.auth0.json.auth;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

import java.io.IOException;
import java.time.Instant;
import java.util.Date;

/**
* A custom Jackson Deserializer for the {@linkplain TokenHolder} object. It deserializes the JSON response and calculates
* an {@code expiresAt} date derived from the current time and the {@code expires_at} returned on the response.
*/
public class TokenHolderDeserializer extends StdDeserializer<TokenHolder> {

@SuppressWarnings("unused")
public TokenHolderDeserializer() {
this(null);
}

public TokenHolderDeserializer(Class<?> clazz) {
super(clazz);
}

@Override
public TokenHolder deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode node = p.getCodec().readTree(p);

JsonNode accessTokenNode = node.get("access_token");
JsonNode idTokenNode = node.get("id_token");
JsonNode refreshTokenNode = node.get("refresh_token");
JsonNode tokenTypeNode = node.get("token_type");
JsonNode scopeNode = node.get("scope");
JsonNode expiresInNode = node.get("expires_in");

String accessToken = accessTokenNode != null ? accessTokenNode.asText() : null;
String idToken = idTokenNode != null ? idTokenNode.asText() : null;
String refreshToken = refreshTokenNode != null ? refreshTokenNode.asText() : null;
String tokenType = tokenTypeNode != null ? tokenTypeNode.asText() : null;
String scope = scopeNode != null ? scopeNode.asText() : null;

// As a primitive, if expires_in is not sent on the response, value will be 0 instead of null
long expiresIn = expiresInNode != null ? expiresInNode.asLong() : 0L;
Comment thread
lbalmaceda marked this conversation as resolved.
Date expiresAt = expiresInNode != null ? Date.from(Instant.now().plusSeconds(expiresIn)) : null;

return new TokenHolder(accessToken, idToken, refreshToken, tokenType, expiresIn, scope, expiresAt);
}
}
78 changes: 67 additions & 11 deletions src/test/java/com/auth0/json/auth/TokenHolderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,81 @@
import com.auth0.json.JsonTest;
import org.junit.Test;

import java.time.Instant;
import java.util.Date;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.*;

public class TokenHolderTest extends JsonTest<TokenHolder> {

private static final String json = "{\"id_token\":\"eyJ0eXAiOiJKV1Qi...\",\"access_token\":\"A9CvPwFojaBI...\",\"refresh_token\":\"GEbRxBN...edjnXbL\",\"token_type\": \"bearer\",\"expires_in\":86000, \"scope\": \"openid profile email\"}";

@Test
public void shouldDeserialize() throws Exception {
String json = "{\"id_token\":\"eyJ0eXAiOiJKV1Qi...\",\"access_token\":\"A9CvPwFojaBI...\",\"refresh_token\":\"GEbRxBN...edjnXbL\",\"token_type\": \"bearer\",\"expires_in\":86000, \"scope\": \"openid profile email\"}";

TokenHolder holder = fromJSON(json, TokenHolder.class);

assertThat(holder, is(notNullValue()));
assertThat(holder.getAccessToken(), is("A9CvPwFojaBI..."));
assertThat(holder.getIdToken(), is("eyJ0eXAiOiJKV1Qi..."));
assertThat(holder.getRefreshToken(), is("GEbRxBN...edjnXbL"));
assertThat(holder.getTokenType(), is("bearer"));
assertThat(holder.getExpiresIn(), is(86000L));
assertThat(holder.getScope(), is("openid profile email"));

// allow for a small tolerance since now may be calculated at slightly different times in the test
assertThat(getExpectedExpiredAtDiff(86000, holder.getExpiresAt()), is(lessThanOrEqualTo(2L)));
Comment thread
lbalmaceda marked this conversation as resolved.

}

@Test
public void shouldDeserializeWithUnexpectedValues() throws Exception {
String json = "{\"id_token\":\"eyJ0eXAiOiJKV1Qi...\",\"access_token\":\"A9CvPwFojaBI...\",\"refresh_token\":\"GEbRxBN...edjnXbL\",\"token_type\": \"bearer\",\"expires_in\":86000, \"scope\": \"openid profile email\", \"some-key\": \"some-value\"}";

TokenHolder holder = fromJSON(json, TokenHolder.class);

assertThat(holder, is(notNullValue()));
assertThat(holder.getAccessToken(), is("A9CvPwFojaBI..."));
assertThat(holder.getIdToken(), is("eyJ0eXAiOiJKV1Qi..."));
assertThat(holder.getRefreshToken(), is("GEbRxBN...edjnXbL"));
assertThat(holder.getTokenType(), is("bearer"));
assertThat(holder.getExpiresIn(), is(86000L));
assertThat(holder.getScope(), is("openid profile email"));

// allow for a small tolerance since now may be calculated at slightly different times in the test
assertThat(getExpectedExpiredAtDiff(86000, holder.getExpiresAt()), is(lessThanOrEqualTo(2L)));
}

@Test
public void shouldDeserializeWithMissingFields() throws Exception {
String json = "{}";

TokenHolder holder = fromJSON(json, TokenHolder.class);

assertThat(holder, is(notNullValue()));
assertThat(holder.getAccessToken(), is(nullValue()));
assertThat(holder.getIdToken(), is(nullValue()));
assertThat(holder.getRefreshToken(), is(nullValue()));
assertThat(holder.getTokenType(), is(nullValue()));
assertThat(holder.getScope(), is(nullValue()));

// as a primitive, a missing value in the JSON will result in a value of zero on the value object.
assertThat(holder.getExpiresIn(), is(0L));
assertThat(holder.getExpiresAt(), is(nullValue()));

}

@Test
public void shouldHaveDefaultConstructor() {
// To ensure default constructor exists for backwards-compatibility
TokenHolder tokenHolder = new TokenHolder();
assertThat(tokenHolder, is(notNullValue()));
}

assertThat(holder.getAccessToken(), is(notNullValue()));
assertThat(holder.getIdToken(), is(notNullValue()));
assertThat(holder.getRefreshToken(), is(notNullValue()));
assertThat(holder.getTokenType(), is(notNullValue()));
assertThat(holder.getExpiresIn(), is(notNullValue()));
assertThat(holder.getScope(), is(notNullValue()));
private long getExpectedExpiredAtDiff(long expiresIn, Date expiresAt) {
Instant expectedExpiresAt = Instant.now().plusSeconds(expiresIn);
long expectedEpochSeconds = expectedExpiresAt.getEpochSecond();
long actualEpochSeconds = expiresAt.toInstant().getEpochSecond();
return Math.abs(expectedEpochSeconds - actualEpochSeconds);
}
}
}