diff --git a/README.md b/README.md
index 1c283f44..569ac275 100644
--- a/README.md
+++ b/README.md
@@ -619,7 +619,7 @@ AppAuthConfiguration appAuthConfig = new AppAuthConfiguration.Builder()
ID Token validation was introduced in `0.8.0` but not all authorization servers or configurations support it correctly.
-- For testing environments [setSkipIssuerHttpsCheck](https://github.com/openid/AppAuth-Android/blob/master/library/java/net/openid/appauth/AppAuthConfiguration.java#L129) can be used to bypass the fact the issuer needs to be HTTPS.
+- For testing environments [setSkipIssuerHttpsCheck](https://github.com/openid/AppAuth-Android/blob/master/library/java/net/openid/appauth/AppAuthConfiguration.java#L143) can be used to bypass the fact the issuer needs to be HTTPS.
```java
AppAuthConfiguration appAuthConfig = new AppAuthConfiguration.Builder()
@@ -635,6 +635,22 @@ AuthorizationRequest authRequest = authRequestBuilder
.build();
```
+- To change the default allowed time skew of 10 minutes for the issue time, [setAllowedIssueTimeSkew](https://github.com/openid/AppAuth-Android/blob/master/library/java/net/openid/appauth/AppAuthConfiguration.java#L159) can be used.
+
+```java
+AppAuthConfiguration appAuthConfig = new AppAuthConfiguration.Builder()
+ .setAllowedIssueTimeSkew(THIRTY_MINUTES_IN_SECONDS)
+ .build()
+```
+
+- For testing environments [setSkipIssueTimeValidation](https://github.com/openid/AppAuth-Android/blob/master/library/java/net/openid/appauth/AppAuthConfiguration.java#L151) can be used to bypass the issue time validation.
+
+```java
+AppAuthConfiguration appAuthConfig = new AppAuthConfiguration.Builder()
+ .setSkipIssueTimeValidation(true)
+ .build()
+```
+
## Dynamic client registration
AppAuth supports the
diff --git a/library/java/net/openid/appauth/AppAuthConfiguration.java b/library/java/net/openid/appauth/AppAuthConfiguration.java
index 313541df..5e6f8946 100644
--- a/library/java/net/openid/appauth/AppAuthConfiguration.java
+++ b/library/java/net/openid/appauth/AppAuthConfiguration.java
@@ -42,13 +42,21 @@ public class AppAuthConfiguration {
private final boolean mSkipIssuerHttpsCheck;
+ private final boolean mSkipIssueTimeValidation;
+
+ private final Long mAllowedIssueTimeSkew;
+
private AppAuthConfiguration(
@NonNull BrowserMatcher browserMatcher,
@NonNull ConnectionBuilder connectionBuilder,
- Boolean skipIssuerHttpsCheck) {
+ Boolean skipIssuerHttpsCheck,
+ Boolean skipIssueTimeValidation,
+ Long allowedIssueTimeSkew) {
mBrowserMatcher = browserMatcher;
mConnectionBuilder = connectionBuilder;
mSkipIssuerHttpsCheck = skipIssuerHttpsCheck;
+ mSkipIssueTimeValidation = skipIssueTimeValidation;
+ mAllowedIssueTimeSkew = allowedIssueTimeSkew;
}
/**
@@ -76,6 +84,22 @@ public ConnectionBuilder getConnectionBuilder() {
*/
public boolean getSkipIssuerHttpsCheck() { return mSkipIssuerHttpsCheck; }
+ /**
+ * Returns true
if the ID token issue time validation is disables,
+ * otherwise false
.
+ *
+ * @see Builder#setSkipIssueTimeValidation(Boolean)
+ */
+ public boolean getSkipIssueTimeValidation() { return mSkipIssueTimeValidation; }
+
+ /**
+ * Returns the time in seconds that the ID token issue time is allowed to be
+ * skewed.
+ *
+ * @see Builder#setAllowedIssueTimeSkew(Long)
+ */
+ public Long getAllowedIssueTimeSkew() { return mAllowedIssueTimeSkew; }
+
/**
* Creates {@link AppAuthConfiguration} instances.
*/
@@ -84,6 +108,8 @@ public static class Builder {
private BrowserMatcher mBrowserMatcher = AnyBrowserMatcher.INSTANCE;
private ConnectionBuilder mConnectionBuilder = DefaultConnectionBuilder.INSTANCE;
private boolean mSkipIssuerHttpsCheck;
+ private boolean mSkipIssueTimeValidation;
+ private Long mAllowedIssueTimeSkew;
private boolean mSkipNonceVerification;
/**
@@ -119,6 +145,22 @@ public Builder setSkipIssuerHttpsCheck(Boolean skipIssuerHttpsCheck) {
return this;
}
+ /**
+ * Disables issue time validation for the id token.
+ */
+ public Builder setSkipIssueTimeValidation(Boolean skipIssueTimeValidation) {
+ mSkipIssueTimeValidation = skipIssueTimeValidation;
+ return this;
+ }
+
+ /**
+ * Sets the allowed time skew in seconds for id token issue time validation.
+ */
+ public Builder setAllowedIssueTimeSkew(Long allowedIssueTimeSkew) {
+ mAllowedIssueTimeSkew = allowedIssueTimeSkew;
+ return this;
+ }
+
/**
* Creates the instance from the configured properties.
*/
@@ -127,7 +169,9 @@ public AppAuthConfiguration build() {
return new AppAuthConfiguration(
mBrowserMatcher,
mConnectionBuilder,
- mSkipIssuerHttpsCheck
+ mSkipIssuerHttpsCheck,
+ mSkipIssueTimeValidation,
+ mAllowedIssueTimeSkew
);
}
diff --git a/library/java/net/openid/appauth/AuthorizationService.java b/library/java/net/openid/appauth/AuthorizationService.java
index b3bdf7ed..56f59f0e 100644
--- a/library/java/net/openid/appauth/AuthorizationService.java
+++ b/library/java/net/openid/appauth/AuthorizationService.java
@@ -506,7 +506,9 @@ public void performTokenRequest(
mClientConfiguration.getConnectionBuilder(),
SystemClock.INSTANCE,
callback,
- mClientConfiguration.getSkipIssuerHttpsCheck())
+ mClientConfiguration.getSkipIssuerHttpsCheck(),
+ mClientConfiguration.getSkipIssueTimeValidation(),
+ mClientConfiguration.getAllowedIssueTimeSkew())
.execute();
}
@@ -585,6 +587,8 @@ private static class TokenRequestTask
private TokenResponseCallback mCallback;
private Clock mClock;
private boolean mSkipIssuerHttpsCheck;
+ private boolean mSkipIssueTimeValidation;
+ private Long mAllowedIssueTimeSkew;
private AuthorizationException mException;
@@ -593,13 +597,17 @@ private static class TokenRequestTask
@NonNull ConnectionBuilder connectionBuilder,
Clock clock,
TokenResponseCallback callback,
- Boolean skipIssuerHttpsCheck) {
+ Boolean skipIssuerHttpsCheck,
+ Boolean skipissueTimeValidation,
+ Long allowedIssueTimeSkew) {
mRequest = request;
mClientAuthentication = clientAuthentication;
mConnectionBuilder = connectionBuilder;
mClock = clock;
mCallback = callback;
mSkipIssuerHttpsCheck = skipIssuerHttpsCheck;
+ mSkipIssueTimeValidation = skipissueTimeValidation;
+ mAllowedIssueTimeSkew = allowedIssueTimeSkew;
}
@Override
@@ -710,7 +718,9 @@ protected void onPostExecute(JSONObject json) {
idToken.validate(
mRequest,
mClock,
- mSkipIssuerHttpsCheck
+ mSkipIssuerHttpsCheck,
+ mSkipIssueTimeValidation,
+ mAllowedIssueTimeSkew
);
} catch (AuthorizationException ex) {
mCallback.onTokenRequestCompleted(null, ex);
diff --git a/library/java/net/openid/appauth/IdToken.java b/library/java/net/openid/appauth/IdToken.java
index 4a4556ef..96817170 100644
--- a/library/java/net/openid/appauth/IdToken.java
+++ b/library/java/net/openid/appauth/IdToken.java
@@ -204,12 +204,14 @@ static IdToken from(String token) throws JSONException, IdTokenException {
@VisibleForTesting
void validate(@NonNull TokenRequest tokenRequest, Clock clock) throws AuthorizationException {
- validate(tokenRequest, clock, false);
+ validate(tokenRequest, clock, false, false, null);
}
void validate(@NonNull TokenRequest tokenRequest,
Clock clock,
- boolean skipIssuerHttpsCheck) throws AuthorizationException {
+ boolean skipIssuerHttpsCheck,
+ boolean skipIssueTimeValidation,
+ @Nullable Long allowedIssueTimeSkew) throws AuthorizationException {
// OpenID Connect Core Section 3.1.3.7. rule #1
// Not enforced: AppAuth does not support JWT encryption.
@@ -276,13 +278,16 @@ void validate(@NonNull TokenRequest tokenRequest,
new IdTokenException("ID Token expired"));
}
- // OpenID Connect Core Section 3.1.3.7. rule #10
- // Validates that the issued at time is not more than +/- 10 minutes on the current
- // time.
- if (Math.abs(nowInSeconds - this.issuedAt) > TEN_MINUTES_IN_SECONDS) {
- throw AuthorizationException.fromTemplate(GeneralErrors.ID_TOKEN_VALIDATION_ERROR,
- new IdTokenException("Issued at time is more than 10 minutes "
- + "before or after the current time"));
+
+ if (!skipIssueTimeValidation) {
+ // OpenID Connect Core Section 3.1.3.7. rule #10
+ // Validates that the issued at time is not more than the +/- configured allowed time skew,
+ // or +/- 10 minutes as a default, on the current time.
+ if (Math.abs(nowInSeconds - this.issuedAt) > (allowedIssueTimeSkew == null ? TEN_MINUTES_IN_SECONDS : allowedIssueTimeSkew)) {
+ throw AuthorizationException.fromTemplate(GeneralErrors.ID_TOKEN_VALIDATION_ERROR,
+ new IdTokenException("Issued at time is more than 10 minutes "
+ + "before or after the current time"));
+ }
}
// Only relevant for the authorization_code response type
diff --git a/library/javatests/net/openid/appauth/IdTokenTest.java b/library/javatests/net/openid/appauth/IdTokenTest.java
index 13944f7c..7b5a679e 100644
--- a/library/javatests/net/openid/appauth/IdTokenTest.java
+++ b/library/javatests/net/openid/appauth/IdTokenTest.java
@@ -272,7 +272,7 @@ public void testValidate_shouldSkipNonHttpsIssuer()
.setRedirectUri(TEST_APP_REDIRECT_URI)
.build();
Clock clock = SystemClock.INSTANCE;
- idToken.validate(tokenRequest, clock, true);
+ idToken.validate(tokenRequest, clock, true, false, null);
}
@Test(expected = AuthorizationException.class)
@@ -464,6 +464,60 @@ public void testValidate_shouldFailOnIssuedAtOverTenMinutesAgo() throws Authoriz
idToken.validate(tokenRequest, clock);
}
+ @Test
+ public void testValidate_withSkipIssueTimeValidation() throws AuthorizationException {
+ Long nowInSeconds = SystemClock.INSTANCE.getCurrentTimeMillis() / 1000;
+ Long anHourInSeconds = (long) (60 * 60);
+ IdToken idToken = new IdToken(
+ TEST_ISSUER,
+ TEST_SUBJECT,
+ Collections.singletonList(TEST_CLIENT_ID),
+ nowInSeconds,
+ nowInSeconds - (anHourInSeconds * 2),
+ TEST_NONCE,
+ TEST_CLIENT_ID
+ );
+ TokenRequest tokenRequest = getAuthCodeExchangeRequestWithNonce();
+ Clock clock = SystemClock.INSTANCE;
+ idToken.validate(tokenRequest, clock, false, true, null);
+ }
+
+ @Test(expected = AuthorizationException.class)
+ public void testValidate_shouldFailOnIssuedAtOverConfiguredTimeSkew() throws AuthorizationException {
+ Long nowInSeconds = SystemClock.INSTANCE.getCurrentTimeMillis() / 1000;
+ Long anHourInSeconds = (long) (60 * 60);
+ IdToken idToken = new IdToken(
+ TEST_ISSUER,
+ TEST_SUBJECT,
+ Collections.singletonList(TEST_CLIENT_ID),
+ nowInSeconds,
+ nowInSeconds - anHourInSeconds - 1,
+ TEST_NONCE,
+ TEST_CLIENT_ID
+ );
+ TokenRequest tokenRequest = getAuthCodeExchangeRequestWithNonce();
+ Clock clock = SystemClock.INSTANCE;
+ idToken.validate(tokenRequest, clock, false, false, anHourInSeconds);
+ }
+
+ @Test
+ public void testValidate_withConfiguredTimeSkew() throws AuthorizationException {
+ Long nowInSeconds = SystemClock.INSTANCE.getCurrentTimeMillis() / 1000;
+ Long anHourInSeconds = (long) (60 * 60);
+ IdToken idToken = new IdToken(
+ TEST_ISSUER,
+ TEST_SUBJECT,
+ Collections.singletonList(TEST_CLIENT_ID),
+ nowInSeconds,
+ nowInSeconds - anHourInSeconds,
+ TEST_NONCE,
+ TEST_CLIENT_ID
+ );
+ TokenRequest tokenRequest = getAuthCodeExchangeRequestWithNonce();
+ Clock clock = SystemClock.INSTANCE;
+ idToken.validate(tokenRequest, clock, false, false, anHourInSeconds);
+ }
+
@Test(expected = AuthorizationException.class)
public void testValidate_shouldFailOnNonceMismatch() throws AuthorizationException {
Long nowInSeconds = SystemClock.INSTANCE.getCurrentTimeMillis() / 1000;