I released an update to my app a few days ago, the user can add their account info, which gets sent to a SAML2 webservice, returns a refresh token, and stores it in the iOS Keychain using the following method:
+(void)saveString:(NSString *)inputString forKey:(NSString *)account {
/
/
NSMutableDictionary *query=[NSMutableDictionary dictionary];
[query setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[query setObject:account forKey:(__bridge id)kSecAttrAccount];
[query setObject:(__bridge id)kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
OSStatus error=SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL);
if (error == errSecSuccess) {
/
NSDictionary *attributesToUpdate=@{(__bridge id)kSecAttrAccount: account, (__bridge id)kSecValueData: [inputString dataUsingEncoding:NSUTF8StringEncoding]};
error = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)attributesToUpdate);
/
if (error != errSecSuccess) {
NSLog(@"SecItemUpdate failed: %d", error);
}
}else if (error == errSecItemNotFound) {
[query setObject:account forKey:(__bridge id)kSecAttrAccount];
[query setObject:[inputString dataUsingEncoding:NSUTF8StringEncoding] forKey:(__bridge id)kSecValueData];
error=SecItemAdd((__bridge CFDictionaryRef)query, NULL);
/
if (error != errSecSuccess) {
NSLog(@"SecItemUpdate failed: %d", error);
}
}else {
/
NSLog(@"SecItemCopyMatching failed: %d", error);
}
}
Depending on who has the app installed, it seems like it's not storing the refresh token to the keychain, and silently failing. When I plugged in one of the affected user's phones into XCode and ran a debug build, it gave me an "errSecNotAvailable" message (code -25291).
When the user (via the process of logging into an authenticated app screen) attempt to pull the token out of the keychain using the following method, it pulls out a nil value, and doesn't log them in (as expected):
+(NSString *)getStringForKey:(NSString *)account {
/
if (account != nil) {
NSMutableDictionary *query=[NSMutableDictionary dictionary];
[query setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[query setObject:account forKey:(__bridge id)kSecAttrAccount];
[query setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
CFDataRef dataFromKeychain=nil;
OSStatus error=SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&dataFromKeychain);
NSData *dataResult=(__bridge_transfer NSData *)dataFromKeychain;
NSString *stringToReturn=nil;
if (error == errSecSuccess) {
stringToReturn=[[NSString alloc] initWithData:dataResult encoding:NSUTF8StringEncoding];
}
return stringToReturn;
}else {
return nil;
}
}
This has been very frustrating for both my users, as well as myself, since the keychain works on most devices (including my personal and test iPhones), but not on others. Is there a reason for this error to occur on some devices but not others? I couldn't even determine a pattern (i.e. device model, iOS Version, etc). It seems to be indiscriminate.
Thanks in advance!
- Scott