I regularly help developers with keychain problems, both here on DevForums and for my Day Job™ in DTS. Many of these problems are caused by a fundamental misunderstanding of how the keychain works. This post is my attempt to explain that. I wrote it primarily so that Future Quinn™ can direct folks here rather than explain everything from scratch (-:
If you have questions or comments about any of this, put them in a new thread and apply the Security tag so that I see it.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
SecItem: Fundamentals
or How I Learned to Stop Worrying and Love the SecItem API
The SecItem API seems very simple. After all, it only has four function calls, how hard can it be? In reality, things are not that easy. Various factors contribute to making this API much trickier than it might seem at first glance.
This post explains the fundamental underpinnings of the keychain. For information about specific issues, see its companion post, SecItem: Pitfalls and Best Practices.
Keychain Documentation
Your basic starting point should be Keychain Items.
If your code runs on the Mac, also read TN3137 On Mac keychain APIs and implementations.
Read the doc comments in <Security/SecItem.h>. In many cases those doc comments contain critical tidbits.
When you read keychain documentation [1] and doc comments, keep in mind that statements specific to iOS typically apply to iPadOS, tvOS, and watchOS as well (r. 102786959). Also, they typically apply to macOS when you target the data protection keychain. Conversely, statements specific to macOS may not apply when you target the data protection keychain.
[1] Except TN3137, which is very clear about this (-:
Caveat Mac Developer
macOS supports two different keychain implementations: the original file-based keychain and the iOS-style data protection keychain.
IMPORTANT If you’re able to use the data protection keychain, do so. It’ll make your life easier. See the Careful With that Shim, Mac Developer section of SecItem: Pitfalls and Best Practices for more about this.
TN3137 On Mac keychain APIs and implementations explains this distinction. It also says:
The file-based keychain is on the road to deprecation.
This is talking about the implementation, not any specific API. The SecItem API can’t be deprecated because it works with both the data protection keychain and the file-based keychain. However, Apple has deprecated many APIs that are specific to the file-based keychain, for example, SecKeychainCreate.
TN3137 also notes that some programs, like launchd daemons, can’t use the file-based keychain. If you’re working on such a program then you don’t have to worry about the deprecation of these file-based keychain APIs. You’re already stuck with the file-based keychain implementation, so using a deprecated file-based keychain API doesn’t make things worse.
The Four Freedoms^H^H^H^H^H^H^H^H Functions
The SecItem API contains just four functions:
SecItemAdd(_:_:)
SecItemCopyMatching(_:_:)
SecItemUpdate(_:_:)
SecItemDelete(_:)
These directly map to standard SQL database operations:
SecItemAdd(_:_:) maps to INSERT.
SecItemCopyMatching(_:_:) maps to SELECT.
SecItemUpdate(_:_:) maps to UPDATE.
SecItemDelete(_:) maps to DELETE.
You can think of each keychain item class (generic password, certificate, and so on) as a separate SQL table within the database. The rows of that table are the individual keychain items for that class and the columns are the attributes of those items.
Note Except for the digital identity class, kSecClassIdentity, where the values are split across the certificate and key tables. See Digital Identities Aren’t Real in SecItem: Pitfalls and Best Practices.
This is not an accident. The data protection keychain is actually implemented as an SQLite database. If you’re curious about its structure, examine it on the Mac by pointing your favourite SQLite inspection tool — for example, the sqlite3 command-line tool — at the keychain database in ~/Library/Keychains/UUU/keychain-2.db, where UUU is a UUID.
WARNING Do not depend on the location and structure of this file. These have changed in the past and are likely to change again in the future. If you embed knowledge of them into a shipping product, it’s likely that your product will have binary compatibility problems at some point in the future. The only reason I’m mentioning them here is because I find it helpful to poke around in the file to get a better understanding of how the API works.
For information about which attributes are supported by each keychain item class — that is, what columns are in each table — see the Note box at the top of Item Attribute Keys and Values. Alternatively, look at the Attribute Key Constants doc comment in <Security/SecItem.h>.
Uniqueness
A critical part of the keychain model is uniqueness. How does the keychain determine if item A is the same as item B? It turns out that this is class dependent. For each keychain item class there is a set of attributes that form the uniqueness constraint for items of that class. That is, if you try to add item A where all of its attributes are the same as item B, the add fails with errSecDuplicateItem. For more information, see the errSecDuplicateItem page. It has lists of attributes that make up this uniqueness constraint, one for each class.
These uniqueness constraints are a major source of confusion, as discussed in the Queries and the Uniqueness Constraints section of SecItem: Pitfalls and Best Practices.
Parameter Blocks Understanding
The SecItem API is a classic ‘parameter block’ API. All of its inputs are dictionaries, and you have to know which properties to set in each dictionary to achieve your desired result. Likewise for when you read properties in output dictionaries.
There are five different property groups:
The item class property, kSecClass, determines the class of item you’re operating on: kSecClassGenericPassword, kSecClassCertificate, and so on.
The item attribute properties, like kSecAttrAccessGroup, map directly to keychain item attributes.
The search properties, like kSecMatchLimit, control how the system runs a query.
The return type properties, like kSecReturnAttributes, determine what values the query returns.
The value type properties, like kSecValueRef perform multiple duties, as explained below.
There are other properties that perform a variety of specific functions. For example, kSecUseDataProtectionKeychain tells macOS to use the data protection keychain instead of the file-based keychain. These properties are hard to describe in general; for the details, see the documentation for each such property.
Inputs
Each of the four SecItem functions take dictionary input parameters of the same type, CFDictionary, but these dictionaries are not the same. Different dictionaries support different property groups:
The first parameter of SecItemAdd(_:_:) is an add dictionary. It supports all property groups except the search properties.
The first parameter of SecItemCopyMatching(_:_:) is a query and return dictionary. It supports all property groups.
The first parameter of SecItemUpdate(_:_:) is a pure query dictionary. It supports all property groups except the return type properties.
Likewise for the only parameter of SecItemDelete(_:).
The second parameter of SecItemUpdate(_:_:) is an update dictionary. It supports the item attribute and value type property groups.
Outputs
Two of the SecItem functions, SecItemAdd(_:_:) and SecItemCopyMatching(_:_:), return values. These output parameters are of type CFTypeRef because the type of value you get back depends on the return type properties you supply in the input dictionary:
If you supply a single return type property, except kSecReturnAttributes, you get back a value appropriate for that return type.
If you supply multiple return type properties or kSecReturnAttributes, you get back a dictionary. This supports the item attribute and value type property groups. To get a non-attribute value from this dictionary, use the value type property that corresponds to its return type property. For example, if you set kSecReturnPersistentRef in the input dictionary, use kSecValuePersistentRef to get the persistent reference from the output dictionary.
In the single item case, the type of value you get back depends on the return type property and the keychain item class:
For kSecReturnData you get back the keychain item’s data. This makes most sense for password items, where the data holds the password. It also works for certificate items, where you get back the DER-encoded certificate. Using this for key items is kinda sketchy. If you want to export a key, called SecKeyCopyExternalRepresentation. Using this for digital identity items is nonsensical.
For kSecReturnRef you get back an object reference. This only works for keychain item classes that have an object representation, namely certificates, keys, and digital identities. You get back a SecCertificate, a SecKey, or a SecIdentity, respectively.
For kSecReturnPersistentRef you get back a data value that holds the persistent reference.
Value Type Subtleties
There are three properties in the value type property group:
kSecValueData
kSecValueRef
kSecValuePersistentRef
Their semantics vary based on the dictionary type.
For kSecValueData:
In an add dictionary, this is the value of the item to add. For example, when adding a generic password item (kSecClassGenericPassword), the value of this key is a Data value containing the password.
This is not supported in a query dictionary.
In an update dictionary, this is the new value for the item.
For kSecValueRef:
In add and query dictionaries, the system infers the class property and attribute properties from the supplied object. For example, if you supply a certificate object (SecCertificate, created using SecCertificateCreateWithData), the system will infer a kSecClass value of kSecClassCertificate and various attribute values, like kSecAttrSerialNumber, from that certificate object.
This is not supported in an update dictionary.
For kSecValuePersistentRef:
For query dictionaries, this uniquely identifies the item to operate on.
This is not supported in add and update dictionaries.
Revision History
2025-05-28 Expanded the Caveat Mac Developer section to cover some subtleties associated with the deprecation of the file-based keychain.
2023-09-12 Fixed various bugs in the revision history. Added a paragraph explaining how to determine which attributes are supported by each keychain item class.
2023-02-22 Made minor editorial changes.
2023-01-28 First posted.
General
RSS for tagPrioritize user privacy and data security in your app. Discuss best practices for data handling, user consent, and security measures to protect user information.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
Without developer mode, I was able to get Password AutoFill to work in my SwiftUI app with my local Vapor server using ngrok and adding the Associated Domains capability with the value webcredentials:....ngrok-free.app and the respective apple-app-site-association file on my local server in /.well-known/. (works on device, but not in the simulator).
However, if I use the developer mode (webcredentials:....ngrok-free.app?mode=developer) it only works halfway when running from Xcode: I get asked to save the password, but the saved passwords are not picked up, when I try to login again. Neither on device, nor in the simulator. If I remove the ?mode=developer it seems to work as expected.
Is this by design, or am I missing something?
var body: some View {
...
Section(header: Text("Email")) {
TextField("Email", text: $viewModel.credentials.username)
.textContentType(.username)
.autocapitalization(.none)
.keyboardType(.emailAddress)
}
Section(header: Text("Passwort")) {
SecureField("Passwort", text: $viewModel.credentials.password)
.textContentType(.password)
}
...
}
Topic:
Privacy & Security
SubTopic:
General
Tags:
SwiftUI
Universal Links
Authentication Services
Autofill
We are currently trying to fix a bug when using SignIn with Apple. It appears that on some occasions we are not receiving a user's profile info (name, email) when a new account is created.
After doing some investigation we believe this bug is due to the same Apple login being used as an already deleted account. ASF only appears to send profile info the very first time an Apple login is used. If that account is deleted and another is created with the same apple login we won't receive the profile info.
As a result we are not in compliance with Apple's guidelines requiring that we use the provided profile info with Apple SigIn, and need to prompt users to enter it again.
Is there a process in place to properly "clear" a user after their account is deleted in our system, so that the next time a user creates an account with the same Apple login, we receive their profile info again?
Our company has a micro service which sends a notification email to an iCloud account/email and the email is going to the junk folder. As we tested, the email generated from user-field.company.com goes to the Inbox, while the email from user-dev.company.com goes to the Junk folder. Is there a way to avoid sending the emails to client's Junk folder when the email is sent from a specific company domain?
Is there a way (in code or on the OAuth2 server/webpage) to specify the desired window size when using ASWebAuthenticationSession on macOS? I haven't found anything, and we would prefer the window to be narrower. For one of our users, the window is even stretched to the full screen width which looks completely broken…
With the new ios 26 update, certain numbers will be filtered into other inboxes within imessage. What numbers are classified as "known", and will not be moved into these filters. Do they need to be a contact in your phone, or if a business texts you how will that be filtered?
Topic:
Privacy & Security
SubTopic:
General
Hi everyone,
We are using the App Attest API to securely transition users to our new system. As part of this, we store the Key ID of the attestation key for each user to verify their identity later.
However, we’ve noticed that some users are encountering the error “DCErrorInvalidKey 3” when calling generateAssertion. Importantly, the key was previously successfully attested, and generateAssertion has worked before for these users.
Our questions:
Could this error be caused by an app or iOS update?
Is it problematic to link an attestation key's Key ID directly to a user, or are there scenarios where the key might change or become invalid?
If there’s a way to mitigate this issue or recover affected users, what best practices would you recommend?
Any help or shared experiences would be greatly appreciated! Thanks in advance.
I noticed, that even though my AutoFill Credential Provider Extension works with Safari for both Passwords and Passkeys, it doesn't work in context menus inside arbitrary textfields, meanwhile the same is true for the Apple Passwords app. This is a great hit to AutoFill productivity, as my extension is unable to fill textfields by just going to the context menu and clicking AutoFill > Passwords..
Is this a feature only available to Apple via private APIs, or is this something I can interface with?
I checked and the Passwords app does use some undocumented but non-private entitlements:
[Key] com.apple.authentication-services.access-credential-identities
[Value]
[Bool] true
I also checked the responsible executable for some hints (AutoFillPanelService) however found nothing that would lead me to believe this is a public extension point.
Another idea I had was trying to use a macOS Service for this, however Services in the "General" category won't show up in any context menu, only in the Application's Main Menu.
General:
Forums subtopic: Privacy & Security > General
Forums tag: App Sandbox
App Sandbox documentation
App Sandbox Design Guide documentation — This is no longer available from Apple. There’s still some info in there that isn’t covered by the current docs but, with the latest updates, it’s pretty minimal (r. 110052019). Still, if you’re curious, you can consult an old copy [1].
App Sandbox Temporary Exception Entitlements archived documentation — To better understand the role of temporary exception entitlements, see this post.
Embedding a command-line tool in a sandboxed app documentation
Discovering and diagnosing App Sandbox violations (replaces the Viewing Sandbox Violation Reports forums post)
Resolving App Sandbox Inheritance Problems forums post
The Case for Sandboxing a Directly Distributed App forums post
Implementing Script Attachment in a Sandboxed App forums post
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
[1] For example, this one archived by the Wayback Machine.
Hello
I'm using Auth0 for handling auth in my app
When the user wants to sign in, it will show the auth system pop-up
And when the user wants to log out it shows the same pop-up
My issue is how to replace the Sign In text in this pop-up to show Sign Out instead of Sign In when the user wants to sign out?
We have a crash on DCDevice.current.isSupported
We want to try to make a serial queue to generate tokens but the side effect would be the same token would be used on multiple server API requests that are made within a few ms of each other?
Is this safe or will the Apple server immediately reject the same token being reused?
Can you share how long tokens are safe to use for?
Here is the code we want to try
final actor DeviceTokenController: NSObject {
static var shared: DeviceTokenController = .init()
private var tokenGenerationTask: Task<Data?, Never>?
var ephemeralDeviceToken: Data? {
get async {
// Re-using the token for short periods of time
if let existingTask = tokenGenerationTask {
return await existingTask.value
}
let task = Task<Data?, Never> {
guard DCDevice.current.isSupported else { return nil }
do {
return try await DCDevice.current.generateToken()
} catch {
Log("Failed to generate ephemeral device token", error)
return nil
}
}
tokenGenerationTask = task
let result = await task.value
tokenGenerationTask = nil
return result
}
}
}
Hi Apple Devs,
For our app, we utilize passkeys for account creation (not MFA). This is mainly for user privacy, as there is 0 PII associated with passkey account creation, but it additionally also satisfies the 4.8: Login Services requirement for the App Store.
However, we're getting blocked in Apple Review. Because the AASA does not get fetched immediately upon app install, the reviewers are not able to create an account immediately via passkeys, and then they reject the build.
I'm optimistic I can mitigate the above. But even if we pass Apple Review, this is a pretty catastrophic issue for user security and experience. There are reports that 5% of users cannot create passkeys immediately (https://developer.apple.com/forums/thread/756740). That is a nontrivial amount of users, and this large of an amount distorts how app developers design onboarding and authentication flows towards less secure experiences:
App developers are incentivized to not require MFA setup on account creation because requiring it causes significant churn, which is bad for user security.
If they continue with it anyways, for mitigation, developers are essentially forced to add in copy into their app saying something along the lines of "We have no ability to force Apple to fetch the config required to continue sign up, so try again in a few minutes, you'll just have to wait."
You can't even implement a fallback method. There's no way to check if the AASA is available before launching the ASAuthorizationController so you can't mitigate a portion of users encountering an error!!
Any app that wants to use the PRF extension to encrypt core functionality (again, good for user privacy) simply cannot exist because the app simply does not work for an unspecified amount of time for a nontrivial portion of users.
It feels like a. Apple should provide a syscall API that we can call to force SWCD to verify the AASA or b. implement a config based on package name for the app store such that the installation will immediately include a verified AASA from Apple's CDN. Flicking the config on would require talking with Apple. If this existed, this entire class of error would go away.
It feels pretty shocking that there isn't a mitigation in place for this already given that it incentivizes app developers to pursue strictly less secure and less private authentication practices.
Topic:
Privacy & Security
SubTopic:
General
Tags:
Authentication Services
Universal Links
Passkeys in iCloud Keychain
I have a launch daemon that's using the Endpoint Security framework which also is causing high memory usage (in Activity Monitor memory column shows for example 2GB and Real Memory 11MB) when building a big project in Xcode. Is it some kind of memory caching by the system? leaks -forkCorpse seems to not show any leaks.
How can I attach with heap or Instruments without the process being killed with "ENDPOINTSECURITY, Code 2 EndpointSecurity client terminated because it failed to respond to a message before its deadline"?
My application is supporting hybrid transport on FIDO2 webAuthn specs to create credential and assertion. And it support legacy passkeys which only mean to save to 1 device and not eligible to backup.
However In my case, if i set the Backup Eligibility and Backup State flag to false, it fails on the completion of the registrationRequest to save the passkey credential within credential extension, the status is false instead of true.
self.extension.completeRegistrationRequest(using: passkeyRegistrationCredential)
The attestation and assertion flow only works when both flags set to true.
Can advice why its must have to set both to true in this case?
Hello everyone,
In my application, i have implemented authentication using ASWebauthenticationSession. However, when redirecting the user to a WKWebView, no cookies are shared, causing the session to be lost and requiring the user to log in again.
Is there a way to share cookies between the two? If not, what would be the best approach to set up authentication that ensures SSO when switching to a WebView ?
Thank you very much for your help !
Dear Apple Developer Support,
We are currently encountering a recurring issue with the DeviceCheck API across multiple devices in our production environment.
The following error is frequently returned:
com.apple.devicecheck.error 0 We would like to ask the following:
What are the possible underlying causes that could lead to this specific error code (0) in the DeviceCheck API?
Is there any known behavior or condition where Wi-Fi network configurations (e.g., DNS filtering, proxy settings, captive portals) could result in this error?
Are there known timeouts, connectivity expectations, or TLS-level requirements that the DeviceCheck API enforces which could fail silently under certain network conditions?
Is this error ever triggered locally (e.g., client library-level issues) or is it always from a failed communication with Apple’s servers?
Any technical clarification, documentation, or internal insight into this error code would be greatly appreciated. This would help us significantly narrow down root causes and better support our users
I am developing a sample authorization plugin to sync the user’s local password to the network password. During the process, I prompt the user to enter both their old and new passwords in custom plugin. After the user enters the information, I use the following code to sync the passwords:
try record.changePassword(oldPssword, toPassword: newPassword)
However, I have noticed that this is clearing all saved keychain information, such as web passwords and certificates. Is it expected behavior for record.changePassword to clear previously stored keychain data?
If so, how can I overcome this issue and ensure the keychain information is preserved while syncing the password?
Thank you for your help!
First, I do not publish my application to the AppStore, but I need to customize a sandbox environment. It seems that sandbox-exec cannot configure entitlements, so I have used some other APIs, such as sandbox_compile_entitlements and sandbox_apply_container. When encountering the entitlement "com.apple.security.files.user-selected.read-only", I am unsure how to correctly write sandbox profile to implement this. Can anyone help me?
can i get transferid by /auth/usermigrationinfo api before transfered app?
https://developer.apple.com/documentation/sign_in_with_apple/transferring-your-apps-and-users-to-another-team#Generate-the-transfer-identifier
I’m having an issue with my Credential Provider Extension for passkey registration. On the browser I click on registration, in IOS i can select my App for passkey registration with a continue button. Wenn I click the continue button the prepareInterface(forPasskeyRegistration:) function is called but the MainInterface is not shown —it only appears when I click the continue button a second time.
Here’s a simplified version of my prepareInterface method:
override func prepareInterface(forPasskeyRegistration registrationRequest: ASCredentialRequest) {
guard let request = registrationRequest as? ASPasskeyCredentialRequest,
let identity = request.credentialIdentity as? ASPasskeyCredentialIdentity else {
extensionContext.cancelRequest(withError: ASExtensionError(.failed))
return
}
self.identity = identity
self.request = request
log.info("prepareInterface called successfully")
}
In viewDidAppear, I trigger FaceID authentication and complete the registration process if register is true. However, the UI only shows after a second “Continue” tap.
Has anyone encountered this behavior or have suggestions on how to ensure the UI appears immediately after prepareInterface is called? Could it be a timing or lifecycle issue with the extension context?
Thanks for any insights!