From 196d034be94b1b903de64f587e4d0c3f5862f3b9 Mon Sep 17 00:00:00 2001 From: William Denniss Date: Sat, 25 Mar 2017 16:29:16 -0500 Subject: [PATCH] =?UTF-8?q?Added=20ID=20Token=20parsing.=20=E2=80=93=20The?= =?UTF-8?q?=205=20required=20fields=20are=20exposes=20as=20params.=20?= =?UTF-8?q?=E2=80=93=C2=A0Token=20exchange=20now=20validates=20the=20ID=20?= =?UTF-8?q?Token=20iss,=20aud,=20and=20iat=20claims.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AppAuth.xcodeproj/project.pbxproj | 28 +++++++ Source/AppAuth.h | 1 + Source/Framework/AppAuth.h | 1 + Source/OIDAuthorizationService.m | 64 ++++++++++++++ Source/OIDError.h | 7 ++ Source/OIDIDToken.h | 76 +++++++++++++++++ Source/OIDIDToken.m | 134 ++++++++++++++++++++++++++++++ 7 files changed, 311 insertions(+) create mode 100644 Source/OIDIDToken.h create mode 100644 Source/OIDIDToken.m diff --git a/AppAuth.xcodeproj/project.pbxproj b/AppAuth.xcodeproj/project.pbxproj index c631408bd..b0cde27b7 100644 --- a/AppAuth.xcodeproj/project.pbxproj +++ b/AppAuth.xcodeproj/project.pbxproj @@ -360,6 +360,18 @@ 347424101E7F4BA000D3E6D6 /* OIDTokenResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741D41C5D8243000EF209 /* OIDTokenResponse.m */; }; 347424111E7F4BA000D3E6D6 /* OIDTokenUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741D61C5D8243000EF209 /* OIDTokenUtilities.m */; }; 347424121E7F4BA000D3E6D6 /* OIDURLQueryComponent.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741D81C5D8243000EF209 /* OIDURLQueryComponent.m */; }; + 34A663291E871DD40060B664 /* OIDIDToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 34A663261E871DD40060B664 /* OIDIDToken.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 34A6632A1E871DD40060B664 /* OIDIDToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 34A663261E871DD40060B664 /* OIDIDToken.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 34A6632B1E871DD40060B664 /* OIDIDToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 34A663261E871DD40060B664 /* OIDIDToken.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 34A6632C1E871DD40060B664 /* OIDIDToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 34A663261E871DD40060B664 /* OIDIDToken.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 34A6632D1E871DD40060B664 /* OIDIDToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A663271E871DD40060B664 /* OIDIDToken.m */; }; + 34A6632E1E871DD40060B664 /* OIDIDToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A663271E871DD40060B664 /* OIDIDToken.m */; }; + 34A6632F1E871DD40060B664 /* OIDIDToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A663271E871DD40060B664 /* OIDIDToken.m */; }; + 34A663301E871DD40060B664 /* OIDIDToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A663271E871DD40060B664 /* OIDIDToken.m */; }; + 34A663311E871DD40060B664 /* OIDIDToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A663271E871DD40060B664 /* OIDIDToken.m */; }; + 34A663321E871DD40060B664 /* OIDIDToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A663271E871DD40060B664 /* OIDIDToken.m */; }; + 34A663331E871DD40060B664 /* OIDIDToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A663271E871DD40060B664 /* OIDIDToken.m */; }; + 34A663341E871DD40060B664 /* OIDIDToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A663271E871DD40060B664 /* OIDIDToken.m */; }; 34D5EC451E6D1AD900814354 /* OIDSwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D5EC441E6D1AD900814354 /* OIDSwiftTests.swift */; }; 34FEA6AE1DB6E083005C9212 /* OIDLoopbackHTTPServer.h in Headers */ = {isa = PBXBuildFile; fileRef = 34FEA6AC1DB6E083005C9212 /* OIDLoopbackHTTPServer.h */; }; 34FEA6AF1DB6E083005C9212 /* OIDLoopbackHTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 34FEA6AD1DB6E083005C9212 /* OIDLoopbackHTTPServer.m */; }; @@ -531,6 +543,8 @@ 343AAAC21E8348A900F9D36E /* AppAuth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppAuth.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 343AAACA1E8348AA00F9D36E /* AppAuth_macOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AppAuth_macOSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 347423F61E7F4B5600D3E6D6 /* libAppAuth-watchOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libAppAuth-watchOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 34A663261E871DD40060B664 /* OIDIDToken.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDIDToken.h; sourceTree = ""; }; + 34A663271E871DD40060B664 /* OIDIDToken.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDIDToken.m; sourceTree = ""; }; 34D5EC431E6D1AD900814354 /* OIDAppAuthTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OIDAppAuthTests-Bridging-Header.h"; sourceTree = ""; }; 34D5EC441E6D1AD900814354 /* OIDSwiftTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OIDSwiftTests.swift; sourceTree = ""; }; 34FEA6AC1DB6E083005C9212 /* OIDLoopbackHTTPServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDLoopbackHTTPServer.h; sourceTree = ""; }; @@ -744,6 +758,8 @@ 60140F7B1DE42E1000DA0DC3 /* OIDRegistrationRequest.m */, 341741C51C5D8243000EF209 /* OIDGrantTypes.h */, 341741C61C5D8243000EF209 /* OIDGrantTypes.m */, + 34A663261E871DD40060B664 /* OIDIDToken.h */, + 34A663271E871DD40060B664 /* OIDIDToken.m */, 341741C71C5D8243000EF209 /* OIDResponseTypes.h */, 341741C81C5D8243000EF209 /* OIDResponseTypes.m */, 341741C91C5D8243000EF209 /* OIDScopes.h */, @@ -861,6 +877,7 @@ 343AAAE81E83499000F9D36E /* OIDAuthStateChangeDelegate.h in Headers */, 343AAA6B1E83465500F9D36E /* AppAuth.h in Headers */, 343AAA6E1E83466B00F9D36E /* OIDAuthorizationUICoordinatorIOS.h in Headers */, + 34A663291E871DD40060B664 /* OIDIDToken.h in Headers */, 343AAAF21E83499000F9D36E /* OIDResponseTypes.h in Headers */, 343AAAF71E83499000F9D36E /* OIDTokenRequest.h in Headers */, 343AAAF41E83499000F9D36E /* OIDScopeUtilities.h in Headers */, @@ -901,6 +918,7 @@ 343AAB9B1E834A8800F9D36E /* AppAuth.h in Headers */, 343AAB001E83499100F9D36E /* OIDAuthStateChangeDelegate.h in Headers */, 343AAB081E83499100F9D36E /* OIDRegistrationRequest.h in Headers */, + 34A6632A1E871DD40060B664 /* OIDIDToken.h in Headers */, 343AAB101E83499100F9D36E /* OIDTokenResponse.h in Headers */, 343AAAFC1E83499100F9D36E /* OIDAuthorizationResponse.h in Headers */, 343AAB0C1E83499100F9D36E /* OIDScopeUtilities.h in Headers */, @@ -929,6 +947,7 @@ 343AAB9C1E834A8900F9D36E /* AppAuth.h in Headers */, 343AAB181E83499200F9D36E /* OIDAuthStateChangeDelegate.h in Headers */, 343AAB201E83499200F9D36E /* OIDRegistrationRequest.h in Headers */, + 34A6632B1E871DD40060B664 /* OIDIDToken.h in Headers */, 343AAB281E83499200F9D36E /* OIDTokenResponse.h in Headers */, 343AAB141E83499200F9D36E /* OIDAuthorizationResponse.h in Headers */, 343AAB241E83499200F9D36E /* OIDScopeUtilities.h in Headers */, @@ -964,6 +983,7 @@ 343AAB311E83499200F9D36E /* OIDAuthStateErrorDelegate.h in Headers */, 343AAB2F1E83499200F9D36E /* OIDAuthState.h in Headers */, 343AAB3E1E83499200F9D36E /* OIDServiceDiscovery.h in Headers */, + 34A6632C1E871DD40060B664 /* OIDIDToken.h in Headers */, 343AAADE1E83494400F9D36E /* OIDAuthorizationService+Mac.h in Headers */, 343AAB2E1E83499200F9D36E /* OIDAuthorizationUICoordinator.h in Headers */, 343AAB301E83499200F9D36E /* OIDAuthStateChangeDelegate.h in Headers */, @@ -1417,6 +1437,7 @@ 340DAE581D5821A100EC285B /* OIDAuthorizationUICoordinatorMac.m in Sources */, 340DAE5A1D5821AB00EC285B /* OIDAuthorizationRequest.m in Sources */, 347423E41E7F3C4000D3E6D6 /* OIDAuthorizationResponse.m in Sources */, + 34A6632E1E871DD40060B664 /* OIDIDToken.m in Sources */, 340DAE591D5821A100EC285B /* OIDAuthState+Mac.m in Sources */, 341310D01E6F944B00D5DEE5 /* OIDURLQueryComponent.m in Sources */, 341310C81E6F944B00D5DEE5 /* OIDResponseTypes.m in Sources */, @@ -1429,6 +1450,7 @@ buildActionMask = 2147483647; files = ( 341741E01C5D8243000EF209 /* OIDErrorUtilities.m in Sources */, + 34A6632D1E871DD40060B664 /* OIDIDToken.m in Sources */, 341741EA1C5D8243000EF209 /* OIDTokenUtilities.m in Sources */, 341741E21C5D8243000EF209 /* OIDGrantTypes.m in Sources */, 60140F7C1DE42E1000DA0DC3 /* OIDRegistrationRequest.m in Sources */, @@ -1527,6 +1549,7 @@ 341310D71E6F944D00D5DEE5 /* OIDRegistrationRequest.m in Sources */, 341310DD1E6F944D00D5DEE5 /* OIDServiceDiscovery.m in Sources */, 341E70991DE18796004353C1 /* OIDAuthorizationResponse.m in Sources */, + 34A6632F1E871DD40060B664 /* OIDIDToken.m in Sources */, 341310DB1E6F944D00D5DEE5 /* OIDScopeUtilities.m in Sources */, 341310D61E6F944D00D5DEE5 /* OIDRegistrationResponse.m in Sources */, 341310D31E6F944D00D5DEE5 /* OIDError.m in Sources */, @@ -1548,6 +1571,7 @@ buildActionMask = 2147483647; files = ( 343AAA881E83478900F9D36E /* OIDFieldMapping.m in Sources */, + 34A663311E871DD40060B664 /* OIDIDToken.m in Sources */, 343AAA841E83478900F9D36E /* OIDAuthState.m in Sources */, 343AAA701E83467D00F9D36E /* OIDAuthState+IOS.m in Sources */, 343AAA921E83478900F9D36E /* OIDTokenResponse.m in Sources */, @@ -1605,6 +1629,7 @@ 343AAB741E8349B000F9D36E /* OIDRegistrationRequest.m in Sources */, 343AAB7A1E8349B000F9D36E /* OIDServiceDiscovery.m in Sources */, 343AAB6C1E8349B000F9D36E /* OIDAuthorizationResponse.m in Sources */, + 34A663321E871DD40060B664 /* OIDIDToken.m in Sources */, 343AAB781E8349B000F9D36E /* OIDScopeUtilities.m in Sources */, 343AAB731E8349B000F9D36E /* OIDRegistrationResponse.m in Sources */, 343AAB701E8349B000F9D36E /* OIDError.m in Sources */, @@ -1632,6 +1657,7 @@ 343AAB601E8349B000F9D36E /* OIDRegistrationRequest.m in Sources */, 343AAB661E8349B000F9D36E /* OIDServiceDiscovery.m in Sources */, 343AAB581E8349B000F9D36E /* OIDAuthorizationResponse.m in Sources */, + 34A663331E871DD40060B664 /* OIDIDToken.m in Sources */, 343AAB641E8349B000F9D36E /* OIDScopeUtilities.m in Sources */, 343AAB5F1E8349B000F9D36E /* OIDRegistrationResponse.m in Sources */, 343AAB5C1E8349B000F9D36E /* OIDError.m in Sources */, @@ -1694,6 +1720,7 @@ 343AAB491E8349AF00F9D36E /* OIDErrorUtilities.m in Sources */, 343AAADB1E83493D00F9D36E /* OIDAuthorizationUICoordinatorMac.m in Sources */, 343AAB471E8349AF00F9D36E /* OIDClientMetadataParameters.m in Sources */, + 34A663341E871DD40060B664 /* OIDIDToken.m in Sources */, 343AAB461E8349AF00F9D36E /* OIDAuthState.m in Sources */, 343AAB561E8349AF00F9D36E /* OIDURLQueryComponent.m in Sources */, 343AAB4E1E8349AF00F9D36E /* OIDResponseTypes.m in Sources */, @@ -1733,6 +1760,7 @@ 347424081E7F4BA000D3E6D6 /* OIDRegistrationRequest.m in Sources */, 3474240E1E7F4BA000D3E6D6 /* OIDServiceDiscovery.m in Sources */, 347424001E7F4BA000D3E6D6 /* OIDAuthorizationResponse.m in Sources */, + 34A663301E871DD40060B664 /* OIDIDToken.m in Sources */, 3474240C1E7F4BA000D3E6D6 /* OIDScopeUtilities.m in Sources */, 347424071E7F4BA000D3E6D6 /* OIDRegistrationResponse.m in Sources */, 347424041E7F4BA000D3E6D6 /* OIDError.m in Sources */, diff --git a/Source/AppAuth.h b/Source/AppAuth.h index 908c6f30c..c01c02c3e 100644 --- a/Source/AppAuth.h +++ b/Source/AppAuth.h @@ -26,6 +26,7 @@ #import "OIDError.h" #import "OIDErrorUtilities.h" #import "OIDGrantTypes.h" +#import "OIDIDToken.h" #import "OIDRegistrationRequest.h" #import "OIDRegistrationResponse.h" #import "OIDResponseTypes.h" diff --git a/Source/Framework/AppAuth.h b/Source/Framework/AppAuth.h index 1275e36f2..7c1ea78e5 100644 --- a/Source/Framework/AppAuth.h +++ b/Source/Framework/AppAuth.h @@ -34,6 +34,7 @@ FOUNDATION_EXPORT const unsigned char AppAuthVersionString[]; #import #import #import +#import #import #import #import diff --git a/Source/OIDAuthorizationService.m b/Source/OIDAuthorizationService.m index e1179c76c..8e98d8d63 100644 --- a/Source/OIDAuthorizationService.m +++ b/Source/OIDAuthorizationService.m @@ -23,6 +23,7 @@ #import "OIDAuthorizationUICoordinator.h" #import "OIDDefines.h" #import "OIDErrorUtilities.h" +#import "OIDIDToken.h" #import "OIDRegistrationRequest.h" #import "OIDRegistrationResponse.h" #import "OIDServiceConfiguration.h" @@ -344,6 +345,69 @@ + (void)performTokenRequest:(OIDTokenRequest *)request callback:(OIDTokenCallbac return; } + // Validates ID Token if it exists + if (tokenResponse.idToken) { + OIDIDToken *idToken = [[OIDIDToken alloc] initWithIDTokenString:tokenResponse.idToken]; + if (!idToken) { + NSError *invalidIDToken = + [OIDErrorUtilities errorWithCode:OIDErrorCodeIDTokenParsingError + underlyingError:nil + description:@"ID Token parsing failed"]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, invalidIDToken); + }); + return; + } + + NSURL *issuer = tokenResponse.request.configuration.discoveryDocument.issuer; + if (issuer && ![idToken.issuer isEqual:issuer]) { + NSError *invalidIDToken = + [OIDErrorUtilities errorWithCode:OIDErrorCodeIDTokenFailedValidationError + underlyingError:nil + description:@"Issuer mismatch"]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, invalidIDToken); + }); + return; + } + + NSString *clientID = tokenResponse.request.clientID; + if (![idToken.audience isEqual:clientID]) { + NSError *invalidIDToken = + [OIDErrorUtilities errorWithCode:OIDErrorCodeIDTokenFailedValidationError + underlyingError:nil + description:@"Audience mismatch"]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, invalidIDToken); + }); + return; + } + + NSTimeInterval issuedAtDifference = [idToken.issuedAt timeIntervalSinceNow]; + if (issuedAtDifference > 300) { + NSError *invalidIDToken = + [OIDErrorUtilities errorWithCode:OIDErrorCodeIDTokenFailedValidationError + underlyingError:nil + description:@"Issued at time is too far in the future"]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, invalidIDToken); + }); + return; + } + + NSTimeInterval expiresAtDifference = [idToken.expiresAt timeIntervalSinceNow]; + if (expiresAtDifference < -60) { + NSError *invalidIDToken = + [OIDErrorUtilities errorWithCode:OIDErrorCodeIDTokenFailedValidationError + underlyingError:nil + description:@"ID Token expired"]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, invalidIDToken); + }); + return; + } + } + // Success dispatch_async(dispatch_get_main_queue(), ^{ callback(tokenResponse, nil); diff --git a/Source/OIDError.h b/Source/OIDError.h index 229802cc0..85b71e267 100644 --- a/Source/OIDError.h +++ b/Source/OIDError.h @@ -144,6 +144,13 @@ typedef NS_ENUM(NSInteger, OIDErrorCode) { */ OIDErrorCodeJSONSerializationError = -13, + /*! @brief The ID Token did not parse. + */ + OIDErrorCodeIDTokenParsingError = -14, + + /*! @brief The ID Token did not pass validation (e.g. issuer, audience checks). + */ + OIDErrorCodeIDTokenFailedValidationError = -15, }; /*! @brief Enum of all possible OAuth error codes as defined by RFC6749 diff --git a/Source/OIDIDToken.h b/Source/OIDIDToken.h new file mode 100644 index 000000000..9d3314ecd --- /dev/null +++ b/Source/OIDIDToken.h @@ -0,0 +1,76 @@ +/*! @file OIDIDToken.h + @brief AppAuth iOS SDK + @copyright + Copyright 2017 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +#import + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief A convenience class that parses _but not validates_ and ID Token. + */ +@interface OIDIDToken : NSObject + +/*! @internal + @brief Unavailable. Please use @c initWithAuthorizationResponse:. + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @brief Parses the given ID Token string. + @param idToken The ID Token spring. + */ +- (nullable instancetype)initWithIDTokenString:(NSString *)idToken; + +/*! @brief The header JWT values. + */ +@property(nonatomic, readonly) NSDictionary *header; + +/*! @brief All ID Token claims. + */ +@property(nonatomic, readonly) NSDictionary *claims; + +/*! @brief Issuer Identifier for the Issuer of the response. + @remarks iss + @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken + */ +@property(nonatomic, readonly) NSURL *issuer; + +/*! @brief Subject Identifier. + @remarks sub + @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken + */ +@property(nonatomic, readonly) NSString *subject; + +/*! @brief Audience(s) that this ID Token is intended for. + @remarks aud + @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken + */ +@property(nonatomic, readonly) NSString *audience; + +/*! @brief Expiration time on or after which the ID Token MUST NOT be accepted for processing. + @remarks exp + @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken + */ +@property(nonatomic, readonly) NSDate *expiresAt; + +/*! @brief Time at which the JWT was issued. + @remarks iat + @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken + */ +@property(nonatomic, readonly) NSDate *issuedAt; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/OIDIDToken.m b/Source/OIDIDToken.m new file mode 100644 index 000000000..8f767ff1c --- /dev/null +++ b/Source/OIDIDToken.m @@ -0,0 +1,134 @@ +/*! @file OIDIDToken.m + @brief AppAuth iOS SDK + @copyright + Copyright 2017 Google Inc. All Rights Reserved. + @copydetails + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "OIDIDToken.h" + +/*! Field keys associated with an ID Token. */ +static NSString *const kIssKey = @"iss"; +static NSString *const kSubKey = @"sub"; +static NSString *const kAudKey = @"aud"; +static NSString *const kExpKey = @"exp"; +static NSString *const kIatKey = @"iat"; + +#import "OIDFieldMapping.h" + +@implementation OIDIDToken { +} + +- (instancetype)initWithIDTokenString:(NSString *)idToken { + self = [super init]; + NSArray *sections = [idToken componentsSeparatedByString:@"."]; + if (sections.count > 1) { + _header = [[self class] parseJWTSection:sections[0]]; + _claims = [[self class] parseJWTSection:sections[1]]; + if (!_header || !_claims) { + return nil; + } + + [OIDFieldMapping remainingParametersWithMap:[[self class] fieldMap] + parameters:_claims + instance:self]; + + // Required fields. + if (!_issuer || !_audience || !_subject || !_expiresAt || !_issuedAt) { + return nil; + } + + return self; + } + return nil; +} + +/*! @brief Returns a mapping of incoming parameters to instance variables. + @return A mapping of incoming parameters to instance variables. + */ ++ (NSDictionary *)fieldMap { + static NSMutableDictionary *fieldMap; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + fieldMap = [NSMutableDictionary dictionary]; + + fieldMap[kIssKey] = + [[OIDFieldMapping alloc] initWithName:@"_issuer" + type:[NSURL class] + conversion:[OIDFieldMapping URLConversion]]; + fieldMap[kSubKey] = + [[OIDFieldMapping alloc] initWithName:@"_subject" type:[NSString class]]; + fieldMap[kAudKey] = + [[OIDFieldMapping alloc] initWithName:@"_audience" type:[NSString class]]; + fieldMap[kExpKey] = + [[OIDFieldMapping alloc] initWithName:@"_expiresAt" + type:[NSDate class] + conversion:^id _Nullable(NSObject *_Nullable value) { + if (![value isKindOfClass:[NSNumber class]]) { + return value; + } + NSNumber *valueAsNumber = (NSNumber *)value; + return [NSDate dateWithTimeIntervalSince1970:[valueAsNumber longLongValue]]; + }]; + fieldMap[kIatKey] = + [[OIDFieldMapping alloc] initWithName:@"_issuedAt" + type:[NSDate class] + conversion:^id _Nullable(NSObject *_Nullable value) { + if (![value isKindOfClass:[NSNumber class]]) { + return value; + } + NSNumber *valueAsNumber = (NSNumber *)value; + return [NSDate dateWithTimeIntervalSince1970:[valueAsNumber longLongValue]]; + }]; + }); + return fieldMap; +} + ++ (NSDictionary *)parseJWTSection:(NSString *)sectionString { + NSData *decodedData = [[self class] base64urlNoPaddingDecode:sectionString]; + + // Parses JSON. + NSError *error; + id object = [NSJSONSerialization JSONObjectWithData:decodedData options:0 error:&error]; + if (error) { + NSLog(@"Error %@ parsing token payload %@", error, sectionString); + } + if ([object isKindOfClass:[NSDictionary class]]) { + return (NSDictionary *)object; + } + + return nil; +} + ++ (NSData *)base64urlNoPaddingDecode:(NSString *)base64urlNoPaddingString { + NSMutableString *body = [base64urlNoPaddingString mutableCopy]; + + // Converts base64url to base64. + NSRange range = NSMakeRange(0, base64urlNoPaddingString.length); + [body replaceOccurrencesOfString:@"-" withString:@"+" options:NSLiteralSearch range:range]; + [body replaceOccurrencesOfString:@"_" withString:@"/" options:NSLiteralSearch range:range]; + + // Converts base64 no padding to base64 with padding + while (body.length % 4 != 0) { + [body appendString:@"="]; + } + + // Decodes base64 string. + NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:body options:0]; + return decodedData; +} + +@end + +