My app stores the user's credentials when he signs in or up. When the app gets started I check in didFinishLaunchingWithOptions
if we have stored credentials. This works fine when starting the app by tapping on the App Icon or launching it from Xcode.
However, when the app gets killed in the background and relaunched by the system due to a location change update, the credential returned by defaultCredentialForProtectionSpace
is nil. When I restart the app again normally, the credential is back again.
So when [launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]
is true, the NSURLCredential
returned by the NSURLCredentialStorage
is nil; when it's false, we get the expected credential.
Here's some code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Other init things happen here, including setting up the NSURLProtectionSpace
NSURLCredentialStorage *credentialStorage = [NSURLCredentialStorage sharedCredentialStorage];
NSURLCredential *credential = [credentialStorage defaultCredentialForProtectionSpace:self.protectionSpace];
if (credential) {
// do something - XXX this does not happen when app is launched in background
}
}
It turns out the
NSURLCredentialStorage
setskSecAttrAccessibleWhenUnlocked
in the Keychain to store the credentials. That means the credentials can not be accessed when the user sets a password to unlock the phone and the phone is locked.---EDIT---
The above explains why it's not working and here is the solution I ended up implementing: change the
kSecAttrAccessible
in the Keychain tokSecAttrAccessibleAfterFirstUnlock
and deal with not being able to access the credentails in the rare cases when the phone got rebooted but has not been unlocked yet.When the App is in the foreground, the Keychain is unlocked in the mode used by
NSURLCredentialStorage
:kSecAttrAccessibleWhenUnlocked
. So inapplicationDidBecomeActive:
we can access the Keychain entries fromNSURLCredentialStorage
and make our changes:The above code uses an adapted version of the KeychainItemWrapper. The original version is from Apple, but I based my solution on the ARCified version here: https://gist.github.com/dhoerl/1170641. You still have to change that version to work with
kSecClassInternetPassword
instead ofkSecClassGenericPassword
, which mainly means you can't usekSecAttrGeneric
to query the Keychain items, but insteadkSecAttrServer
.---EDIT---
The above method worked fine with iOS 7.x but does not work anymore on iOS 8.x. The code runs fine, but the Keychain still uses
kSecAttrAccessibleWhenUnlocked
.The only solution now is to use
NSURLCredentialStorage
only when you know you will only use this in the foreground. Otherwise you need to store credentials yourself in the Keychain using the KeychainItemWrapper from https://gist.github.com/dhoerl/1170641. Then you can setkSecAttrAccessibleAfterFirstUnlock
yourself when storing the credentials.Example code: