Unexpected errSecInteractionNotAllowed (-25308) When Reading Keychain Item with kSecAttrAccessibleAfterFirstUnlock in Background

Hi everyone,

I’m encountering an unexpected Keychain behavior in a production environment and would like to confirm whether this is expected or if I’m missing something.

In my app, I store a deviceId in the Keychain based on the classic KeychainItemWrapper implementation. I extended it by explicitly setting:

kSecAttrAccessible = kSecAttrAccessibleAfterFirstUnlock

My understanding is that kSecAttrAccessibleAfterFirstUnlock should allow Keychain access while the app is running in the background, as long as the device has been unlocked at least once after reboot.

However, after the app went live, I observed that when the app performs background execution (e.g., triggered by background tasks / silent push), Keychain read attempts intermittently fail with:

errSecInteractionNotAllowed (-25308)

This seems inconsistent with the documented behavior of kSecAttrAccessibleAfterFirstUnlock.

Additional context:

The issue never occurs in foreground.

The issue does not appear on development devices.

User devices are not freshly rebooted when this happens.

The Keychain item is created successfully; only background reads fail.

Setting the accessibility to kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly produces the same result.

Questions:

Under what circumstances can kSecAttrAccessibleAfterFirstUnlock still cause a -25308 error?

Is there any known restriction when accessing Keychain while the app is running in background execution contexts?

Could certain system states (Low Power Mode, Background App Refresh conditions, device lock state, etc.) cause Keychain reads to be blocked unexpectedly?

Any insights or similar experiences would be greatly appreciated. Thank you!

Answered by DTS Engineer in 868802022
kSecAttrAccessibleAfterFirstUnlock should allow [access] as long as the device has been unlocked at least once after reboot.

Correct.

I’m aware of two common causes of this problem:

  • There are very limited circumstances under which iOS will run third-party code before first unlock. The canonical example of this is VoIP push notifications.
  • Sometimes these problems are caused by the keychain item not having the correct kSecAttrAccessible value. For example, where the developer shipped a version of their app that set the attribute incorrectly and fixed that in the next version, which left any user who first ran the broken version of their app with the wrong value.

Beyond that, it’s hard to say what’s going on here. It’s certainly possible to imagine an OS-level bug causing a problem like this, but I’m not aware of any such bug in play right now.

I have general advice about how to approach issues like this in Investigating hard-to-reproduce keychain problems.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

kSecAttrAccessibleAfterFirstUnlock should allow [access] as long as the device has been unlocked at least once after reboot.

Correct.

I’m aware of two common causes of this problem:

  • There are very limited circumstances under which iOS will run third-party code before first unlock. The canonical example of this is VoIP push notifications.
  • Sometimes these problems are caused by the keychain item not having the correct kSecAttrAccessible value. For example, where the developer shipped a version of their app that set the attribute incorrectly and fixed that in the next version, which left any user who first ran the broken version of their app with the wrong value.

Beyond that, it’s hard to say what’s going on here. It’s certainly possible to imagine an OS-level bug causing a problem like this, but I’m not aware of any such bug in play right now.

I have general advice about how to approach issues like this in Investigating hard-to-reproduce keychain problems.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hi Quinn,

Thanks for your previous explanation. After further investigation, I realized that our Keychain item structure might be related to the issue, so I’d like to share more details in case this helps narrow down the cause.

How we currently store data in a single Keychain item

For the same Keychain item, we store two different business values:

  • kSecValueData → contains the deviceId
  • kSecAttrAccount → contains a dynamic userKey

This means the item looks like this:

Service: <fixed service name>
Account: <userKey>           // changes when user switches account
Value: <deviceId>
kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlock

Potential problem we’ve discovered

Because the userKey (stored in kSecAttrAccount) can change over time, our update operation may unintentionally rewrite or merge attributes in a way that leaves some users with inconsistent Keychain item metadata.

For example:

  1. Version A of the app may write incorrect or incomplete attributes.
  2. Version B fixes the attributes, but existing users still keep the old item with mismatched attributes.
  3. The item may end up with:
  • a different account name than the app expects
  • missing or incorrect accessible settings
  • values written by older versions of the app
  1. In this state, the item sometimes becomes readable in the foreground but fails in the background with:errSecInteractionNotAllowed (-25308)

My question

From your experience, could this type of Keychain item design— storing two business values (deviceId + userKey) inside the same item and allowing attributes like kSecAttrAccount to change across app versions—

lead to the kind of attribute inconsistency that causes background reads to fail even when kSecAttrAccessibleAfterFirstUnlock has been set correctly in the latest version?

If this pattern is unsafe, we are considering splitting it into two independent Keychain items:

  • One item dedicated to deviceId
  • A separate item for userKey
  • Each item with stable kSecAttrAccount and fixed attributes
  • Plus a migration mechanism to repair old inconsistent records

Before implementing this migration, I’d like to confirm whether this theory aligns with how Keychain attributes are expected to behave internally, especially across updates.

Any further guidance you can provide would be greatly appreciated. Thanks again for your time and expertise!

Share and Enjoy,

— Yvan

could this type of Keychain item design … lead to the kind of attribute inconsistency

It’s certainly possible.

My experience is that there’s a lot of really bad keychain code out there )-: That’s partly because the SecItem API is way more subtle than people think, and partly because Apple’s initial documentation and sample code for it was… well… less than ideal.

Moreover, the keychain is a persistent database where it’s important to not lose user state. So if your app has been around for a while, debugging a keychain problem means that you have to understand your current code and all the previous iterations of that code.

There’s a few ways to tackle a problem like this:

  • You could use source control to resurrect old versions of your keychain code, put all that code in a test app, and then try it out.
  • You could use telemetry to understand the state of your users’ keychain items in practice.
  • You could do what you’re proposing here, which is to migrate to a new system in a way that leaves behind all the historical baggage.

Honestly, that’s your call to make, but I certainly do understand the attraction of the last option.

IMPORTANT If you pursue the last option, make sure your new keychain code is well designed. I have a bunch of hints and tips on that front in:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Unexpected errSecInteractionNotAllowed (-25308) When Reading Keychain Item with kSecAttrAccessibleAfterFirstUnlock in Background
 
 
Q