diff --git a/README.md b/README.md
index 1c283f44..d4e61978 100644
--- a/README.md
+++ b/README.md
@@ -261,7 +261,7 @@ this redirect URI.
We recommend using a custom scheme based redirect URI (i.e. those of form
`my.scheme:/path`), as this is the most widely supported across all versions of
-Android. To avoid conflicts with other apps, it is recommended to configure a
+Android. To avoid conflicts with other apps, it is recommended to configure a
distinct scheme using "reverse domain name notation". This can either match
your service web domain (in reverse) e.g. `com.example.service` or your package
name `com.example.app` or be something completely new as long as it's distinct
@@ -619,12 +619,12 @@ 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#L141) can be used to bypass the fact the issuer needs to be HTTPS.
```java
AppAuthConfiguration appAuthConfig = new AppAuthConfiguration.Builder()
.setSkipIssuerHttpsCheck(true)
- .build()
+ .build();
```
- For services that don't support nonce[s] resulting in **IdTokenException** `Nonce mismatch` just set nonce to `null` on the `AuthorizationRequest`. Please consider **raising an issue** with your Identity Provider and removing this once it is fixed.
@@ -635,6 +635,23 @@ AuthorizationRequest authRequest = authRequestBuilder
.build();
```
+- For testing environments [setSkipTimeValidation](https://github.com/openid/AppAuth-Android/blob/master/library/java/net/openid/appauth/AppAuthConfiguration.java#L149) can be used to bypass the issue time validation.
+
+```java
+AppAuthConfiguration appAuthConfig = new AppAuthConfiguration.Builder()
+ .setSkipTimeValidation(true)
+ .build();
+```
+
+- To change the default allowed time skew of 10 minutes for the issue time, [setAllowedTimeSkew](https://github.com/openid/AppAuth-Android/blob/master/library/java/net/openid/appauth/AppAuthConfiguration.java#L157) can be used.
+
+```java
+AppAuthConfiguration appAuthConfig = new AppAuthConfiguration.Builder()
+ .setAllowedTimeSkew(TWENTY_MINUTES_IN_SECONDS)
+ .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..696ebeb5 100644
--- a/library/java/net/openid/appauth/AppAuthConfiguration.java
+++ b/library/java/net/openid/appauth/AppAuthConfiguration.java
@@ -42,13 +42,20 @@ public class AppAuthConfiguration {
private final boolean mSkipIssuerHttpsCheck;
+ private final boolean mSkipTimeValidation;
+
+ private final Long mAllowedTimeSkew;
private AppAuthConfiguration(
@NonNull BrowserMatcher browserMatcher,
@NonNull ConnectionBuilder connectionBuilder,
- Boolean skipIssuerHttpsCheck) {
+ Boolean skipIssuerHttpsCheck,
+ Boolean skipTimeValidation,
+ Long allowedTimeSkew) {
mBrowserMatcher = browserMatcher;
mConnectionBuilder = connectionBuilder;
mSkipIssuerHttpsCheck = skipIssuerHttpsCheck;
+ mSkipTimeValidation = skipTimeValidation;
+ mAllowedTimeSkew = allowedTimeSkew;
}
/**
@@ -76,6 +83,22 @@ public ConnectionBuilder getConnectionBuilder() {
*/
public boolean getSkipIssuerHttpsCheck() { return mSkipIssuerHttpsCheck; }
+ /**
+ * Returns true
if the ID token issue time validation is disables,
+ * otherwise false
.
+ *
+ * @see Builder#setSkipTimeValidation(Boolean)
+ */
+ public boolean getSkipTimeValidation() { return mSkipTimeValidation; }
+
+ /**
+ * Returns the time in seconds that the ID token issue time is allowed to be
+ * skewed.
+ *
+ * @see Builder#setAllowedTimeSkew(Long)
+ */
+ public Long getAllowedTimeSkew() { return mAllowedTimeSkew; }
+
/**
* Creates {@link AppAuthConfiguration} instances.
*/
@@ -85,7 +108,8 @@ public static class Builder {
private ConnectionBuilder mConnectionBuilder = DefaultConnectionBuilder.INSTANCE;
private boolean mSkipIssuerHttpsCheck;
private boolean mSkipNonceVerification;
-
+ private boolean mSkipTimeValidation;
+ private Long mAllowedTimeSkew;
/**
* Specify the browser matcher to use, which controls the browsers that can be used
* for authorization.
@@ -119,6 +143,21 @@ public Builder setSkipIssuerHttpsCheck(Boolean skipIssuerHttpsCheck) {
return this;
}
+ /**
+ * Disables issue time validation for the id token.
+ */
+ public Builder setSkipTimeValidation(Boolean skipTimeValidation) {
+ mSkipTimeValidation = skipTimeValidation;
+ return this;
+ }
+
+ /**
+ * Sets the allowed time skew in seconds for id token issue time validation.
+ */
+ public Builder setAllowedTimeSkew(Long allowedTimeSkew) {
+ mAllowedTimeSkew = allowedTimeSkew;
+ return this;
+ }
/**
* Creates the instance from the configured properties.
*/
@@ -127,7 +166,9 @@ public AppAuthConfiguration build() {
return new AppAuthConfiguration(
mBrowserMatcher,
mConnectionBuilder,
- mSkipIssuerHttpsCheck
+ mSkipIssuerHttpsCheck,
+ mSkipTimeValidation,
+ mAllowedTimeSkew
);
}
diff --git a/library/java/net/openid/appauth/AuthorizationService.java b/library/java/net/openid/appauth/AuthorizationService.java
index b3bdf7ed..93467303 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.getSkipTimeValidation(),
+ mClientConfiguration.getAllowedTimeSkew())
.execute();
}
@@ -585,7 +587,8 @@ private static class TokenRequestTask
private TokenResponseCallback mCallback;
private Clock mClock;
private boolean mSkipIssuerHttpsCheck;
-
+ private boolean mSkipTimeValidation;
+ private Long mAllowedTimeSkew;
private AuthorizationException mException;
TokenRequestTask(TokenRequest request,
@@ -593,13 +596,17 @@ private static class TokenRequestTask
@NonNull ConnectionBuilder connectionBuilder,
Clock clock,
TokenResponseCallback callback,
- Boolean skipIssuerHttpsCheck) {
+ Boolean skipIssuerHttpsCheck,
+ Boolean skipTimeValidation,
+ Long allowedTimeSkew) {
mRequest = request;
mClientAuthentication = clientAuthentication;
mConnectionBuilder = connectionBuilder;
mClock = clock;
mCallback = callback;
mSkipIssuerHttpsCheck = skipIssuerHttpsCheck;
+ mSkipTimeValidation = skipTimeValidation;
+ mAllowedTimeSkew = allowedTimeSkew;
}
@Override
@@ -710,7 +717,9 @@ protected void onPostExecute(JSONObject json) {
idToken.validate(
mRequest,
mClock,
- mSkipIssuerHttpsCheck
+ mSkipIssuerHttpsCheck,
+ mSkipTimeValidation,
+ mAllowedTimeSkew
);
} 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..cd3be9c6 100644
--- a/library/java/net/openid/appauth/IdToken.java
+++ b/library/java/net/openid/appauth/IdToken.java
@@ -19,6 +19,7 @@
import android.net.Uri;
import android.text.TextUtils;
import android.util.Base64;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@@ -204,12 +205,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 skipTimeValidation,
+ @Nullable Long allowedTimeSkew) throws AuthorizationException {
// OpenID Connect Core Section 3.1.3.7. rule #1
// Not enforced: AppAuth does not support JWT encryption.
@@ -271,18 +274,22 @@ void validate(@NonNull TokenRequest tokenRequest,
// OpenID Connect Core Section 3.1.3.7. rule #9
// Validates that the current time is before the expiry time.
Long nowInSeconds = clock.getCurrentTimeMillis() / MILLIS_PER_SECOND;
- if (nowInSeconds > this.expiration) {
- throw AuthorizationException.fromTemplate(GeneralErrors.ID_TOKEN_VALIDATION_ERROR,
- new IdTokenException("ID Token expired"));
+ if (!skipTimeValidation) {
+ if (nowInSeconds > this.expiration) {
+ throw AuthorizationException.fromTemplate(GeneralErrors.ID_TOKEN_VALIDATION_ERROR,
+ 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 (!skipTimeValidation) {
+ // 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) > (allowedTimeSkew == null ? TEN_MINUTES_IN_SECONDS : allowedTimeSkew)) {
+ 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..5799f94a 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;