Skip to content

Commit e5450a3

Browse files
Feature: custom token validator (#334)
* Adds ability to use a custom token validator to verify issueAt and expiry times and updates the Example application to use TrueTime as a sample * Implement fixes to support token validation functionality * Remove cocoapods deps * Add token type to validate access and id tokens * Fix failure * Fix linkage issue * Remove useless comments Co-authored-by: Erik Manor <[email protected]>
1 parent d090c92 commit e5450a3

34 files changed

+388
-88
lines changed

Example/ViewController.swift

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,11 @@ final class ViewController: UIViewController {
5252

5353
let configuration = try? OktaOidcConfig.default()
5454
configuration?.requestCustomizationDelegate = self
55+
56+
configuration?.tokenValidator = self
57+
5558
oktaAppAuth = try? OktaOidc(configuration: isUITest ? testConfig : configuration)
5659
AppDelegate.shared.oktaOidc = oktaAppAuth
57-
5860
if let config = oktaAppAuth?.configuration {
5961
authStateManager = OktaOidcStateManager.readFromSecureStorage(for: config)
6062
authStateManager?.requestCustomizationDelegate = self
@@ -117,21 +119,43 @@ final class ViewController: UIViewController {
117119

118120
@IBAction func introspectButton(_ sender: Any) {
119121
// Get current accessToken
120-
guard let accessToken = authStateManager?.accessToken else { return }
121-
122-
authStateManager?.introspect(token: accessToken, callback: { payload, error in
123-
guard let isValid = payload?["active"] as? Bool else {
124-
self.showMessage("Error: \(error?.localizedDescription ?? "Unknown")")
125-
return
122+
var accessToken = authStateManager?.accessToken
123+
if accessToken == nil {
124+
authStateManager?.renew { newAuthStateManager, error in
125+
if let error = error {
126+
// Error
127+
print("Error trying to Refresh AccessToken: \(error)")
128+
return
129+
}
130+
self.authStateManager = newAuthStateManager
131+
accessToken = newAuthStateManager?.accessToken
132+
133+
self.authStateManager?.introspect(token: accessToken, callback: { payload, error in
134+
guard let isValid = payload?["active"] as? Bool else {
135+
self.showMessage("Error: \(error?.localizedDescription ?? "Unknown")")
136+
return
137+
}
138+
139+
self.showMessage("Is the AccessToken valid? - \(isValid)")
140+
})
126141
}
127-
128-
self.showMessage("Is the AccessToken valid? - \(isValid)")
129-
})
142+
} else {
143+
authStateManager?.introspect(token: accessToken, callback: { payload, error in
144+
guard let isValid = payload?["active"] as? Bool else {
145+
self.showMessage("Error: \(error?.localizedDescription ?? "Unknown")")
146+
return
147+
}
148+
149+
self.showMessage("Is the AccessToken valid? - \(isValid)")
150+
})
151+
}
130152
}
131153

132154
@IBAction func revokeButton(_ sender: Any) {
133155
// Get current accessToken
134-
guard let accessToken = authStateManager?.accessToken else { return }
156+
guard let accessToken = authStateManager?.accessToken else {
157+
return
158+
}
135159

136160
authStateManager?.revoke(accessToken) { _, error in
137161
if error != nil { self.showMessage("Error: \(error!)") }
@@ -224,6 +248,28 @@ extension ViewController: OktaNetworkRequestCustomizationDelegate {
224248
}
225249
}
226250

251+
extension ViewController: OKTTokenValidator {
252+
func isIssued(atDateValid issuedAt: Date?, token: OKTTokenType) -> Bool {
253+
guard let issuedAt = issuedAt else {
254+
return false
255+
}
256+
257+
let now = Date()
258+
259+
return fabs(now.timeIntervalSince(issuedAt)) <= 200
260+
}
261+
262+
func isDateExpired(_ expiry: Date?, token tokenType: OKTTokenType) -> Bool {
263+
guard let expiry = expiry else {
264+
return false
265+
}
266+
267+
let now = Date()
268+
269+
return now >= expiry
270+
}
271+
}
272+
227273
extension OktaOidcError {
228274
var displayMessage: String {
229275
switch self {

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,17 @@ if #available(iOS 13.0, *) {
185185
```
186186
***Note*** Flag is available on iOS 13 and above versions
187187

188+
189+
### Token Time Validation
190+
191+
Custom token time validation is possible by adopting to `OKTTokenValidator` protocol and then setting `tokenValidator` variable:
192+
193+
```swift
194+
configuration?.tokenValidator = self
195+
```
196+
197+
By default `OKTDefaultTokenValidator` object is set.
198+
188199
### How to use in Objective-C project
189200

190201
To use this SDK in Objective-C project, you should do the following:

Sources/AppAuth/OKTAuthState.m

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#import "OKTTokenRequest.h"
3333
#import "OKTTokenResponse.h"
3434
#import "OKTTokenUtilities.h"
35+
#import "OKTDefaultTokenValidator.h"
3536

3637
/*! @brief Key used to encode the @c refreshToken property for @c NSSecureCoding.
3738
*/
@@ -131,6 +132,7 @@ @implementation OKTAuthState {
131132
authStateByPresentingAuthorizationRequest:(OKTAuthorizationRequest *)authorizationRequest
132133
externalUserAgent:(id<OKTExternalUserAgent>)externalUserAgent
133134
delegate:(id<OktaNetworkRequestCustomizationDelegate> _Nullable)delegate
135+
validator:(id<OKTTokenValidator> _Nonnull)validator
134136
callback:(OKTAuthStateAuthorizationCallback)callback {
135137
// presents the authorization request
136138
id<OKTExternalUserAgentSession> authFlowSession = [OKTAuthorizationService
@@ -151,6 +153,7 @@ @implementation OKTAuthState {
151153
[OKTAuthorizationService performTokenRequest:tokenExchangeRequest
152154
originalAuthorizationResponse:authorizationResponse
153155
delegate:delegate
156+
validator:validator
154157
callback:^(OKTTokenResponse * _Nullable tokenResponse, NSError * _Nullable tokenError) {
155158
OKTAuthState *authState;
156159
if (tokenResponse) {
@@ -199,7 +202,11 @@ - (instancetype)initWithAuthorizationResponse:(OKTAuthorizationResponse *)author
199202
*/
200203
- (instancetype)initWithAuthorizationResponse:(OKTAuthorizationResponse *)authorizationResponse
201204
tokenResponse:(nullable OKTTokenResponse *)tokenResponse {
202-
return [self initWithAuthorizationResponse:authorizationResponse tokenResponse:tokenResponse registrationResponse:nil delegate:nil];
205+
return [self initWithAuthorizationResponse:authorizationResponse
206+
tokenResponse:tokenResponse
207+
registrationResponse:nil
208+
delegate:nil
209+
validator:[OKTDefaultTokenValidator new]];
203210
}
204211

205212
/*! @brief Creates an auth state from an registration response.
@@ -209,17 +216,20 @@ - (instancetype)initWithRegistrationResponse:(OKTRegistrationResponse *)registra
209216
return [self initWithAuthorizationResponse:nil
210217
tokenResponse:nil
211218
registrationResponse:registrationResponse
212-
delegate:nil];
219+
delegate:nil
220+
validator:[OKTDefaultTokenValidator new]];
213221
}
214222

215223
- (instancetype)initWithAuthorizationResponse:
216224
(nullable OKTAuthorizationResponse *)authorizationResponse
217225
tokenResponse:(nullable OKTTokenResponse *)tokenResponse
218226
registrationResponse:(nullable OKTRegistrationResponse *)registrationResponse
219-
delegate:(nullable id<OktaNetworkRequestCustomizationDelegate>)delegate {
227+
delegate:(nullable id<OktaNetworkRequestCustomizationDelegate>)delegate
228+
validator:(nonnull id<OKTTokenValidator>)validator {
220229
self = [super init];
221230
if (self) {
222231
_delegate = delegate;
232+
_validator = validator;
223233
_pendingActionsSyncObject = [[NSObject alloc] init];
224234

225235
if (registrationResponse) {
@@ -510,13 +520,14 @@ - (void)performActionWithFreshTokens:(OKTAuthStateAction)action
510520
// creates a list of pending actions, starting with this one
511521
_pendingActions = [NSMutableArray arrayWithObject:pendingAction];
512522
}
513-
523+
514524
// refresh the tokens
515525
OKTTokenRequest *tokenRefreshRequest =
516526
[self tokenRefreshRequestWithAdditionalParameters:additionalParameters];
517527
[OKTAuthorizationService performTokenRequest:tokenRefreshRequest
518528
originalAuthorizationResponse:_lastAuthorizationResponse
519529
delegate:_delegate
530+
validator:_validator
520531
callback:^(OKTTokenResponse *_Nullable response,
521532
NSError *_Nullable error) {
522533
// update OKTAuthState based on response

Sources/AppAuth/OKTAuthorizationService.m

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,13 @@
3737
#import "OKTTokenResponse.h"
3838
#import "OKTURLQueryComponent.h"
3939
#import "OKTURLSessionProvider.h"
40+
#import "OKTDefaultTokenValidator.h"
4041

4142
/*! @brief Path appended to an OpenID Connect issuer for discovery
4243
@see https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
4344
*/
4445
static NSString *const kOpenIDConfigurationWellKnownPath = @".well-known/openid-configuration";
4546

46-
/*! @brief Max allowable iat (Issued At) time skew
47-
@see https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
48-
*/
49-
static int const kOKTAuthorizationSessionIATMaxSkew = 600;
50-
5147
NS_ASSUME_NONNULL_BEGIN
5248

5349
@interface OKTAuthorizationSession : NSObject<OKTExternalUserAgentSession>
@@ -425,12 +421,17 @@ + (void)discoverServiceConfigurationForDiscoveryURL:(NSURL *)discoveryURL
425421
+ (void)performTokenRequest:(OKTTokenRequest *)request
426422
delegate:(id<OktaNetworkRequestCustomizationDelegate> _Nullable)delegate
427423
callback:(OKTTokenCallback)callback {
428-
[[self class] performTokenRequest:request originalAuthorizationResponse:nil delegate:delegate callback:callback];
424+
[[self class] performTokenRequest:request
425+
originalAuthorizationResponse:nil
426+
delegate:delegate
427+
validator:[[OKTDefaultTokenValidator alloc] init]
428+
callback:callback];
429429
}
430430

431431
+ (void)performTokenRequest:(OKTTokenRequest *)request
432432
originalAuthorizationResponse:(OKTAuthorizationResponse *_Nullable)authorizationResponse
433433
delegate:(id<OktaNetworkRequestCustomizationDelegate> _Nullable)delegate
434+
validator:(id<OKTTokenValidator> _Nonnull)validator
434435
callback:(OKTTokenCallback)callback {
435436

436437
NSURLRequest *URLRequest = [request URLRequest];
@@ -607,37 +608,32 @@ + (void)performTokenRequest:(OKTTokenRequest *)request
607608

608609
// OpenID Connect Core Section 3.1.3.7. rules #7 & #8
609610
// Not applicable. See rule #6.
611+
612+
NSAssert(validator != nil, @"Validator parameter is missed. Default will be used.");
613+
id<OKTTokenValidator> tokenValidator = validator ?: [OKTDefaultTokenValidator new];
610614

611-
// OpenID Connect Core Section 3.1.3.7. rule #9
612-
// Validates that the current time is before the expiry time.
613-
NSTimeInterval expiresAtDifference = [idToken.expiresAt timeIntervalSinceNow];
614-
if (expiresAtDifference < 0) {
615+
if ([tokenValidator isDateExpired:idToken.expiresAt token:OKTTokenTypeId]) {
615616
NSError *invalidIDToken =
616-
[OKTErrorUtilities errorWithCode:OKTErrorCodeIDTokenFailedValidationError
617-
underlyingError:nil
618-
description:@"ID Token expired"];
617+
[OKTErrorUtilities errorWithCode:OKTErrorCodeIDTokenFailedValidationError
618+
underlyingError:nil
619+
description:@"ID Token expired"];
619620
dispatch_async(dispatch_get_main_queue(), ^{
620621
callback(nil, invalidIDToken);
621622
});
622623
return;
623624
}
624-
625-
// OpenID Connect Core Section 3.1.3.7. rule #10
626-
// Validates that the issued at time is not more than +/- 10 minutes on the current time.
627-
NSTimeInterval issuedAtDifference = [idToken.issuedAt timeIntervalSinceNow];
628-
if (fabs(issuedAtDifference) > kOKTAuthorizationSessionIATMaxSkew) {
629-
NSString *message =
630-
[NSString stringWithFormat:@"Issued at time is more than %d seconds before or after "
631-
"the current time",
632-
kOKTAuthorizationSessionIATMaxSkew];
633-
NSError *invalidIDToken =
625+
626+
if (![tokenValidator isIssuedAtDateValid:idToken.issuedAt token:OKTTokenTypeId]) {
627+
NSString *message =
628+
[NSString stringWithFormat:@"Issued at time is invalid corresponding to the current time"];
629+
NSError *invalidIDToken =
634630
[OKTErrorUtilities errorWithCode:OKTErrorCodeIDTokenFailedValidationError
635631
underlyingError:nil
636632
description:message];
637-
dispatch_async(dispatch_get_main_queue(), ^{
638-
callback(nil, invalidIDToken);
639-
});
640-
return;
633+
dispatch_async(dispatch_get_main_queue(), ^{
634+
callback(nil, invalidIDToken);
635+
});
636+
return;
641637
}
642638

643639
// Only relevant for the authorization_code response type
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright (c) 2022-Present, Okta, Inc. and/or its affiliates. All rights reserved.
3+
* The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
4+
*
5+
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
6+
* Unless required by applicable law or agreed to in writing, software
7+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
8+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
*
10+
* See the License for the specific language governing permissions and limitations under the License.
11+
*/
12+
13+
#import "OKTDefaultTokenValidator.h"
14+
15+
int const kOKTAuthorizationSessionIATMaxSkew = 600;
16+
17+
@implementation OKTDefaultTokenValidator
18+
19+
- (BOOL)isDateExpired:(nullable NSDate *)expiresAtDate token:(OKTTokenType)tokenType {
20+
if (!expiresAtDate) {
21+
return YES;
22+
}
23+
24+
switch (tokenType) {
25+
case OKTTokenTypeId: {
26+
// OpenID Connect Core Section 3.1.3.7. rule #9
27+
// Validates that the current time is before the expiry time.
28+
NSTimeInterval expiresAtDifference = [expiresAtDate timeIntervalSinceNow];
29+
return expiresAtDifference < 0;
30+
}
31+
case OKTTokenTypeAccess:
32+
return expiresAtDate.timeIntervalSince1970 <= [NSDate new].timeIntervalSince1970;
33+
default:
34+
NSAssert(NO, @"Unknown token type.");
35+
return YES;
36+
}
37+
}
38+
39+
- (BOOL)isIssuedAtDateValid:(nullable NSDate *)issuedAt token:(OKTTokenType)tokenType {
40+
if (!issuedAt) {
41+
return NO;
42+
}
43+
44+
// OpenID Connect Core Section 3.1.3.7. rule #10
45+
// Validates that the issued at time is not more than +/- 10 minutes on the current time.
46+
NSTimeInterval issuedAtDifference = [issuedAt timeIntervalSinceNow];
47+
return fabs(issuedAtDifference) <= kOKTAuthorizationSessionIATMaxSkew;
48+
}
49+
50+
@end

Sources/AppAuth/iOS/OKTAuthState+IOS.m

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,24 +32,28 @@ @implementation OKTAuthState (IOS)
3232
authStateByPresentingAuthorizationRequest:(OKTAuthorizationRequest *)authorizationRequest
3333
presentingViewController:(UIViewController *)presentingViewController
3434
delegate:(id<OktaNetworkRequestCustomizationDelegate> _Nullable)delegate
35+
validator:(id<OKTTokenValidator> _Nullable)validator
3536
callback:(OKTAuthStateAuthorizationCallback)callback {
3637
OKTExternalUserAgentIOS *externalUserAgent =
3738
[[OKTExternalUserAgentIOS alloc]
3839
initWithPresentingViewController:presentingViewController];
3940
return [self authStateByPresentingAuthorizationRequest:authorizationRequest
4041
externalUserAgent:externalUserAgent
4142
delegate:delegate
43+
validator: validator
4244
callback:callback];
4345
}
4446

4547
+ (id<OKTExternalUserAgentSession>)
4648
authStateByPresentingAuthorizationRequest:(OKTAuthorizationRequest *)authorizationRequest
4749
delegate:(id<OktaNetworkRequestCustomizationDelegate> _Nullable)delegate
50+
validator:(id<OKTTokenValidator> _Nullable)validator
4851
callback:(OKTAuthStateAuthorizationCallback)callback {
4952
OKTExternalUserAgentIOS *externalUserAgent = [[OKTExternalUserAgentIOS alloc] init];
5053
return [self authStateByPresentingAuthorizationRequest:authorizationRequest
5154
externalUserAgent:externalUserAgent
5255
delegate:delegate
56+
validator: validator
5357
callback:callback];
5458
}
5559

Sources/AppAuth/include/AppAuthCore.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,4 @@
4444
#import "OKTURLSessionProvider.h"
4545
#import "OKTEndSessionRequest.h"
4646
#import "OKTEndSessionResponse.h"
47+
#import "OKTDefaultTokenValidator.h"

Sources/AppAuth/include/OKTAuthState+IOS.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,13 @@ NS_ASSUME_NONNULL_BEGIN
5252
authStateByPresentingAuthorizationRequest:(OKTAuthorizationRequest *)authorizationRequest
5353
presentingViewController:(UIViewController *)presentingViewController
5454
delegate:(id<OktaNetworkRequestCustomizationDelegate> _Nullable)delegate
55+
validator:(id<OKTTokenValidator> _Nullable)validator
5556
callback:(OKTAuthStateAuthorizationCallback)callback;
5657

5758
+ (id<OKTExternalUserAgentSession>)
5859
authStateByPresentingAuthorizationRequest:(OKTAuthorizationRequest *)authorizationRequest
5960
delegate:(id<OktaNetworkRequestCustomizationDelegate> _Nullable)delegate
61+
validator:(id<OKTTokenValidator> _Nullable)validator
6062
callback:(OKTAuthStateAuthorizationCallback)callback API_AVAILABLE(ios(11))
6163
__deprecated_msg("This method will not work on iOS 13. Use "
6264
"authStateByPresentingAuthorizationRequest:presentingViewController:callback:");

0 commit comments

Comments
 (0)