I'd like to use AppAuth's performActionWithFreshTokens
method. This requires using the OIDAuthState returned by the authStateByPresentingAuthorizationRequest
when generating access and refresh tokens. I want to save that OIDAuthState in the Keychain for security reasons, then use it at a later time when a token refresh is necessary. What I can't seem to do is properly archive and unarchive the saved value from the Keychain.
I have two methods: (1) used to convert the OIDAuthState to an NSString to store to the Keychain and (2) used to convert the NSString from the Keychain to an OIDAuthState. Here is the first:
NSData *checkEncoding;
- (NSString *)authStateToString:(OIDAuthState *)authState {
NSError *errRet;
//NSData *authStateData = [NSKeyedArchiver archivedDataWithRootObject:authState];
NSData *authStateData = [NSKeyedArchiver archivedDataWithRootObject:authState
requiringSecureCoding:NO error:&errRet];
checkEncoding = authStateData;
NSString *authString = [[NSString alloc] initWithData:authStateData
encoding:NSUnicodeStringEncoding];
NSLog(@"ToString length = %ld, authStateData length = %ld",
(unsigned long)authString.length, (unsigned long)authStateData.length);
return authString;
}
And here is the second:
- (OIDAuthState *)stringToAuthState:(NSString *)authString {
NSError *errRet;
NSData *authStateData = [authString dataUsingEncoding:NSUnicodeStringEncoding allowLossyConversion:NO];
NSLog(@"ToAuthState length = %ld, authStateData length = %ld, checkEncoding = %d",
(unsigned long)authString.length, (unsigned long)authStateData.length, [authStateData isEqualToData:checkEncoding]);
OIDAuthState *authState = [NSKeyedUnarchiver unarchivedObjectOfClass:[OIDAuthState class]
fromData:authStateData error:&errRet];
//OIDAuthState *authState = [NSKeyedUnarchiver unarchiveTopLevelObjectWithData:authStateData error:&errRet];
//OIDAuthState *authState = [NSKeyedUnarchiver unarchiveObjectWithData:authStateData];
/* NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:authStateData];
OIDAuthState *authState = [unarchiver decodeObject];
[unarchiver finishDecoding]; */
return authState;
}
(The multiple tries at unarchiving don't come into play since they all fail and the uncommented version is probably the right/undeprecated one.)
Note the NSLog statements in each, which seem to point out the problem. For the authStateToString
NSLog, the output is "ToString length = 7930, authStateData length = 15860". For the stringToAuthState
NSLog, the output is "ToAuthState length = 7930, authStateData length = 15862, checkEncoding = 0". The NSStrings in both methods are equal according to isEqualToString
.
So, I'm guessing that the OIDAuthState can't be regenerated because the conversion for the NSString versions is occuring but isn't regenerated the same NSData representation; the encoded version is 15860 bytes while the decoded version is 15862 bytes and the isEqualToData
returns false.
I do note that the OIDAuthState class NSKeyedArchiver method encodeWithCoder
is called as expected when encoding the state. The corresponding NSKeyedUnarchiver method initWithCoder
is not called but that's not surprising since the NSData being passed is probably not valid.
I've tried multiple dataUsingEncoding
encoding types with most failing (the resulting NSData is zero-length). NSUnicodeStringEncoding
is the only one that seemed to produce appropriate length NSData instances. I've tried setting 'requiringSecureCoding' to both YES and NO. I've tried setting 'allowLossyConversion' to both YES and NO.
I am open to other methods of persisting the OIDAuthState but it seems what I've done should be valid. Can someone suggest why I can't seem to correctly unarchive?
I ended up implementing a different Keychain approach, using the Apple-provided KeychainItemWrapper to simplify Keychain operations.
- (void)storeAuthState:(OIDAuthState *)authState {
NSError *errRet;
NSData *authStateData = [NSKeyedArchiver archivedDataWithRootObject:authState
requiringSecureCoding:NO error:&errRet];
KeychainItemWrapper *keychainItem =
[[KeychainItemWrapper alloc] initWithIdentifier:@"<your ID here>" accessGroup:nil];
[keychainItem setObject:authStateData forKey:(id)kSecAttrAccount];
}
- (OIDAuthState *)retrieveAuthState {
NSError *errRet;
KeychainItemWrapper *keychainItem =
[[KeychainItemWrapper alloc] initWithIdentifier:@"<your ID here>" accessGroup:nil];
NSData *data = [keychainItem objectForKey:(id)kSecAttrAccount];
OIDAuthState *authState = [NSKeyedUnarchiver unarchivedObjectOfClass:[OIDAuthState class]
fromData:data error:&errRet];
return authState;
}