Skip to content

Commit

Permalink
Idtoken time skew (#3)
Browse files Browse the repository at this point in the history
* Skip issue time validation or change the allowed time skew

Allows to completely disable ID tokens issue time validation, or change the default of 10 minutes to a custom allowed time skew in seconds.

* Update documentation to change the allowed id token time skew

---------

Co-authored-by: Tim Klingeleers <Mardaneus86@users.noreply.github.com>
  • Loading branch information
nisrulz and Mardaneus86 authored Feb 26, 2024
1 parent 040dbcd commit 9efe2d7
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 16 deletions.
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand Down
48 changes: 46 additions & 2 deletions library/java/net/openid/appauth/AppAuthConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -76,6 +84,22 @@ public ConnectionBuilder getConnectionBuilder() {
*/
public boolean getSkipIssuerHttpsCheck() { return mSkipIssuerHttpsCheck; }

/**
* Returns <code>true</code> if the ID token issue time validation is disables,
* otherwise <code>false</code>.
*
* @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.
*/
Expand All @@ -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;

/**
Expand Down Expand Up @@ -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.
*/
Expand All @@ -127,7 +169,9 @@ public AppAuthConfiguration build() {
return new AppAuthConfiguration(
mBrowserMatcher,
mConnectionBuilder,
mSkipIssuerHttpsCheck
mSkipIssuerHttpsCheck,
mSkipIssueTimeValidation,
mAllowedIssueTimeSkew
);
}

Expand Down
16 changes: 13 additions & 3 deletions library/java/net/openid/appauth/AuthorizationService.java
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,9 @@ public void performTokenRequest(
mClientConfiguration.getConnectionBuilder(),
SystemClock.INSTANCE,
callback,
mClientConfiguration.getSkipIssuerHttpsCheck())
mClientConfiguration.getSkipIssuerHttpsCheck(),
mClientConfiguration.getSkipIssueTimeValidation(),
mClientConfiguration.getAllowedIssueTimeSkew())
.execute();
}

Expand Down Expand Up @@ -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;

Expand All @@ -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
Expand Down Expand Up @@ -710,7 +718,9 @@ protected void onPostExecute(JSONObject json) {
idToken.validate(
mRequest,
mClock,
mSkipIssuerHttpsCheck
mSkipIssuerHttpsCheck,
mSkipIssueTimeValidation,
mAllowedIssueTimeSkew
);
} catch (AuthorizationException ex) {
mCallback.onTokenRequestCompleted(null, ex);
Expand Down
23 changes: 14 additions & 9 deletions library/java/net/openid/appauth/IdToken.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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
Expand Down
56 changes: 55 additions & 1 deletion library/javatests/net/openid/appauth/IdTokenTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 9efe2d7

Please sign in to comment.