Prioritize user privacy and data security in your app. Discuss best practices for data handling, user consent, and security measures to protect user information.

All subtopics
Posts under Privacy & Security topic

Post

Replies

Boosts

Views

Activity

How can an iOS browser app request the Web Browser Public Key Credential entitlement for Passkeys?
Hello Apple Developer Team, I am developing a general-purpose web browser for iOS using WKWebView. Current status: • The app is distributed through TestFlight (Internal Testing). • The app is registered in App Store Connect. • The browser supports standard web browsing with multiple tabs and arbitrary websites. My goal is to support WebAuthn passkey authentication for websites such as Google, GitHub, Microsoft, and other websites that support passkeys. While reviewing Apple's documentation, I found: "Passkey use in web browsers" "Authenticating people by using passkeys in browser apps" These documents mention that browser apps on iOS can support passkeys using: ASAuthorizationWebBrowserPublicKeyCredentialManager and the entitlement: com.apple.developer.web-browser.public-key-credential However, I could only find a request form titled: "Request the macOS Web Browser Public Key Credential Entitlement" which specifically asks: "Is your app a web browser on macOS?" My application is an iOS web browser, not a macOS browser. In addition, I previously requested the Default Web Browser entitlement, but my request was declined. My questions are: Is there a separate application process for requesting the Web Browser Public Key Credential entitlement for an iOS browser? Is approval of the Default Web Browser entitlement required before an iOS browser can use WebAuthn passkeys? Can an iOS browser request only the Web Browser Public Key Credential entitlement without becoming the system default browser? Is there any official documentation describing the entitlement request process for iOS browser apps? I know that third-party browsers such as Aloha Browser appear to support system Passkeys on iOS, so I would like to understand the correct implementation and entitlement process for an iOS browser. Thank you very much for your guidance.
1
0
200
2w
Identifying system OCSP/CRL traffic in Network Extension.
Hi! We're developing a security product that uses both EndpointSecurity.framework to intercept and authorize process and file events; and NetworkExtension.framework o intercept and inspect network connections. We're occasionally seeing crashes caused by Endpoint Security timeouts. After investigating several crash reports, we believe we've identified a deadlock involving code signature verification: our Network Extension intercepts connections initiated by nsurlsessiond to retrieve OCSP/CRL data (we believe these requests are made on behalf of trustd during code signature validation). To determine which policy should be applied to an intercepted connection, our Network Extension verifies the code signature of the originating process. However, that code signature verification itself blocks while waiting for the OCSP/CRL requests to complete. Since those requests are being intercepted by our Network Extension, we end up with a circular dependency: A process requires code signature verification. Signature verification triggers OCSP/CRL network requests. Those requests are intercepted by our Network Extension. Our Network Extension attempts to verify the initiator's code signature before allowing the connection. That verification waits for the same OCSP/CRL requests to complete. As a result, code signature verification becomes blocked process-wide, including verification performed while handling Endpoint Security events. Eventually, our Endpoint Security client exceeds the allowed response timeout and is terminated. We're considering bypassing interception for OCSP/CRL traffic to avoid this deadlock, but we'd like to understand whether this is the recommended or most robust approach. Questions Is there a reliable way to identify network connections that are fetching OCSP or CRL data for code signature validation? What is the relationship between trustd and nsurlsessiond for these requests? Is there a dedicated nsurlsessiond instance serving trustd, or are these requests performed by the shared system/session-wide nsurlsessiond? Would it be a reasonable and future-proof approach to identify these requests by checking NEAppProxyFlow.remoteHostname (for example, ocsp.apple.com and crl.apple.com) and bypassing interception for those connections? Is there another recommended approach to avoid this deadlock when combining Endpoint Security and Network Extension in this way? Any guidance or best practices would be greatly appreciated. Thank you!
1
0
217
2w
Token endpoint returns invalid_client for real authorization codes, but invalid_grant (client auth accepted) for identical credentials with a test code
Sign in with Apple (web flow) fails 100% reproducibly at the token exchange for our newly created identifiers. POST to https://appleid.apple.com/auth/token with a REAL authorization code → 400 {"error":"invalid_client"}. The exact same client_id + client_secret from the same production server with a dummy code → {"error":"invalid_grant"} — i.e. client authentication passes and only the code is rejected, as expected. 16/16 probe requests across both registered redirect_uris. The authorization phase succeeds (user completes the appleid.apple.com sheet; code arrives via form_post to the registered return URL) and the exchange happens within ~1 second, so code expiry is not the cause. client_secret is an ES256 JWT with correct iss/sub/aud, validity ~6 months, sent via client_secret_post with no Authorization header. I have reviewed TN3107 — none of the documented invalid_client causes fit, given the invalid_grant asymmetry above. Persisted 24+ hours and survived: recreating the Services ID under a new identifier with a freshly minted secret; toggling the Sign in with Apple capability off/on (as primary) on the App ID; re-saving the Services ID configuration (domains and both return URLs re-confirmed). Team ID: CGSLWL988T Primary App ID: app.bookagym (created 2026-07-10) Services ID: app.bookagym.signin (an earlier Services ID app.bookagym.web behaved identically) Key ID: C99J756C86 This looks like stuck or unpropagated server-side provisioning for these identifiers rather than a client-side error. Per the "Gathering required information for troubleshooting Sign in with Apple authorization and token requests" post, I have filed the full details (including the failing request with all parameter values and a long-lived client secret) in Feedback Assistant: FB23692739
0
0
235
3w
Unable to verify the app
Hey guys, I am having issue, unable to verify the app. An internet connection is required to verify trust of the developer .... I am connected to the internet, date and time is correct. I did some google, some one reported this link: https://ppq-ext.v.aaplimg.com/ ssl expired, which is causing this issue. Can anyone help? I am testing to test my app on Iphone 15 pro max.
0
0
224
3w
Native Sign in with Apple in Capacitor + NextAuth
I'm building an iOS app using Capacitor with a Next.js 14 backend. Authentication currently uses: NextAuth Prisma Sign in with Apple Google Sign-In Magic Link The web version works correctly. For the native iOS app, tapping Continue with Apple currently opens the NextAuth endpoint (/api/auth/signin/apple), which launches the web authentication flow. App Review rejected the app because the Sign in with Apple flow leaves the native experience and opens a web view/browser during sign in. My goal is to keep Sign in with Apple fully native while continuing to use the same Prisma user database and authentication system used by the web application. What is Apple's recommended architecture for this scenario? Should I: Perform native Sign in with Apple in the Capacitor app. Send the returned identity token to my backend. Verify the token server-side. Create my own authenticated session instead of using the NextAuth Apple provider. Or is there another approach Apple recommends for hybrid apps using Capacitor? Any guidance would be greatly appreciated.
0
0
195
3w
Validating Signature Of XPC Process
Quinn, you've often suggested that to validate the other side of an XPC connection, we should use the audit token. But that's not available from the XPC object, whereas the PID is. So everyone uses the PID. While looking for something completely unrelated, I found this in the SecCode.h file OSStatus SecCodeCreateWithXPCMessage(xpc_object_t message, SecCSFlags flags, SecCodeRef * __nonnull CF_RETURNS_RETAINED target); Would this be the preferred way to do this now? At least from 11.0 and up. Like I said, I was looking for something completely unrelated and found this and don't have the cycles right now to try it. But it looks promising from the description and I wanted to check in with you about it in case you can say yes or no before I get a chance to test it. Thanks
8
0
8.7k
3w
resetKeys() also resets sharedDeviceSigningKey unexpectedly
I am using ASAuthorizationProviderExtensionLoginManager.resetKeys() to generate new user-specific keys, specifically userDeviceSigningKey and userDeviceEncryptionKey. Based on the documentation, my understanding was that resetKeys() only resets keys associated with a particular user account: https://developer.apple.com/documentation/authenticationservices/asauthorizationproviderextensionloginmanager/resetkeys/ However, during testing, I observed that calling resetKeys() also resets sharedDeviceSigningKey. I had assumed that shared device keys would only be reset via resetDeviceKeys().
1
1
795
3w
macOS 27 beta: LocalAuthenticationView causes LAContext policy evaluation to fail with LAErrorDomain -1007
I’m seeing a regression in macOS 27 beta when using SwiftUI LocalAuthenticationView. When an LAContext is attached to LocalAuthenticationView, subsequent policy evaluation fails immediately with: Error Domain=com.apple.LocalAuthentication Code=-1007 NSDebugDescription="Caller is not Apple signed." NSLocalizedDescription="Authentication denied." The same policies work when evaluated on a plain LAContext that has not been attached to LocalAuthenticationView. Minimal shape of the failing path: @State private var context = LAContext() LocalAuthenticationView(context: context) { EmptyView() } context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Unlock") { success, error in print(success, error as Any) } This affects Touch ID unlock in our macOS app. We currently work around it by detecting LAErrorDomain / -1007, removing LocalAuthenticationView, and asking the user to manually start Touch ID with a fresh LAContext. Filed as Feedback: FB23262713 Could someone from the beta / LocalAuthentication team confirm whether this is an intended restriction for LocalAuthenticationView, or a macOS 27 beta regression?
3
2
533
3w
Sign in with Apple Failure, AKAuthenticationServerError=-24000
Apple Developer Support Request - Sign in with Apple Failure Date prepared: 2026-07-08 App name: LingJing / XiaoLing Bundle ID: com.jachymchen.xiaoling Apple Developer Team ID: 765B64SW9Z Platform: iOS Summary Sign in with Apple fails before the app receives a usable Apple identity token. The user-facing Apple system sheet ends with the Chinese alert "未完成注册" ("Sign Up Not Completed"). The app callback does not receive an identityToken, so our backend auth provider is not reached successfully. We reproduced the same failure in a minimal native Swift app that only uses AuthenticationServices and the same Bundle ID / provisioning profile, without Supabase or any app-specific backend code. This suggests the failure is likely in the Apple Sign in with Apple authorization flow, account/device state, or App ID/provisioning configuration recognized by Apple's auth services, rather than in our app's backend implementation. Environment Mac: macOS 26.5 Apple Silicon MacBook Pro Xcode 26.5 iPhone: Real device connected over USB Device name: 硬糖的iPhone iOS 26.5.1 Device UDID: 00008150-000344540C99401C App: Bundle ID: com.jachymchen.xiaoling App version currently used for testing: 0.1.0 App Store Connect app ID observed during TestFlight work: 6784075688 TestFlight build upload previously succeeded for version 0.1.0 build 2 Provisioning / Entitlements Checked Development provisioning profile: Profile name: XiaoLing Dev application-identifier: 765B64SW9Z.com.jachymchen.xiaoling com.apple.developer.applesignin: [Default] com.apple.developer.team-identifier: 765B64SW9Z get-task-allow: true keychain-access-groups includes: 765B64SW9Z.* com.apple.token Profile includes the test device UDID: 00008150-000344540C99401C Expiration: 2027-06-27 Distribution / TestFlight provisioning profile: Profile name: XiaoLing AppStore application-identifier: 765B64SW9Z.com.jachymchen.xiaoling com.apple.developer.applesignin: [Default] beta-reports-active: true get-task-allow: false Signed app entitlements were checked from the executable and matched the expected Bundle ID / Team ID / Sign in with Apple entitlement. Important note: Running codesign -d --entitlements :- XiaoLing.app against the .app bundle may print an "invalid entitlements blob" style result. Running codesign against the actual executable inside the .app is the useful check, and that showed the expected entitlements. Reproduction Steps Install and launch the iOS app with Bundle ID com.jachymchen.xiaoling on the real iPhone. Tap "Sign in with Apple". Complete the Apple ID system prompt. The Apple system flow fails and shows "未完成注册". The app does not receive a valid ASAuthorizationAppleIDCredential identity token. Observed Result The Apple authorization flow fails before returning a usable credential to the app. The system UI shows "未完成注册" ("Sign Up Not Completed"). Expected Result AuthenticationServices should return an ASAuthorizationAppleIDCredential with an identityToken so the app can continue its own backend sign-in. Key Device Logs Observed During real-device attempts, the following log lines appeared around the failure: AuthKit continuation-key-creation token is missing AppleIDAuthSupport: setError: 2:M2 missing (bad password) SRP authentication with server failed AUTH_ALERT_SIGN_UP_NOT_COMPLETED -> 未完成注册 AKRemoteViewController did complete with authorization (null) AKAuthenticationServerError Code=-24000 The private framework error code by itself is not enough to identify the root cause, but the flow consistently fails inside Apple's AuthKit / Apple ID authorization path before our app receives an identity token. Minimal Native Demo Test To exclude app-specific implementation and backend issues, we created a minimal native SwiftUI app using only: AuthenticationServices SignInWithAppleButton ASAuthorizationAppleIDProvider The same Bundle ID: com.jachymchen.xiaoling The same Team ID / provisioning entitlement setup No Supabase No custom backend No React Native No third-party auth library The minimal native demo produced the same user-facing failure: "未完成注册". This strongly suggests the issue is not caused by Supabase, nonce hashing, OAuth handling, React Native, or our app UI. The failure happens before any backend auth exchange can occur. Additional Context The tester tried another Apple ID and still reproduced the failure. The tester stated the Apple ID itself is otherwise usable. The app's Bundle ID is intended to be exactly: com.jachymchen.xiaoling We specifically checked for provisioning profile / Bundle ID mismatch and did not find a mismatch in the installed build. Questions for Apple Developer Support Can Apple check whether App ID 765B64SW9Z.com.jachymchen.xiaoling has any server-side Sign in with Apple configuration issue? Can Apple explain what conditions produce: AUTH_ALERT_SIGN_UP_NOT_COMPLETED AKAuthenticationServerError Code=-24000 "M2 missing (bad password)" "AuthKit continuation-key-creation token is missing" in a native AuthenticationServices Sign in with Apple flow? Is there any known issue on iOS 26.5.1 or with development-signed apps where Sign in with Apple fails before returning an ASAuthorizationAppleIDCredential? Are there account-level or device-level requirements beyond normal Apple ID login that can cause Sign in with Apple to show "Sign Up Not Completed"? Can Apple verify whether this Bundle ID / Team ID is correctly enabled for Sign in with Apple on Apple's backend? Minimal Code Shape Used for Native Demo The native demo used a standard SignInWithAppleButton: SignInWithAppleButton(.signIn) { request in request.requestedScopes = [.fullName, .email] } onCompletion: { result in switch result { case .success(let authorization): // Check authorization.credential as ASAuthorizationAppleIDCredential // and read identityToken. case .failure(let error): // Log NSError domain, code, localizedDescription, and userInfo. } } Support Request Please help determine why Apple's Sign in with Apple authorization flow fails for Bundle ID com.jachymchen.xiaoling before returning a credential, despite the app having the Sign in with Apple entitlement and matching provisioning profile / Bundle ID.
0
1
177
3w
way to attest that a Secure Enclave key is hardware-bound on macOS
We generate Secure Enclave keys via SecKeyCreateRandomKey with kSecAttrTokenIDSecureEnclave on macOS. We need to prove to a remote server that the key is genuinely hardware-bound, not a software key claiming to be one. Is there any API on macOS for an app to obtain an Apple-signed certificate or attestation statement for such a Secure Enclave key, similar to how ASAuthorizationProviderExtensionLoginManager.attestKey() works within Platform SSO but available to general apps? Or other possible workaround for this? Thank you!
5
0
1.2k
3w
Sign In with Apple: AuthorizationError 1000, empty userInfo, delegate never receives authorization
Subject: Sign In with Apple consistently fails with AuthorizationError 1000 (empty userInfo) immediately after successful biometric authentication — native delegate logging confirms failure occurs before authorization is returned to app App ID: com.andresalecina.vant
Team ID: C6GK7Z5GY2
Services ID: com.andresalecina.vant.signin Summary:
Sign In with Apple fails 100% of the time in our native iOS app (Capacitor 7 wrapper) with error code 1000 and completely empty userInfo. The native authentication sheet displays correctly and the user completes Face ID successfully, but authentication fails immediately afterward. Critical new evidence (delegate-level logging):
We instrumented both ASAuthorizationControllerDelegate callback methods directly with NSLog to determine exactly where the failure occurs: authorizationController(controller:didCompleteWithAuthorization:) — never fires. Confirmed across multiple fresh test attempts. authorizationController(controller:didCompleteWithError:) — fires every time, receiving: Error Domain=com.apple.AuthenticationServices.AuthorizationError Code=1000 "(null)" with completely empty userInfo. This confirms the failure originates inside AuthenticationServices itself, before any authorization object is ever returned to our app. Our app code is not receiving a token, credential, or authorization of any kind to process — there is nothing for our code to mishandle. Everything already verified clean on our end: Sign In with Apple capability present in Xcode target and confirmed in Apple Developer Portal (Primary App ID, not grouped) Entitlement com.apple.developer.applesignin confirmed present in both the development-signed binary and the App Store distribution provisioning profile Team ID in the compiled binary matches the entitlement (verified via codesign -dv and codesign -d --entitlements) Provisioning profile freshly regenerated after capability was added Apple Developer Program License Agreement and Developer Agreement both fully accepted, no pending items Reproduces identically on two separate physical devices (one iOS 27 beta, one non-beta), on both WiFi and cellular Reproduces with a completely clean Apple ID test account (no prior Family Sharing/managed account involvement, confirmed adult standard account) Device clock, timezone, and Screen Time/Content & Privacy Restrictions confirmed not a factor Native plugin code reviewed line-by-line: uses stock ASAuthorizationAppleIDProvider().createRequest(), standard .fullName/.email scopes only, robust presentation anchor resolution (no detached/fake UIWindow) Timeline:
The Sign In with Apple capability was added to this App ID within the past 24–48 hours (it was missing entirely on our initial rejected App Store submission). Error 1000 has persisted consistently across many rebuilds since the capability was added, which raises the possibility of a propagation delay on Apple's authentication backend, but this has now exceeded typical propagation windows for other capabilities we've observed (Push Notifications, Associated Domains). What we're asking:
Can you verify, on Apple's side, whether App ID com.andresalecina.vant under Team C6GK7Z5GY2 is fully provisioned and active on Apple's Sign In with Apple authentication backend? All client-side, code-side, and account-side factors we can verify are confirmed correct; the delegate-level logging above confirms the failure is occurring before any authorization is returned to the client, which points to a state on Apple's servers that isn't visible to us through the standard Developer Portal UI. Happy to provide device sysdiagnose logs, the full Xcode archive, or a minimal reproduction project (bare SwiftUI, no Capacitor/Supabase) if useful.
1
0
417
Jul ’26
SecItem: Pitfalls and Best Practices
I regularly help developers with keychain problems, both here on DevForums and in various DTS cases. Over the years I’ve learnt a lot about the API, including many pitfalls and best practices. This post is my attempt to collect that experience in one place. 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: Pitfalls and Best Practices It’s just four functions, how hard can it be? 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 some of the keychain’s pitfalls and then goes on to explain various best practices. Before reading this, make sure you understand the fundamentals by reading its companion post, SecItem: Fundamentals. Pitfalls Lets start with some common pitfalls. Queries and Uniqueness Constraints The relationship between query dictionaries and uniqueness constraints is a major source of problems with the keychain API. Consider code like this: var copyResult: CFTypeRef? = nil let query = [ kSecClass: kSecClassGenericPassword, kSecAttrService: "AYS", kSecAttrAccount: "mrgumby", kSecAttrGeneric: Data("SecItemHints".utf8), ] as NSMutableDictionary let err = SecItemCopyMatching(query, &copyResult) if err == errSecItemNotFound { query[kSecValueData] = Data("opendoor".utf8) let err2 = SecItemAdd(query, nil) if err2 == errSecDuplicateItem { fatalError("… can you get here? …") } } Can you get to the fatal error? At first glance this might not seem possible because you’ve run your query and it’s returned errSecItemNotFound. However, the fatal error is possible because the query contains an attribute, kSecAttrGeneric, that does not contribute to the uniqueness. If the keychain contains a generic password whose service (kSecAttrService) and account (kSecAttrAccount) attributes match those supplied but whose generic (kSecAttrGeneric) attribute does not, the SecItemCopyMatching calls will return errSecItemNotFound. However, for a generic password item, of the attributes shown here, only the service and account attributes are included in the uniqueness constraint. If you try to add an item where those attributes match an existing item, the add will fail with errSecDuplicateItem even though the value of the generic attribute is different. The take-home point is that that you should study the attributes that contribute to uniqueness and use them in a way that’s aligned with your view of uniqueness. See the Uniqueness section of SecItem: Fundamentals for a link to the relevant documentation. Erroneous Attributes Each keychain item class supports its own specific set of attributes. For information about the attributes supported by a given class, see SecItem: Fundamentals. I regularly see folks use attributes that aren’t supported by the class they’re working with. For example, the kSecAttrApplicationTag attribute is only supported for key items (kSecClassKey). Using it with a certificate item (kSecClassCertificate) will cause, at best, a runtime error and, at worst, mysterious bugs. This is an easy mistake to make because: The ‘parameter block’ nature of the SecItem API means that the compiler won’t complain if you use an erroneous attribute. On macOS, the shim that connects to the file-based keychain ignores unsupported attributes. Imagine you want to store a certificate for a particular user. You might write code like this: let err = SecItemAdd([ kSecClass: kSecClassCertificate, kSecAttrApplicationTag: Data(name.utf8), kSecValueRef: cert, ] as NSDictionary, nil) The goal is to store the user’s name in the kSecAttrApplicationTag attribute so that you can get back their certificate with code like this: let err = SecItemCopyMatching([ kSecClass: kSecClassCertificate, kSecAttrApplicationTag: Data(name.utf8), kSecReturnRef: true, ] as NSDictionary, &copyResult) On iOS, and with the data protection keychain on macOS, both calls will fail with errSecNoSuchAttr. That makes sense, because the kSecAttrApplicationTag attribute is not supported for certificate items. Unfortunately, the macOS shim that connects the SecItem API to the file-based keychain ignores extraneous attributes. This results in some very bad behaviour: SecItemAdd works, ignoring kSecAttrApplicationTag. SecItemCopyMatching ignores kSecAttrApplicationTag, returning the first certificate that it finds. If you only test with a single user, everything seems to work. But, later on, when you try your code with multiple users, you might get back the wrong result depending on the which certificate the SecItemCopyMatching call happens to discover first. Ouch! Context Matters Some properties change behaviour based on the context. The value type properties are the biggest offender here, as discussed in the Value Type Subtleties section of SecItem: Fundamentals. However, there are others. The one that’s bitten me is kSecMatchLimit: In a query and return dictionary its default value is kSecMatchLimitOne. If you don’t supply a value for kSecMatchLimit, SecItemCopyMatching returns at most one item that matches your query. In a pure query dictionary its default value is kSecMatchLimitAll. For example, if you don’t supply a value for kSecMatchLimit, SecItemDelete will delete all items that match your query. This is a lesson that, once learnt, is never forgotten! Note Although this only applies to the data protection keychain. If you’re on macOS and targeting the file-based keychain, kSecMatchLimit always defaults to kSecMatchLimitOne. This is clearly a bug, but we can’t fix it due to compatibility concerns (r. 105800863). Fun times! Digital Identities Aren’t Real A digital identity is the combination of a certificate and the private key that matches the public key within that certificate. The SecItem API has a digital identity keychain item class, namely kSecClassIdentity. However, the keychain does not store digital identities. When you add a digital identity to the keychain, the system stores its components, the certificate and the private key, separately, using kSecClassCertificate and kSecClassKey respectively. This has a number of non-obvious effects: Adding a certificate can ‘add’ a digital identity. If the new certificate happens to match a private key that’s already in the keychain, the keychain treats that pair as a digital identity. Likewise when you add a private key. Similarly, removing a certificate or private key can ‘remove’ a digital identity. Adding a digital identity will either add a private key, or a certificate, or both, depending on what’s already in the keychain. Removing a digital identity removes its certificate. It might also remove the private key, depending on whether that private key is used by a different digital identity. The system forms a digital identity by matching the kSecAttrApplicationLabel (klbl) attribute of the private key with the kSecAttrPublicKeyHash (pkhh) attribute of the certificate. If you add both items to the keychain and the system doesn’t form an identity, check the value of these attributes. For more information the key attributes, see SecItem attributes for keys. Keys Aren’t Stored in the Secure Enclave Apple platforms let you protect a key with the Secure Enclave (SE). The key is then hardware bound. It can only be used by that specific SE [1]. Earlier versions of the Protecting keys with the Secure Enclave article implied that SE-protected keys were stored in the SE itself. This is not true, and it’s caused a lot of confusion. For example, I once asked the keychain team “How much space does the SE have available to store keys?”, a question that’s complete nonsense once you understand how this works. In reality, SE-protected keys are stored in the standard keychain database alongside all your other keychain items. The difference is that the key is constructed in such a way that only the SE can use it. So, the key is protected by the SE, not stored in the SE. A while back we updated the docs to clarify this point but the confusion persists. [1] Technically it’s that specific iteration of that specific SE. If you erase the device then the key material needed to use the key is erased and so the key becomes permanently useless. Or at least that’s my understanding of how things work (-: For details like this I defer to Apple Platform Security. Careful With that Shim, Mac Developer As explained in TN3137 On Mac keychain APIs and implementations, macOS has a shim that connects the SecItem API to either the data protection keychain or the file-based keychain depending on the nature of the request. That shim has limitations. Some of those are architectural but others are simply bugs in the shim. For some great examples, see the Investigating Complex Attributes section below. The best way to avoid problems like this is to target the data protection keychain. If you can’t do that, try to avoid exploring the outer reaches of the SecItem API. If you encounter a case that doesn’t make sense, try that same case with the data protection keychain. If it works there but fails with the file-based keychain, please do file a bug against the shim. It’ll be in good company. Here’s some known issues with the shim: It ignores unsupported attributes. See Erroneous Attributes, above, for more background on that. The shim can fan out to both the data protection and the file-based keychain. In that case it has to make a policy decision about how to handle errors. This results in some unexpected behaviour (r. 143405965). For example, if you call SecItemCopyMatching while the keychain is locked, the data protection keychain will fail with errSecInteractionNotAllowed (-25308). OTOH, it’s possible to query for the presence of items in the file-based keychain even when it’s locked. If you do that and there’s no matching item, the file-based keychain fails with errSecItemNotFound (-25300). When the shim gets these conflicting errors, it chooses to return the latter. Whether this is right or wrong depends on your perspective, but it’s certainly confusing, especially if you’re coming at this from the iOS side. If you call SecItemDelete without specifying a match limit (kSecMatchLimit), the data protection keychain deletes all matching items, whereas the file-based keychain just deletes a single match (r. 105800863). While these shim issue have all have bug numbers, there’s no guarantee that any of them will be fixed. Fixing bugs like this is tricky because of binary compatibility concerns. Add-only Attributes Some attributes can only be set when you add an item. These attributes are usually associated with the scope of the item. For example, to protect an item with the Secure Enclave, supply the kSecAttrAccessControl attribute to the SecItemAdd call. Once you do that, however, you can’t change the attribute. Calling SecItemUpdate with a new kSecAttrAccessControl won’t work. Lost Keychain Items A common complaint from developers is that a seemingly minor update to their app has caused it to lose all of its keychain items. Usually this is caused by one of two problems: Entitlement changes Query dictionary confusion Access to keychain items is mediated by various entitlements, as described in Sharing access to keychain items among a collection of apps. If the two versions of your app have different entitlements, one version may not be able to ‘see’ items created by the other. Let’s walk through an example of this. Imagine you have an app with an App ID of SKMME9E2Y8.com.example.waffle-varnisher. Version 1 of your app does nothing fancy with the keychain. It uses neither keychain access groups nor app groups. Thus its keychain access group list consists of just the App ID, that is, [ SKMME9E2Y8.com.example.waffle-varnisher ]. When that version of your app creates a keychain item, the kSecAttrAccessGroup value will default to the only value available, SKMME9E2Y8.com.example.waffle-varnisher. In version 2 of your app you want to use keychain access groups, so you add the Keychain Sharing capability to your project and populate it with two values, SKMME9E2Y8.groupA and SKMME9E2Y8.groupB. If you take no other action, your app’s keychain access group list will be [ SKMME9E2Y8.groupA, SKMME9E2Y8.groupB, SKMME9E2Y8.com.example.waffle-varnisher ]. This changes the default value for new items to SKMME9E2Y8.groupA. This is an obvious pitfall. Version 1 of your app created new keychain items in SKMME9E2Y8.com.example.waffle-varnisher while version 2 creates them in SKMME9E2Y8.groupA. You now have different items in different groups, depending on which version the user first launched, and that’s a recipe for chaos. There are two common ways to avoid problems here: Migrate items from SKMME9E2Y8.com.example.waffle-varnisher to SKMME9E2Y8.groupA. See Transfer Items Between Keychain Access Groups, below. Add your App ID to the front of the Keychain Sharing list. This results in a keychain access group list of [ SKMME9E2Y8.com.example.waffle-varnisher, SKMME9E2Y8.groupA, SKMME9E2Y8.groupB, SKMME9E2Y8.com.example.waffle-varnisher ], which means that the default keychain access group doesn’t change. (The second instance of SKMME9E2Y8.com.example.waffle-varnisher in this list is redundant but doesn’t cause any complications.) So far so good. Now let’s say you took the first option and shipped version 2 of your app with SKMME9E2Y8.groupA as the default keychain access group. You want to update the app again, to version 3, and you’ve decided that SKMME9E2Y8.groupA no longer makes sense and you want to remove it, relying on SKMME9E2Y8.groupB instead. Doing that isn’t safe. If version 3 of your app has no access to SKMME9E2Y8.groupA, it won’t be able to access items created by version 2, even if the only goal is to migrate those items to SKMME9E2Y8.groupB. To make this work you have to: Move SKMME9E2Y8.groupA to the end of the Keychain Sharing list, so new items get created in SKMME9E2Y8.groupB. Add a migration from SKMME9E2Y8.groupA to SKMME9E2Y8.groupB. Update the migration from SKMME9E2Y8.com.example.waffle-varnisher to target SKMME9E2Y8.groupB instead of SKMME9E2Y8.groupA. That last point is necessary because a user might install version 1, skip version 2, and instead update straight to version 3. This is just an example, but the message is clear: Any change to your keychain access group list requires careful planning and testing. You’ll also see problems like this if you change your App ID prefix, as described in App ID Prefix Change and Keychain Access. IMPORTANT When checking for this problem, don’t rely on your .entitlements file. There are many steps between it and your app’s actual entitlements. Rather, run codesign to dump the entitlements of your built app: % codesign -d --entitlements - /path/to/your.app Lost Keychain Items, Redux Another common cause of lost keychain items is confusion about query dictionaries, something discussed in detail in this post and SecItem: Fundamentals. If SecItemCopyMatching isn’t returning the expected item, add some test code to get all the items and their attributes. For example, to dump all the generic password items, run code like this: func dumpGenericPasswords() throws { let itemDicts = try secCall { SecItemCopyMatching([ kSecClass: kSecClassGenericPassword, kSecMatchLimit: kSecMatchLimitAll, kSecReturnAttributes: true, ] as NSDictionary, $0) } as! [[String: Any]] for itemDict in itemDicts { print("item:") let sortedKeysAndValues = itemDict.sorted(by: { $0.key < $1.key }) for (key, value) in sortedKeysAndValues { print(" \(key): \(value)") } } } Then compare each item’s attributes against the attributes you’re looking for to see why there was no match. Data Protection and Background Execution Keychain items are subject to data protection. Specifically, an item may or may not be accessible depending on whether specific key material is available. For an in-depth discussion of how this works, see Apple Platform Security. Note This section focuses on iOS but you’ll see similar effects on all Apple platforms. On macOS specifically, the contents of this section only apply to the data protection keychain. The keychain supports three data protection levels: kSecAttrAccessibleWhenUnlocked kSecAttrAccessibleAfterFirstUnlock kSecAttrAccessibleAlways Note There are additional data protection levels, all with the ThisDeviceOnly suffix. Understanding those is not necessary to understanding this pitfall. Each data protection level describes the lifetime of the key material needed to work with items protected in that way. Specifically: The key material needed to work with a kSecAttrAccessibleWhenUnlocked item comes and goes as the user locks and unlocks their device. The key material needed to work with a kSecAttrAccessibleAfterFirstUnlock item becomes available when the device is first unlocked and remains available until the device restarts. The default data protection level is kSecAttrAccessibleWhenUnlocked. If you add an item to the keychain and don’t specify a data protection level, this is what you get [1]. To specify a data protection level when you add an item to the keychain, apply the kSecAttrAccessible attribute. Alternatively, embed the access level within a SecAccessControl object and apply that using the kSecAttrAccessControl attribute. IMPORTANT It’s best practice to set these attributes when you add the item and then never update them. See Add-only Attributes, above, for more on that. If you perform an operation whose data protection is incompatible with the currently available key material, that operation fails with errSecInteractionNotAllowed [2]. There are four fundamental keychain operations, discussed in the SecItem: Fundamentals, and each interacts with data protection in a different way: Copy — If you attempt to access a keychain item whose key material is unavailable, SecItemCopyMatching fails with errSecInteractionNotAllowed. This is an obvious result; the whole point of data protection is to enforce this security policy. Add — If you attempt to add a keychain item whose key material is unavailable, SecItemAdd fails with errSecInteractionNotAllowed. This is less obvious. The reason why this fails is that the system needs the key material to protect (by encryption) the keychain item, and it can’t do that if if that key material isn’t available. Update — If you attempt to update a keychain item whose key material is unavailable, SecItemUpdate fails with errSecInteractionNotAllowed. This result is an obvious consequence of the previous result. Delete — Deleting a keychain item, using SecItemDelete, doesn’t require its key material, and thus a delete will succeed when the item is otherwise unavailable. That last point is a significant pitfall. I regularly see keychain code like this: Read an item holding a critical user credential. If that works, use that credential. If it fails, delete the item and start from a ‘factory reset’ state. The problem is that, if your code ends up running in the background unexpectedly, step 1 fails with errSecInteractionNotAllowed and you turn around and delete the user’s credential. Ouch! Note Even if you didn’t write this code, you might have inherited it from a keychain wrapper library. See Think Before Wrapping, below. There are two paths forward here: If you don’t expect this code to work in the background, check for the errSecInteractionNotAllowed error and non-destructively cancel the operation in that case. If you expect this code to be running in the background, switch to a different data protection level. WARNING For the second path, the most obvious fix is to move from kSecAttrAccessibleWhenUnlocked to kSecAttrAccessibleAfterFirstUnlock. However, this is not a panacea. It’s possible that your app might end up running before first unlock [3]. So, if you choose the second path, you must also make sure to follow the advice for the first path. You can determine whether the device is unlocked using the isProtectedDataAvailable property and its associated notifications. However, it’s best not to use this property as part of your core code, because such preflighting is fundamentally racy. Rather, perform the operation and handle the error gracefully. It might make sense to use isProtectedDataAvailable property as part of debugging, logging, and diagnostic code. [1] For file data protection there’s an entitlement (com.apple.developer.default-data-protection) that controls the default data protection level. There’s no such entitlement for the keychain. That’s actually a good thing! In my experience the file data protection entitlement is an ongoing source of grief. See this thread if you’re curious. [2] This might seem like an odd error but it’s actually pretty reasonable: The operation needs some key material that’s currently unavailable. Only a user action can provide that key material. But the data protection keychain will never prompt the user to unlock their device. Thus you get an error instead. [3] iOS generally avoids running third-party code before first unlock, but there are circumstances where that can happen. The obvious legitimate example of this is a VoIP app, where the user expects their phone to ring even if they haven’t unlocked it since the last restart. There are also other less legitimate examples of this, including historical bugs that caused apps to launch in the background before first unlock. Best Practices With the pitfalls out of the way, let’s talk about best practices. Less Painful Dictionaries I look at a lot of keychain code and it’s amazing how much of it is way more painful than it needs to be. The biggest offender here is the dictionaries. Here are two tips to minimise the pain. First, don’t use CFDictionary. It’s seriously ugly. While the SecItem API is defined in terms of CFDictionary, you don’t have to work with CFDictionary directly. Rather, use NSDictionary and take advantage of the toll-free bridge. For example, consider this CFDictionary code: CFTypeRef keys[4] = { kSecClass, kSecAttrService, kSecMatchLimit, kSecReturnAttributes, }; static const int kTen = 10; CFNumberRef ten = CFNumberCreate(NULL, kCFNumberIntType, &kTen); CFAutorelease(ten); CFTypeRef values[4] = { kSecClassGenericPassword, CFSTR("AYS"), ten, kCFBooleanTrue, }; CFDictionaryRef query = CFDictionaryCreate( NULL, keys, values, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks ); Note This might seem rather extreme but I’ve literally seen code like this, and worse, while helping developers. Contrast this to the equivalent NSDictionary code: NSDictionary * query = @{ (__bridge NSString *) kSecClass: (__bridge NSString *) kSecClassGenericPassword, (__bridge NSString *) kSecAttrService: @"AYS", (__bridge NSString *) kSecMatchLimit: @10, (__bridge NSString *) kSecReturnAttributes: @YES, }; Wow, that’s so much better. Second, if you’re working in Swift, take advantage of its awesome ability to create NSDictionary values from Swift dictionary literals. Here’s the equivalent code in Swift: let query = [ kSecClass: kSecClassGenericPassword, kSecAttrService: "AYS", kSecMatchLimit: 10, kSecReturnAttributes: true, ] as NSDictionary Nice! Avoid Reusing Dictionaries I regularly see folks reuse dictionaries for different SecItem calls. For example, they might have code like this: var copyResult: CFTypeRef? = nil let dict = [ kSecClass: kSecClassGenericPassword, kSecAttrService: "AYS", kSecAttrAccount: "mrgumby", kSecReturnData: true, ] as NSMutableDictionary var err = SecItemCopyMatching(dict, &copyResult) if err == errSecItemNotFound { dict[kSecValueData] = Data("opendoor".utf8) err = SecItemAdd(dict, nil) } This specific example will work, but it’s easy to spot the logic error. kSecReturnData is a return type property and it makes no sense to pass it to a SecItemAdd call whose second parameter is nil. I’m not sure why folks do this. I think it’s because they think that constructing dictionaries is expensive. Regardless, this pattern can lead to all sorts of weird problems. For example, it’s the leading cause of the issue described in the Queries and the Uniqueness Constraints section, above. My advice is that you use a new dictionary for each call. That prevents state from one call accidentally leaking into a subsequent call. For example, I’d rewrite the above as: var copyResult: CFTypeRef? = nil let query = [ kSecClass: kSecClassGenericPassword, kSecAttrService: "AYS", kSecAttrAccount: "mrgumby", kSecReturnData: true, ] as NSMutableDictionary var err = SecItemCopyMatching(query, &copyResult) if err == errSecItemNotFound { let add = [ kSecClass: kSecClassGenericPassword, kSecAttrService: "AYS", kSecAttrAccount: "mrgumby", kSecValueData: Data("opendoor".utf8), ] as NSMutableDictionary err = SecItemAdd(add, nil) } It’s a bit longer, but it’s much easier to track the flow. And if you want to eliminate the repetition, use a helper function: func makeDict() -> NSMutableDictionary { [ kSecClass: kSecClassGenericPassword, kSecAttrService: "AYS", kSecAttrAccount: "mrgumby", ] as NSMutableDictionary } var copyResult: CFTypeRef? = nil let query = makeDict() query[kSecReturnData] = true var err = SecItemCopyMatching(query, &copyResult) if err == errSecItemNotFound { let add = makeDict() query[kSecValueData] = Data("opendoor".utf8) err = SecItemAdd(add, nil) } Think Before Wrapping A lot of folks look at the SecItem API and immediately reach for a wrapper library. A keychain wrapper library might seem like a good idea but there are some serious downsides: It adds another dependency to your project. Different subsystems within your project may use different wrappers. The wrapper can obscure the underlying API. Indeed, its entire raison d’être is to obscure the underlying API. This is problematic if things go wrong. I regularly talk to folks with hard-to-debug keychain problems and the conversation goes something like this: Quinn: What attributes do you use in the query dictionary? J R Developer: What’s a query dictionary? Quinn: OK, so what error are you getting back? J R Developer: It throws WrapperKeychainFailedError. That’s not helpful )-: If you do use a wrapper, make sure it has diagnostic support that includes the values passed to and from the SecItem API. Also make sure that, when it fails, it returns an error that includes the underlying keychain error code. These benefits will be particularly useful if you encounter a keychain problem that only shows up in the field. Wrappers must choose whether to be general or specific. A general wrapper may be harder to understand than the equivalent SecItem calls, and it’ll certainly contain a lot of complex code. On the other hand, a specific wrapper may have a model of the keychain that doesn’t align with your requirements. I recommend that you think twice before using a keychain wrapper. Personally I find the SecItem API relatively easy to call, assuming that: I use the techniques shown in Less Painful Dictionaries, above, to avoid having to deal with CFDictionary. I use my secCall(…) helpers to simplify error handling. For the code, see Calling Security Framework from Swift. If you’re not prepared to take the SecItem API neat, consider writing your own wrapper, one that’s tightly focused on the requirements of your project. For example, in my VPN apps I use the wrapper from this post, which does exactly what I need in about 100 lines of code. Prefer to Update Of the four SecItem functions, SecItemUpdate is the most neglected. Rather than calling SecItemUpdate I regularly see folks delete and then re-add the item. This is a shame because SecItemUpdate has some important benefits: It preserves persistent references. If you delete and then re-add the item, you get a new item with a new persistent reference. It’s well aligned with the fundamental database nature of the keychain. It forces you to think about which attributes uniquely identify your item and which items can be updated without changing the item’s identity. For a cool example of its power, check out Transfer Items Between Keychain Access Groups, below. Understand These Key Attributes Key items have a number of attributes that are similarly named, and it’s important to keep them straight. I created a cheat sheet for this, namely, SecItem attributes for keys. You wouldn’t believe how often I consult this! Starting from Scratch Sometimes it’s useful to be able to start from scratch. Imagine, for example, you’ve been rapidly iterating on some keychain code and you’re not sure whether your current code is compatible with items created by your earlier code. To simplify things, use SecItemDelete to delete all the existing items: _ = SecItemDelete([ kSecClass: kSecClassGenericPassword, kSecUseDataProtectionKeychain: true, ] as NSDictionary) WARNING This code is obviously dangerous. Read the discussion below to learn more. This deletes all generic password items that your app has access to. To delete items in a different keychain item class, change the value for the kSecClass attribute. This code uses kSecUseDataProtectionKeychain. On iOS there is only one keychain, so this is a no-op. On macOS it limits the effect to the data protection keychain. Without it, the call will delete items in file-based keychains as well. This is very dangerous because those items might belong to other apps, or the system. If you want to use this technique in a Mac product that uses the file-based keychain, don’t use this code. Rather, write code that carefully targets your app’s keychain items. Alternatively, avoid this code and instead delete the items using Keychain Access or the security tool. For more about keychains on the Mac, see TN3137 On Mac keychain APIs and implementations. I often invoke this code from my app’s debug UI. For example, in a Mac app I might have a Debug menu with a Reset Keychain menu item. I typically compile that code out of the release build. However, you might choose to leave it in your final product. For example, you might have a ‘secret’ way to enable the debug UI [1] so that you can use it to help users with problems. In that case, make sure your debug UI informs the user of the potential consequences of this action. If you’re working on a big app, it might have different subsystems that user the keychain in different ways. A debug action like this might make sense for your subsystem but not for all the others. In that case, coordinate this work with the owners of any other subsystems that use the keychain. [1] If your app ships on the App Store, make sure that App Review knows about your debug UI. Investigating Complex Attributes Some attributes have values where the format is not obvious. For example, the kSecAttrIssuer attributed is documented as: The corresponding value is of type CFData and contains the X.500 issuer name of a certificate. What exactly does that mean? If I want to search the keychain for all certificates issued by a specific certificate authority, what value should I supply? One way to figure this out is to add a certificate to the keychain, read the attributes back, and then dump the kSecAttrIssuer value. For example: let cert: SecCertificate = … let attrs = try secCall { SecItemAdd([ kSecValueRef: cert, kSecReturnAttributes: true, ] as NSDictionary, $0) } as! [String: Any] let issuer = attrs[kSecAttrIssuer as String] as! NSData print((issuer as NSData).debugDescription) // prints: <3110300e 06035504 030c074d 6f757365 4341310b 30090603 55040613 024742> Those bytes represent the contents of a X.509 Name ASN.1 structure with DER encoding. This is without the outer SEQUENCE element, so if you dump it as ASN.1 you’ll get a nice dump of the first SET and then a warning about extra stuff at the end of the file: % xxd issuer.asn1 00000000: 3110 300e 0603 5504 030c 074d 6f75 7365 1.0...U....Mouse 00000010: 4341 310b 3009 0603 5504 0613 0247 42 CA1.0...U....GB % dumpasn1 -p issuer.asn1 SET { SEQUENCE { OBJECT IDENTIFIER commonName (2 5 4 3) UTF8String 'MouseCA' } } Warning: Further data follows ASN.1 data at position 18. Note For details on the Name structure, see section 4.1.2.4 of RFC 5280. Amusingly, if you run the same test against the file-based keychain you’ll… crash. OK, that’s not amusing. It turns out that the code above doesn’t work when targeting the file-based keychain because SecItemAdd doesn’t return a dictionary but rather an array of dictionaries (r. 21111543). Once you get past that, however, you’ll see it print: <301f3110 300e0603 5504030c 074d6f75 73654341 310b3009 06035504 06130247 42> Which is different! Dumping it as ASN.1 shows that it’s the full Name structure, including the outer SEQUENCE element: % xxd issuer-file-based.asn1 00000000: 301f 3110 300e 0603 5504 030c 074d 6f75 0.1.0...U....Mou 00000010: 7365 4341 310b 3009 0603 5504 0613 0247 seCA1.0...U....G 00000020: 42 B % dumpasn1 -p issuer-file-based.asn1 SEQUENCE { SET { SEQUENCE { OBJECT IDENTIFIER commonName (2 5 4 3) UTF8String 'MouseCA' } } SET { SEQUENCE { OBJECT IDENTIFIER countryName (2 5 4 6) PrintableString 'GB' } } } This difference in behaviour between the data protection and file-based keychains is a known bug (r. 26391756) but in this case it’s handy because the file-based keychain behaviour makes it easier to understand the data protection keychain behaviour. Import, Then Add It’s possible to import data directly into the keychain. For example, you might use this code to add a certificate: let certData: Data = … try secCall { SecItemAdd([ kSecClass: kSecClassCertificate, kSecValueData: certData, ] as NSDictionary, nil) } However, it’s better to import the data and then add the resulting credential reference. For example: let certData: Data = … let cert = try secCall { SecCertificateCreateWithData(nil, certData as NSData) } try secCall { SecItemAdd([ kSecValueRef: cert, ] as NSDictionary, nil) } There are two advantages to this: If you get an error, you know whether the problem was with the import step or the add step. It ensures that the resulting keychain item has the correct attributes. This is especially important for keys. These can be packaged in a wide range of formats, so it’s vital to know whether you’re interpreting the key data correctly. I see a lot of code that adds key data directly to the keychain. That’s understandable because, back in the day, this was the only way to import a key on iOS. Fortunately, that’s not been the case since the introduction of SecKeyCreateWithData in iOS 10 and aligned releases. For more information about importing keys, see Importing Cryptographic Keys. App Groups on the Mac Sharing access to keychain items among a collection of apps explains that three entitlements determine your keychain access: keychain-access-groups application-identifier (com.apple.application-identifier on macOS) com.apple.security.application-groups In the discussion of the last item says: You can use app group names as keychain access group names, without adding them to the Keychain access groups entitlement. That’s true, but it’s also potentially misleading. This affordance works all the time on iOS and its child platforms. But on the Mac it only works if your entitlements are validated by a provisioning profile. For more on that topic, see App Groups: macOS vs iOS: Working Towards Harmony. Transfer Items Between Keychain Access Groups In some cases you might want to move a bunch of keychain items from one app group to another, for example, when preparing for an App ID prefix change. This is easier than you might first think. For example, to move all the generic password items for a particular service between oldGroup and newGroup, run this code: try secCall { SecItemUpdate([ kSecClass: kSecClassGenericPassword, kSecUseDataProtectionKeychain: true, kSecAttrAccessGroup: oldGroup, kSecAttrService: "MyService", ] as NSDictionary, [ kSecAttrAccessGroup: newGroup, ] as NSDictionary) } This snippet highlights both the power and the subtlety of the SecItem API. The first parameter to SecItemUpdate is a pure query dictionary. It selects all the generic password items for MyService that are in the old keychain access group. In contrast, the second parameter is an update dictionary, which in this case just changes a single attribute. See SecItem: Fundamentals for a deeper explanation of these concepts. This call is atomic from your perspective [1]. The call will either fail or all the selected items will move as one. IMPORTANT Bulk operations like this are risky. That’s not because the keychain item will do the wrong thing, but rather because you have to be very careful what you ask for. If, for example, your query dictionary matches more than you intended, you might end up moving items unexpectedly. Be careful when crafting this code, and test it thoroughly. [1] It may even be atomic in a wider sense, given that the keychain is currently implemented as an SQLite database. Command-Line Tools Access to the data protection keychain is mediated by various entitlements, as described in Sharing access to keychain items among a collection of apps. Those entitlements are restricted, that is, they must be authorised by a provisioning profile. This is fine for apps, app extensions, and system extensions, which are all bundled code; they exist within an app-like bundle structure. However, it’s problematic for command-line tools on the Mac, which are non-bundled executables. There’s no obvious way for such executables to include a provisioning profile (r. 125850707). For more about provisioning profiles, see TN3125 Inside Code Signing: Provisioning Profiles. For more about bundled code, see Creating distribution-signed code for macOS. If you’re creating a non-bundled executable for the Mac, first consider its execution context. If it runs as a launchd daemon, or outside of a user login context in some other way, it can’t use the data protection keychain. See TN3137 On Mac keychain APIs and implementations for more about that. If the executable is a command-line tool that’s typically run by the user, in Terminal or over SSH, it can use the data protection keychain. However: You have to embed the tool in an app-like wrapper. For more about that, see Signing a daemon with a restricted entitlement. If the tool is run via SSH, the user’s data protection keychain might be locked. To resolve this, the user must explicitly unlock their login keychain using the security tool. Note While the login keychain is a file-based keychain, unlocking it in this way also unlocks the data protection keychain. In-memory Plug-ins An in-memory plug-in is a native plug-in that’s loaded directly into the host process as a Mach-O bundle or shared library. For example, macOS screen savers are in-memory plug-ins. Note In-memory plug-ins are quite old school. Modern plug-ins are packaged as app extensions. If you’re created a Mac app that supports plug-ins, support app extension plug-ins by adopting ExtensionKit. From the keychain perspective, an in-memory plug-in is indistinguishable from the host app. This has both pros and cons: It can access all the keychain items that the host app has access to, in either the file-based or data protection keychains. It can’t access additional keychain items. For example, you can’t grant your in-memory plug-in access to a keychain access group that’s used by other apps that you create. I’ll leave it up to you to decide which of these is a pro and which is a con (-: Revision History 2026-07-02 Clarified the terminology in the Keys Aren’t Stored in the Secure Enclave section. 2026-05-21 Enhanced the code snippet in the Lost Keychain Items, Redux section. 2026-04-27 Added the Command-Line Tools and In-memory Plug-ins sections. 2026-04-15 Significantly expanded the example in the Lost Keychain Items section. 2026-04-14 Added the Starting from Scratch section. 2026-04-02 Added the Transfer Items Between Keychain Access Groups section. Updated the App Groups on the Mac section to account for recent changes to app groups on the Mac. Made other minor editorial changes. 2025-06-29 Added the Data Protection and Background Execution section. Made other minor editorial changes. 2025-02-03 Added another specific example to the Careful With that Shim, Mac Developer section. 2025-01-29 Added somes specific examples to the Careful With that Shim, Mac Developer section. 2025-01-23 Added the Import, Then Add section. 2024-08-29 Added a discussion of identity formation to the Digital Identities Aren’t Real section. 2024-04-11 Added the App Groups on the Mac section. 2023-10-25 Added the Lost Keychain Items and Lost Keychain Items, Redux sections. 2023-09-22 Made minor editorial changes. 2023-09-12 Fixed various bugs in the revision history. Added the Erroneous Attributes section. 2023-02-22 Fixed the link to the VPNKeychain post. Corrected the name of the Context Matters section. Added the Investigating Complex Attributes section. 2023-01-28 First posted.
0
0
5.3k
Jul ’26
Sign in with Apple for web - is a published App Store app required, or just an App ID?
I'm implementing Sign in with Apple for a web-only service (Services ID associated with a primary App ID, plus a private key for the client secret JWT). Before I finalize the setup I want to confirm one prerequisite, because two of Apple's own pages give different answers. "Configuring your environment for Sign in with Apple" states that to authenticate users with a web service you must have an existing app in the App Store that uses Sign in with Apple: https://developer.apple.com/documentation/signinwithapple/configuring-your-environment-for-sign-in-with-apple "Configure Sign in with Apple for the web" (Account Help) only says to associate your website with an existing primary App ID enabled for Sign in with Apple — i.e., just a registered identifier, with no mention of a published app: https://developer.apple.com/help/account/capabilities/configure-sign-in-with-apple-for-the-web/ My question: for a web-only service, is a published (or in-review) App Store app actually required, or is a registered App ID identifier with the Sign in with Apple capability enabled sufficient on its own? And if a published app isn't required, is the "existing app in the App Store" wording just meant to describe the App ID rather than a live listing? I've contacted Developer Support and was pointed back to the general docs, which are the source of the contradiction rather than the answer, so I'm hoping an engineer or someone who has shipped a web-only setup can confirm which prerequisite governs. Thanks in advance.
0
0
188
Jul ’26
https://app-site-association.cdn-apple.com/a/v1/* 404 Not Found
% curl -v https://app-site-association.cdn-apple.com/a/v1/zfcs.bankts.cn Host app-site-association.cdn-apple.com:443 was resolved. IPv6: (none) IPv4: 218.92.226.151, 119.101.148.193, 218.92.226.6, 115.152.217.3 Trying 218.92.226.151:443... Connected to app-site-association.cdn-apple.com (218.92.226.151) port 443 ALPN: curl offers h2,http/1.1 (304) (OUT), TLS handshake, Client hello (1): CAfile: /etc/ssl/cert.pem CApath: none (304) (IN), TLS handshake, Server hello (2): (304) (IN), TLS handshake, Unknown (8): (304) (IN), TLS handshake, Certificate (11): (304) (IN), TLS handshake, CERT verify (15): (304) (IN), TLS handshake, Finished (20): (304) (OUT), TLS handshake, Finished (20): SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384 / [blank] / UNDEF ALPN: server accepted http/1.1 Server certificate: subject: C=US; ST=California; O=Apple Inc.; CN=app-site-association.cdn-apple.com start date: Sep 25 13:58:08 2025 GMT expire date: Mar 31 17:44:25 2026 GMT subjectAltName: host "app-site-association.cdn-apple.com" matched cert's "app-site-association.cdn-apple.com" issuer: CN=Apple Public Server RSA CA 11 - G1; O=Apple Inc.; ST=California; C=US SSL certificate verify ok. using HTTP/1.x GET /a/v1/zfcs.bankts.cn HTTP/1.1 Host: app-site-association.cdn-apple.com User-Agent: curl/8.7.1 Accept: / Request completely sent off < HTTP/1.1 404 Not Found < Content-Type: text/plain; charset=utf-8 < Content-Length: 10 < Connection: keep-alive < Server: nginx < Date: Wed, 04 Feb 2026 02:26:00 GMT < Expires: Wed, 04 Feb 2026 02:26:10 GMT < Age: 24 < Apple-Failure-Details: {"cause":"context deadline exceeded (Client.Timeout exceeded while awaiting headers)"} < Apple-Failure-Reason: SWCERR00301 Timeout < Apple-From: https://zfcs.bankts.cn/.well-known/apple-app-site-association < Apple-Try-Direct: true < Vary: Accept-Encoding < Via: https/1.1 jptyo12-3p-pst-003.ts.apple.com (acdn/3.16363), http/1.1 jptyo12-3p-pac-043.ts.apple.com (acdn/3.16363), https/1.1 jptyo12-3p-pfe-002.ts.apple.com (acdn/3.16363) < X-Cache: MISS KS-CLOUD < CDNUUID: 736dc646-57fb-43c9-aa0d-eedad3a534f8-1154605242 < x-link-via: yancmp83:443;xmmp02:443;fzct321:443; < x-b2f-cs-cache: no-cache < X-Cache-Status: MISS from KS-CLOUD-FZ-CT-321-35 < X-Cache-Status: MISS from KS-CLOUD-XM-MP-02-16 < X-Cache-Status: MISS from KS-CLOUD-YANC-MP-83-15 < X-KSC-Request-ID: c4a640c815640ee93c263a357ee919d6 < CDN-Server: KSFTF < X-Cdn-Request-ID: c4a640c815640ee93c263a357ee919d6 < Not Found Connection #0 to host app-site-association.cdn-apple.com left intact
2
0
470
Jul ’26
DCAppAttestService.isSupported always returns false on macOS 27
I've been implementing App Attest on macOS 27 following the WWDC 2026 Session 201 announcement. DCAppAttestService.shared.isSupported always returns false on my M4 Mac running macOS 27.0 (26A5368g), even with the correct entitlement and a valid provisioning profile. What I have set up (correctly, as far as I can tell) com.apple.developer.devicecheck.app-attest-opt-in capability enabled in the Developer Portal (value CDhash) Entitlement present in both the binary and the embedded provisioning profile Developer ID signed, ProvisionsAllDevices: true The problem DCAppAttestService.shared.isSupported returns false from every process type I tested: An EndpointSecurity system extension A launchd daemon A sandboxed app running in user session generateKey() fails with com.apple.devicecheck.error code 1 (featureUnsupported). Root cause? (from devicecheckd logs) I see these logs devicecheckd: [com.apple.devicecheck:aai] FeatureFlagsManager.m:35 Mac feature flag enabled { enabled=1 }. devicecheckd: (AppAttestInternal) [com.apple.appattest:secl] SecurityController.swift:44 Failed to fetch value for entitlement. { entitlement=com.apple.devicecheck.daemon-client } devicecheckd: (AppAttestInternal) [com.apple.appattest:aahl] AppAttestHandler.swift:48 Client connection is ineligible. { clientUUID=nil } So the feature IS active in macOS 27 (Mac feature flag enabled=1), but devicecheckd immediately rejects any connecting process that doesn't hold the private entitlement com.apple.devicecheck.daemon-client. What is com.apple.devicecheck.daemon-client? Searching public entitlement databases shows this entitlement exists on iOSbut no macOS binary appears to hold it in any public database. It's not available to third-party developers via the Developer Portal. This check in SecurityController.swift:44 appears to be new in this beta. Questions Is com.apple.devicecheck.daemon-client the correct mechanism for third-party developers to use App Attest on macOS 27, or is this an internal gating mechanism that will be replaced/removed before GM? Is App Attest on macOS 27 fully available to third-party developers in this seed, or is it still restricted to Apple-internal testing? Is there a different entitlement or provisioning capability that third-party developers should request to allow DCAppAttestService.isSupported to return true?
1
0
420
Jun ’26
SecurityAgent taking focus for plugin in macOS 26.1
We have a custom SecurityAgentPlugin that is triggered by multiple authorizationdb entries. Some customers report that the SecurityAgent process takes window focus even though no UI or windows are displayed. Our plugin explicitly ignores the _securityAgent user and does not show any UI for that user. However, in macOS 26.1, it appears that the plugin still causes the SecurityAgent to take focus as soon as it is triggered. Is this a change in macOS 26.1 or a bug? Can we do anything to prevent "focus stealing"?
29
4
9.2k
Jun ’26
SecurityAgent stealing keyboard focus on macOS 26 Tahoe — confirmed chain via exec logs
Environment: MacBook Pro 14-inch Nov 2023 (Apple Silicon M3) macOS 26.5 (25F71) and 26.5.1 (25F80) MDM: Kandji/Iru enrolled BeyondTrust EPM-M 26.1.1495 Confirmed chain via epsext exec logs: The Kandji/Iru Parameter Agent (kandji-parameter-agent) calls /usr/sbin/systemsetup -getusingnetworktime every 15 minutes. Each invocation requests the system.preferences authorization right, waking authd → writeconfig.xpc → SecurityAgent. SecurityAgent then opens a SkyLight connection, calls SetFrontProcess, causes the active window to resign key appearance, and closes — all within 3–15 seconds. Cross-referenced against user-reported times: User reported focus steal at 10:13, 10:58, 11:13, 11:43 (CDT). SecurityAgent fired at exactly those times to the second in our WindowServer logs. Key finding: The systemsetup call itself is not the root issue — it's doing its job. The problem is that on macOS 26 Tahoe, this auth request causes SecurityAgent to grab keyboard focus as a side effect, which it should not do for a background/silent authorization check with no user interaction required. BeyondTrust KB0023327 documents the PMCAdapter event as a separate but related trigger. Both are symptoms of the same underlying SecurityAgent behaviour change in macOS 26.
1
0
393
Jun ’26
Unable to trigger .matchedExcludedCredentials for passkey
Hi everyone, was hoping I could get some help. Recently I've been trying to implement passkeys on my app and one of the use cases was that we don't allow users to create duplicated passkeys from the same device they are on. I passed the excludedCredentials into the registration and tried creating a passkey twice, on the 2nd time I am unable to create a passkey but instead of triggering the .matchedExcludedCredentials from the ASAuthorizationError like I hoped, I get a WKErrorDomain code 8 with the localized description At least one credential matches an entry of the excludeCredentials list in the platform attached authenticator. Been debugging but I still couldn't find the answer as to why the AS error is not triggered.
0
0
252
Jun ’26
How can an iOS browser app request the Web Browser Public Key Credential entitlement for Passkeys?
Hello Apple Developer Team, I am developing a general-purpose web browser for iOS using WKWebView. Current status: • The app is distributed through TestFlight (Internal Testing). • The app is registered in App Store Connect. • The browser supports standard web browsing with multiple tabs and arbitrary websites. My goal is to support WebAuthn passkey authentication for websites such as Google, GitHub, Microsoft, and other websites that support passkeys. While reviewing Apple's documentation, I found: "Passkey use in web browsers" "Authenticating people by using passkeys in browser apps" These documents mention that browser apps on iOS can support passkeys using: ASAuthorizationWebBrowserPublicKeyCredentialManager and the entitlement: com.apple.developer.web-browser.public-key-credential However, I could only find a request form titled: "Request the macOS Web Browser Public Key Credential Entitlement" which specifically asks: "Is your app a web browser on macOS?" My application is an iOS web browser, not a macOS browser. In addition, I previously requested the Default Web Browser entitlement, but my request was declined. My questions are: Is there a separate application process for requesting the Web Browser Public Key Credential entitlement for an iOS browser? Is approval of the Default Web Browser entitlement required before an iOS browser can use WebAuthn passkeys? Can an iOS browser request only the Web Browser Public Key Credential entitlement without becoming the system default browser? Is there any official documentation describing the entitlement request process for iOS browser apps? I know that third-party browsers such as Aloha Browser appear to support system Passkeys on iOS, so I would like to understand the correct implementation and entitlement process for an iOS browser. Thank you very much for your guidance.
Replies
1
Boosts
0
Views
200
Activity
2w
Identifying system OCSP/CRL traffic in Network Extension.
Hi! We're developing a security product that uses both EndpointSecurity.framework to intercept and authorize process and file events; and NetworkExtension.framework o intercept and inspect network connections. We're occasionally seeing crashes caused by Endpoint Security timeouts. After investigating several crash reports, we believe we've identified a deadlock involving code signature verification: our Network Extension intercepts connections initiated by nsurlsessiond to retrieve OCSP/CRL data (we believe these requests are made on behalf of trustd during code signature validation). To determine which policy should be applied to an intercepted connection, our Network Extension verifies the code signature of the originating process. However, that code signature verification itself blocks while waiting for the OCSP/CRL requests to complete. Since those requests are being intercepted by our Network Extension, we end up with a circular dependency: A process requires code signature verification. Signature verification triggers OCSP/CRL network requests. Those requests are intercepted by our Network Extension. Our Network Extension attempts to verify the initiator's code signature before allowing the connection. That verification waits for the same OCSP/CRL requests to complete. As a result, code signature verification becomes blocked process-wide, including verification performed while handling Endpoint Security events. Eventually, our Endpoint Security client exceeds the allowed response timeout and is terminated. We're considering bypassing interception for OCSP/CRL traffic to avoid this deadlock, but we'd like to understand whether this is the recommended or most robust approach. Questions Is there a reliable way to identify network connections that are fetching OCSP or CRL data for code signature validation? What is the relationship between trustd and nsurlsessiond for these requests? Is there a dedicated nsurlsessiond instance serving trustd, or are these requests performed by the shared system/session-wide nsurlsessiond? Would it be a reasonable and future-proof approach to identify these requests by checking NEAppProxyFlow.remoteHostname (for example, ocsp.apple.com and crl.apple.com) and bypassing interception for those connections? Is there another recommended approach to avoid this deadlock when combining Endpoint Security and Network Extension in this way? Any guidance or best practices would be greatly appreciated. Thank you!
Replies
1
Boosts
0
Views
217
Activity
2w
Token endpoint returns invalid_client for real authorization codes, but invalid_grant (client auth accepted) for identical credentials with a test code
Sign in with Apple (web flow) fails 100% reproducibly at the token exchange for our newly created identifiers. POST to https://appleid.apple.com/auth/token with a REAL authorization code → 400 {"error":"invalid_client"}. The exact same client_id + client_secret from the same production server with a dummy code → {"error":"invalid_grant"} — i.e. client authentication passes and only the code is rejected, as expected. 16/16 probe requests across both registered redirect_uris. The authorization phase succeeds (user completes the appleid.apple.com sheet; code arrives via form_post to the registered return URL) and the exchange happens within ~1 second, so code expiry is not the cause. client_secret is an ES256 JWT with correct iss/sub/aud, validity ~6 months, sent via client_secret_post with no Authorization header. I have reviewed TN3107 — none of the documented invalid_client causes fit, given the invalid_grant asymmetry above. Persisted 24+ hours and survived: recreating the Services ID under a new identifier with a freshly minted secret; toggling the Sign in with Apple capability off/on (as primary) on the App ID; re-saving the Services ID configuration (domains and both return URLs re-confirmed). Team ID: CGSLWL988T Primary App ID: app.bookagym (created 2026-07-10) Services ID: app.bookagym.signin (an earlier Services ID app.bookagym.web behaved identically) Key ID: C99J756C86 This looks like stuck or unpropagated server-side provisioning for these identifiers rather than a client-side error. Per the "Gathering required information for troubleshooting Sign in with Apple authorization and token requests" post, I have filed the full details (including the failing request with all parameter values and a long-lived client secret) in Feedback Assistant: FB23692739
Replies
0
Boosts
0
Views
235
Activity
3w
Unable to verify the app
Hey guys, I am having issue, unable to verify the app. An internet connection is required to verify trust of the developer .... I am connected to the internet, date and time is correct. I did some google, some one reported this link: https://ppq-ext.v.aaplimg.com/ ssl expired, which is causing this issue. Can anyone help? I am testing to test my app on Iphone 15 pro max.
Replies
0
Boosts
0
Views
224
Activity
3w
Native Sign in with Apple in Capacitor + NextAuth
I'm building an iOS app using Capacitor with a Next.js 14 backend. Authentication currently uses: NextAuth Prisma Sign in with Apple Google Sign-In Magic Link The web version works correctly. For the native iOS app, tapping Continue with Apple currently opens the NextAuth endpoint (/api/auth/signin/apple), which launches the web authentication flow. App Review rejected the app because the Sign in with Apple flow leaves the native experience and opens a web view/browser during sign in. My goal is to keep Sign in with Apple fully native while continuing to use the same Prisma user database and authentication system used by the web application. What is Apple's recommended architecture for this scenario? Should I: Perform native Sign in with Apple in the Capacitor app. Send the returned identity token to my backend. Verify the token server-side. Create my own authenticated session instead of using the NextAuth Apple provider. Or is there another approach Apple recommends for hybrid apps using Capacitor? Any guidance would be greatly appreciated.
Replies
0
Boosts
0
Views
195
Activity
3w
Validating Signature Of XPC Process
Quinn, you've often suggested that to validate the other side of an XPC connection, we should use the audit token. But that's not available from the XPC object, whereas the PID is. So everyone uses the PID. While looking for something completely unrelated, I found this in the SecCode.h file OSStatus SecCodeCreateWithXPCMessage(xpc_object_t message, SecCSFlags flags, SecCodeRef * __nonnull CF_RETURNS_RETAINED target); Would this be the preferred way to do this now? At least from 11.0 and up. Like I said, I was looking for something completely unrelated and found this and don't have the cycles right now to try it. But it looks promising from the description and I wanted to check in with you about it in case you can say yes or no before I get a chance to test it. Thanks
Replies
8
Boosts
0
Views
8.7k
Activity
3w
resetKeys() also resets sharedDeviceSigningKey unexpectedly
I am using ASAuthorizationProviderExtensionLoginManager.resetKeys() to generate new user-specific keys, specifically userDeviceSigningKey and userDeviceEncryptionKey. Based on the documentation, my understanding was that resetKeys() only resets keys associated with a particular user account: https://developer.apple.com/documentation/authenticationservices/asauthorizationproviderextensionloginmanager/resetkeys/ However, during testing, I observed that calling resetKeys() also resets sharedDeviceSigningKey. I had assumed that shared device keys would only be reset via resetDeviceKeys().
Replies
1
Boosts
1
Views
795
Activity
3w
macOS 27 beta: LocalAuthenticationView causes LAContext policy evaluation to fail with LAErrorDomain -1007
I’m seeing a regression in macOS 27 beta when using SwiftUI LocalAuthenticationView. When an LAContext is attached to LocalAuthenticationView, subsequent policy evaluation fails immediately with: Error Domain=com.apple.LocalAuthentication Code=-1007 NSDebugDescription="Caller is not Apple signed." NSLocalizedDescription="Authentication denied." The same policies work when evaluated on a plain LAContext that has not been attached to LocalAuthenticationView. Minimal shape of the failing path: @State private var context = LAContext() LocalAuthenticationView(context: context) { EmptyView() } context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Unlock") { success, error in print(success, error as Any) } This affects Touch ID unlock in our macOS app. We currently work around it by detecting LAErrorDomain / -1007, removing LocalAuthenticationView, and asking the user to manually start Touch ID with a fresh LAContext. Filed as Feedback: FB23262713 Could someone from the beta / LocalAuthentication team confirm whether this is an intended restriction for LocalAuthenticationView, or a macOS 27 beta regression?
Replies
3
Boosts
2
Views
533
Activity
3w
Sign in with Apple Failure, AKAuthenticationServerError=-24000
Apple Developer Support Request - Sign in with Apple Failure Date prepared: 2026-07-08 App name: LingJing / XiaoLing Bundle ID: com.jachymchen.xiaoling Apple Developer Team ID: 765B64SW9Z Platform: iOS Summary Sign in with Apple fails before the app receives a usable Apple identity token. The user-facing Apple system sheet ends with the Chinese alert "未完成注册" ("Sign Up Not Completed"). The app callback does not receive an identityToken, so our backend auth provider is not reached successfully. We reproduced the same failure in a minimal native Swift app that only uses AuthenticationServices and the same Bundle ID / provisioning profile, without Supabase or any app-specific backend code. This suggests the failure is likely in the Apple Sign in with Apple authorization flow, account/device state, or App ID/provisioning configuration recognized by Apple's auth services, rather than in our app's backend implementation. Environment Mac: macOS 26.5 Apple Silicon MacBook Pro Xcode 26.5 iPhone: Real device connected over USB Device name: 硬糖的iPhone iOS 26.5.1 Device UDID: 00008150-000344540C99401C App: Bundle ID: com.jachymchen.xiaoling App version currently used for testing: 0.1.0 App Store Connect app ID observed during TestFlight work: 6784075688 TestFlight build upload previously succeeded for version 0.1.0 build 2 Provisioning / Entitlements Checked Development provisioning profile: Profile name: XiaoLing Dev application-identifier: 765B64SW9Z.com.jachymchen.xiaoling com.apple.developer.applesignin: [Default] com.apple.developer.team-identifier: 765B64SW9Z get-task-allow: true keychain-access-groups includes: 765B64SW9Z.* com.apple.token Profile includes the test device UDID: 00008150-000344540C99401C Expiration: 2027-06-27 Distribution / TestFlight provisioning profile: Profile name: XiaoLing AppStore application-identifier: 765B64SW9Z.com.jachymchen.xiaoling com.apple.developer.applesignin: [Default] beta-reports-active: true get-task-allow: false Signed app entitlements were checked from the executable and matched the expected Bundle ID / Team ID / Sign in with Apple entitlement. Important note: Running codesign -d --entitlements :- XiaoLing.app against the .app bundle may print an "invalid entitlements blob" style result. Running codesign against the actual executable inside the .app is the useful check, and that showed the expected entitlements. Reproduction Steps Install and launch the iOS app with Bundle ID com.jachymchen.xiaoling on the real iPhone. Tap "Sign in with Apple". Complete the Apple ID system prompt. The Apple system flow fails and shows "未完成注册". The app does not receive a valid ASAuthorizationAppleIDCredential identity token. Observed Result The Apple authorization flow fails before returning a usable credential to the app. The system UI shows "未完成注册" ("Sign Up Not Completed"). Expected Result AuthenticationServices should return an ASAuthorizationAppleIDCredential with an identityToken so the app can continue its own backend sign-in. Key Device Logs Observed During real-device attempts, the following log lines appeared around the failure: AuthKit continuation-key-creation token is missing AppleIDAuthSupport: setError: 2:M2 missing (bad password) SRP authentication with server failed AUTH_ALERT_SIGN_UP_NOT_COMPLETED -> 未完成注册 AKRemoteViewController did complete with authorization (null) AKAuthenticationServerError Code=-24000 The private framework error code by itself is not enough to identify the root cause, but the flow consistently fails inside Apple's AuthKit / Apple ID authorization path before our app receives an identity token. Minimal Native Demo Test To exclude app-specific implementation and backend issues, we created a minimal native SwiftUI app using only: AuthenticationServices SignInWithAppleButton ASAuthorizationAppleIDProvider The same Bundle ID: com.jachymchen.xiaoling The same Team ID / provisioning entitlement setup No Supabase No custom backend No React Native No third-party auth library The minimal native demo produced the same user-facing failure: "未完成注册". This strongly suggests the issue is not caused by Supabase, nonce hashing, OAuth handling, React Native, or our app UI. The failure happens before any backend auth exchange can occur. Additional Context The tester tried another Apple ID and still reproduced the failure. The tester stated the Apple ID itself is otherwise usable. The app's Bundle ID is intended to be exactly: com.jachymchen.xiaoling We specifically checked for provisioning profile / Bundle ID mismatch and did not find a mismatch in the installed build. Questions for Apple Developer Support Can Apple check whether App ID 765B64SW9Z.com.jachymchen.xiaoling has any server-side Sign in with Apple configuration issue? Can Apple explain what conditions produce: AUTH_ALERT_SIGN_UP_NOT_COMPLETED AKAuthenticationServerError Code=-24000 "M2 missing (bad password)" "AuthKit continuation-key-creation token is missing" in a native AuthenticationServices Sign in with Apple flow? Is there any known issue on iOS 26.5.1 or with development-signed apps where Sign in with Apple fails before returning an ASAuthorizationAppleIDCredential? Are there account-level or device-level requirements beyond normal Apple ID login that can cause Sign in with Apple to show "Sign Up Not Completed"? Can Apple verify whether this Bundle ID / Team ID is correctly enabled for Sign in with Apple on Apple's backend? Minimal Code Shape Used for Native Demo The native demo used a standard SignInWithAppleButton: SignInWithAppleButton(.signIn) { request in request.requestedScopes = [.fullName, .email] } onCompletion: { result in switch result { case .success(let authorization): // Check authorization.credential as ASAuthorizationAppleIDCredential // and read identityToken. case .failure(let error): // Log NSError domain, code, localizedDescription, and userInfo. } } Support Request Please help determine why Apple's Sign in with Apple authorization flow fails for Bundle ID com.jachymchen.xiaoling before returning a credential, despite the app having the Sign in with Apple entitlement and matching provisioning profile / Bundle ID.
Replies
0
Boosts
1
Views
177
Activity
3w
way to attest that a Secure Enclave key is hardware-bound on macOS
We generate Secure Enclave keys via SecKeyCreateRandomKey with kSecAttrTokenIDSecureEnclave on macOS. We need to prove to a remote server that the key is genuinely hardware-bound, not a software key claiming to be one. Is there any API on macOS for an app to obtain an Apple-signed certificate or attestation statement for such a Secure Enclave key, similar to how ASAuthorizationProviderExtensionLoginManager.attestKey() works within Platform SSO but available to general apps? Or other possible workaround for this? Thank you!
Replies
5
Boosts
0
Views
1.2k
Activity
3w
How to share a keychain item between a mac os app and cli app
Hi, I would like to share a keychain item common for mac os app (sandboxed) and swift cli app (non sandboxed). Is there a recommended way for doing this? Thanks
Replies
1
Boosts
0
Views
310
Activity
4w
Sign In with Apple: AuthorizationError 1000, empty userInfo, delegate never receives authorization
Subject: Sign In with Apple consistently fails with AuthorizationError 1000 (empty userInfo) immediately after successful biometric authentication — native delegate logging confirms failure occurs before authorization is returned to app App ID: com.andresalecina.vant
Team ID: C6GK7Z5GY2
Services ID: com.andresalecina.vant.signin Summary:
Sign In with Apple fails 100% of the time in our native iOS app (Capacitor 7 wrapper) with error code 1000 and completely empty userInfo. The native authentication sheet displays correctly and the user completes Face ID successfully, but authentication fails immediately afterward. Critical new evidence (delegate-level logging):
We instrumented both ASAuthorizationControllerDelegate callback methods directly with NSLog to determine exactly where the failure occurs: authorizationController(controller:didCompleteWithAuthorization:) — never fires. Confirmed across multiple fresh test attempts. authorizationController(controller:didCompleteWithError:) — fires every time, receiving: Error Domain=com.apple.AuthenticationServices.AuthorizationError Code=1000 "(null)" with completely empty userInfo. This confirms the failure originates inside AuthenticationServices itself, before any authorization object is ever returned to our app. Our app code is not receiving a token, credential, or authorization of any kind to process — there is nothing for our code to mishandle. Everything already verified clean on our end: Sign In with Apple capability present in Xcode target and confirmed in Apple Developer Portal (Primary App ID, not grouped) Entitlement com.apple.developer.applesignin confirmed present in both the development-signed binary and the App Store distribution provisioning profile Team ID in the compiled binary matches the entitlement (verified via codesign -dv and codesign -d --entitlements) Provisioning profile freshly regenerated after capability was added Apple Developer Program License Agreement and Developer Agreement both fully accepted, no pending items Reproduces identically on two separate physical devices (one iOS 27 beta, one non-beta), on both WiFi and cellular Reproduces with a completely clean Apple ID test account (no prior Family Sharing/managed account involvement, confirmed adult standard account) Device clock, timezone, and Screen Time/Content & Privacy Restrictions confirmed not a factor Native plugin code reviewed line-by-line: uses stock ASAuthorizationAppleIDProvider().createRequest(), standard .fullName/.email scopes only, robust presentation anchor resolution (no detached/fake UIWindow) Timeline:
The Sign In with Apple capability was added to this App ID within the past 24–48 hours (it was missing entirely on our initial rejected App Store submission). Error 1000 has persisted consistently across many rebuilds since the capability was added, which raises the possibility of a propagation delay on Apple's authentication backend, but this has now exceeded typical propagation windows for other capabilities we've observed (Push Notifications, Associated Domains). What we're asking:
Can you verify, on Apple's side, whether App ID com.andresalecina.vant under Team C6GK7Z5GY2 is fully provisioned and active on Apple's Sign In with Apple authentication backend? All client-side, code-side, and account-side factors we can verify are confirmed correct; the delegate-level logging above confirms the failure is occurring before any authorization is returned to the client, which points to a state on Apple's servers that isn't visible to us through the standard Developer Portal UI. Happy to provide device sysdiagnose logs, the full Xcode archive, or a minimal reproduction project (bare SwiftUI, no Capacitor/Supabase) if useful.
Replies
1
Boosts
0
Views
417
Activity
Jul ’26
SecItem: Pitfalls and Best Practices
I regularly help developers with keychain problems, both here on DevForums and in various DTS cases. Over the years I’ve learnt a lot about the API, including many pitfalls and best practices. This post is my attempt to collect that experience in one place. 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: Pitfalls and Best Practices It’s just four functions, how hard can it be? 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 some of the keychain’s pitfalls and then goes on to explain various best practices. Before reading this, make sure you understand the fundamentals by reading its companion post, SecItem: Fundamentals. Pitfalls Lets start with some common pitfalls. Queries and Uniqueness Constraints The relationship between query dictionaries and uniqueness constraints is a major source of problems with the keychain API. Consider code like this: var copyResult: CFTypeRef? = nil let query = [ kSecClass: kSecClassGenericPassword, kSecAttrService: "AYS", kSecAttrAccount: "mrgumby", kSecAttrGeneric: Data("SecItemHints".utf8), ] as NSMutableDictionary let err = SecItemCopyMatching(query, &copyResult) if err == errSecItemNotFound { query[kSecValueData] = Data("opendoor".utf8) let err2 = SecItemAdd(query, nil) if err2 == errSecDuplicateItem { fatalError("… can you get here? …") } } Can you get to the fatal error? At first glance this might not seem possible because you’ve run your query and it’s returned errSecItemNotFound. However, the fatal error is possible because the query contains an attribute, kSecAttrGeneric, that does not contribute to the uniqueness. If the keychain contains a generic password whose service (kSecAttrService) and account (kSecAttrAccount) attributes match those supplied but whose generic (kSecAttrGeneric) attribute does not, the SecItemCopyMatching calls will return errSecItemNotFound. However, for a generic password item, of the attributes shown here, only the service and account attributes are included in the uniqueness constraint. If you try to add an item where those attributes match an existing item, the add will fail with errSecDuplicateItem even though the value of the generic attribute is different. The take-home point is that that you should study the attributes that contribute to uniqueness and use them in a way that’s aligned with your view of uniqueness. See the Uniqueness section of SecItem: Fundamentals for a link to the relevant documentation. Erroneous Attributes Each keychain item class supports its own specific set of attributes. For information about the attributes supported by a given class, see SecItem: Fundamentals. I regularly see folks use attributes that aren’t supported by the class they’re working with. For example, the kSecAttrApplicationTag attribute is only supported for key items (kSecClassKey). Using it with a certificate item (kSecClassCertificate) will cause, at best, a runtime error and, at worst, mysterious bugs. This is an easy mistake to make because: The ‘parameter block’ nature of the SecItem API means that the compiler won’t complain if you use an erroneous attribute. On macOS, the shim that connects to the file-based keychain ignores unsupported attributes. Imagine you want to store a certificate for a particular user. You might write code like this: let err = SecItemAdd([ kSecClass: kSecClassCertificate, kSecAttrApplicationTag: Data(name.utf8), kSecValueRef: cert, ] as NSDictionary, nil) The goal is to store the user’s name in the kSecAttrApplicationTag attribute so that you can get back their certificate with code like this: let err = SecItemCopyMatching([ kSecClass: kSecClassCertificate, kSecAttrApplicationTag: Data(name.utf8), kSecReturnRef: true, ] as NSDictionary, &copyResult) On iOS, and with the data protection keychain on macOS, both calls will fail with errSecNoSuchAttr. That makes sense, because the kSecAttrApplicationTag attribute is not supported for certificate items. Unfortunately, the macOS shim that connects the SecItem API to the file-based keychain ignores extraneous attributes. This results in some very bad behaviour: SecItemAdd works, ignoring kSecAttrApplicationTag. SecItemCopyMatching ignores kSecAttrApplicationTag, returning the first certificate that it finds. If you only test with a single user, everything seems to work. But, later on, when you try your code with multiple users, you might get back the wrong result depending on the which certificate the SecItemCopyMatching call happens to discover first. Ouch! Context Matters Some properties change behaviour based on the context. The value type properties are the biggest offender here, as discussed in the Value Type Subtleties section of SecItem: Fundamentals. However, there are others. The one that’s bitten me is kSecMatchLimit: In a query and return dictionary its default value is kSecMatchLimitOne. If you don’t supply a value for kSecMatchLimit, SecItemCopyMatching returns at most one item that matches your query. In a pure query dictionary its default value is kSecMatchLimitAll. For example, if you don’t supply a value for kSecMatchLimit, SecItemDelete will delete all items that match your query. This is a lesson that, once learnt, is never forgotten! Note Although this only applies to the data protection keychain. If you’re on macOS and targeting the file-based keychain, kSecMatchLimit always defaults to kSecMatchLimitOne. This is clearly a bug, but we can’t fix it due to compatibility concerns (r. 105800863). Fun times! Digital Identities Aren’t Real A digital identity is the combination of a certificate and the private key that matches the public key within that certificate. The SecItem API has a digital identity keychain item class, namely kSecClassIdentity. However, the keychain does not store digital identities. When you add a digital identity to the keychain, the system stores its components, the certificate and the private key, separately, using kSecClassCertificate and kSecClassKey respectively. This has a number of non-obvious effects: Adding a certificate can ‘add’ a digital identity. If the new certificate happens to match a private key that’s already in the keychain, the keychain treats that pair as a digital identity. Likewise when you add a private key. Similarly, removing a certificate or private key can ‘remove’ a digital identity. Adding a digital identity will either add a private key, or a certificate, or both, depending on what’s already in the keychain. Removing a digital identity removes its certificate. It might also remove the private key, depending on whether that private key is used by a different digital identity. The system forms a digital identity by matching the kSecAttrApplicationLabel (klbl) attribute of the private key with the kSecAttrPublicKeyHash (pkhh) attribute of the certificate. If you add both items to the keychain and the system doesn’t form an identity, check the value of these attributes. For more information the key attributes, see SecItem attributes for keys. Keys Aren’t Stored in the Secure Enclave Apple platforms let you protect a key with the Secure Enclave (SE). The key is then hardware bound. It can only be used by that specific SE [1]. Earlier versions of the Protecting keys with the Secure Enclave article implied that SE-protected keys were stored in the SE itself. This is not true, and it’s caused a lot of confusion. For example, I once asked the keychain team “How much space does the SE have available to store keys?”, a question that’s complete nonsense once you understand how this works. In reality, SE-protected keys are stored in the standard keychain database alongside all your other keychain items. The difference is that the key is constructed in such a way that only the SE can use it. So, the key is protected by the SE, not stored in the SE. A while back we updated the docs to clarify this point but the confusion persists. [1] Technically it’s that specific iteration of that specific SE. If you erase the device then the key material needed to use the key is erased and so the key becomes permanently useless. Or at least that’s my understanding of how things work (-: For details like this I defer to Apple Platform Security. Careful With that Shim, Mac Developer As explained in TN3137 On Mac keychain APIs and implementations, macOS has a shim that connects the SecItem API to either the data protection keychain or the file-based keychain depending on the nature of the request. That shim has limitations. Some of those are architectural but others are simply bugs in the shim. For some great examples, see the Investigating Complex Attributes section below. The best way to avoid problems like this is to target the data protection keychain. If you can’t do that, try to avoid exploring the outer reaches of the SecItem API. If you encounter a case that doesn’t make sense, try that same case with the data protection keychain. If it works there but fails with the file-based keychain, please do file a bug against the shim. It’ll be in good company. Here’s some known issues with the shim: It ignores unsupported attributes. See Erroneous Attributes, above, for more background on that. The shim can fan out to both the data protection and the file-based keychain. In that case it has to make a policy decision about how to handle errors. This results in some unexpected behaviour (r. 143405965). For example, if you call SecItemCopyMatching while the keychain is locked, the data protection keychain will fail with errSecInteractionNotAllowed (-25308). OTOH, it’s possible to query for the presence of items in the file-based keychain even when it’s locked. If you do that and there’s no matching item, the file-based keychain fails with errSecItemNotFound (-25300). When the shim gets these conflicting errors, it chooses to return the latter. Whether this is right or wrong depends on your perspective, but it’s certainly confusing, especially if you’re coming at this from the iOS side. If you call SecItemDelete without specifying a match limit (kSecMatchLimit), the data protection keychain deletes all matching items, whereas the file-based keychain just deletes a single match (r. 105800863). While these shim issue have all have bug numbers, there’s no guarantee that any of them will be fixed. Fixing bugs like this is tricky because of binary compatibility concerns. Add-only Attributes Some attributes can only be set when you add an item. These attributes are usually associated with the scope of the item. For example, to protect an item with the Secure Enclave, supply the kSecAttrAccessControl attribute to the SecItemAdd call. Once you do that, however, you can’t change the attribute. Calling SecItemUpdate with a new kSecAttrAccessControl won’t work. Lost Keychain Items A common complaint from developers is that a seemingly minor update to their app has caused it to lose all of its keychain items. Usually this is caused by one of two problems: Entitlement changes Query dictionary confusion Access to keychain items is mediated by various entitlements, as described in Sharing access to keychain items among a collection of apps. If the two versions of your app have different entitlements, one version may not be able to ‘see’ items created by the other. Let’s walk through an example of this. Imagine you have an app with an App ID of SKMME9E2Y8.com.example.waffle-varnisher. Version 1 of your app does nothing fancy with the keychain. It uses neither keychain access groups nor app groups. Thus its keychain access group list consists of just the App ID, that is, [ SKMME9E2Y8.com.example.waffle-varnisher ]. When that version of your app creates a keychain item, the kSecAttrAccessGroup value will default to the only value available, SKMME9E2Y8.com.example.waffle-varnisher. In version 2 of your app you want to use keychain access groups, so you add the Keychain Sharing capability to your project and populate it with two values, SKMME9E2Y8.groupA and SKMME9E2Y8.groupB. If you take no other action, your app’s keychain access group list will be [ SKMME9E2Y8.groupA, SKMME9E2Y8.groupB, SKMME9E2Y8.com.example.waffle-varnisher ]. This changes the default value for new items to SKMME9E2Y8.groupA. This is an obvious pitfall. Version 1 of your app created new keychain items in SKMME9E2Y8.com.example.waffle-varnisher while version 2 creates them in SKMME9E2Y8.groupA. You now have different items in different groups, depending on which version the user first launched, and that’s a recipe for chaos. There are two common ways to avoid problems here: Migrate items from SKMME9E2Y8.com.example.waffle-varnisher to SKMME9E2Y8.groupA. See Transfer Items Between Keychain Access Groups, below. Add your App ID to the front of the Keychain Sharing list. This results in a keychain access group list of [ SKMME9E2Y8.com.example.waffle-varnisher, SKMME9E2Y8.groupA, SKMME9E2Y8.groupB, SKMME9E2Y8.com.example.waffle-varnisher ], which means that the default keychain access group doesn’t change. (The second instance of SKMME9E2Y8.com.example.waffle-varnisher in this list is redundant but doesn’t cause any complications.) So far so good. Now let’s say you took the first option and shipped version 2 of your app with SKMME9E2Y8.groupA as the default keychain access group. You want to update the app again, to version 3, and you’ve decided that SKMME9E2Y8.groupA no longer makes sense and you want to remove it, relying on SKMME9E2Y8.groupB instead. Doing that isn’t safe. If version 3 of your app has no access to SKMME9E2Y8.groupA, it won’t be able to access items created by version 2, even if the only goal is to migrate those items to SKMME9E2Y8.groupB. To make this work you have to: Move SKMME9E2Y8.groupA to the end of the Keychain Sharing list, so new items get created in SKMME9E2Y8.groupB. Add a migration from SKMME9E2Y8.groupA to SKMME9E2Y8.groupB. Update the migration from SKMME9E2Y8.com.example.waffle-varnisher to target SKMME9E2Y8.groupB instead of SKMME9E2Y8.groupA. That last point is necessary because a user might install version 1, skip version 2, and instead update straight to version 3. This is just an example, but the message is clear: Any change to your keychain access group list requires careful planning and testing. You’ll also see problems like this if you change your App ID prefix, as described in App ID Prefix Change and Keychain Access. IMPORTANT When checking for this problem, don’t rely on your .entitlements file. There are many steps between it and your app’s actual entitlements. Rather, run codesign to dump the entitlements of your built app: % codesign -d --entitlements - /path/to/your.app Lost Keychain Items, Redux Another common cause of lost keychain items is confusion about query dictionaries, something discussed in detail in this post and SecItem: Fundamentals. If SecItemCopyMatching isn’t returning the expected item, add some test code to get all the items and their attributes. For example, to dump all the generic password items, run code like this: func dumpGenericPasswords() throws { let itemDicts = try secCall { SecItemCopyMatching([ kSecClass: kSecClassGenericPassword, kSecMatchLimit: kSecMatchLimitAll, kSecReturnAttributes: true, ] as NSDictionary, $0) } as! [[String: Any]] for itemDict in itemDicts { print("item:") let sortedKeysAndValues = itemDict.sorted(by: { $0.key < $1.key }) for (key, value) in sortedKeysAndValues { print(" \(key): \(value)") } } } Then compare each item’s attributes against the attributes you’re looking for to see why there was no match. Data Protection and Background Execution Keychain items are subject to data protection. Specifically, an item may or may not be accessible depending on whether specific key material is available. For an in-depth discussion of how this works, see Apple Platform Security. Note This section focuses on iOS but you’ll see similar effects on all Apple platforms. On macOS specifically, the contents of this section only apply to the data protection keychain. The keychain supports three data protection levels: kSecAttrAccessibleWhenUnlocked kSecAttrAccessibleAfterFirstUnlock kSecAttrAccessibleAlways Note There are additional data protection levels, all with the ThisDeviceOnly suffix. Understanding those is not necessary to understanding this pitfall. Each data protection level describes the lifetime of the key material needed to work with items protected in that way. Specifically: The key material needed to work with a kSecAttrAccessibleWhenUnlocked item comes and goes as the user locks and unlocks their device. The key material needed to work with a kSecAttrAccessibleAfterFirstUnlock item becomes available when the device is first unlocked and remains available until the device restarts. The default data protection level is kSecAttrAccessibleWhenUnlocked. If you add an item to the keychain and don’t specify a data protection level, this is what you get [1]. To specify a data protection level when you add an item to the keychain, apply the kSecAttrAccessible attribute. Alternatively, embed the access level within a SecAccessControl object and apply that using the kSecAttrAccessControl attribute. IMPORTANT It’s best practice to set these attributes when you add the item and then never update them. See Add-only Attributes, above, for more on that. If you perform an operation whose data protection is incompatible with the currently available key material, that operation fails with errSecInteractionNotAllowed [2]. There are four fundamental keychain operations, discussed in the SecItem: Fundamentals, and each interacts with data protection in a different way: Copy — If you attempt to access a keychain item whose key material is unavailable, SecItemCopyMatching fails with errSecInteractionNotAllowed. This is an obvious result; the whole point of data protection is to enforce this security policy. Add — If you attempt to add a keychain item whose key material is unavailable, SecItemAdd fails with errSecInteractionNotAllowed. This is less obvious. The reason why this fails is that the system needs the key material to protect (by encryption) the keychain item, and it can’t do that if if that key material isn’t available. Update — If you attempt to update a keychain item whose key material is unavailable, SecItemUpdate fails with errSecInteractionNotAllowed. This result is an obvious consequence of the previous result. Delete — Deleting a keychain item, using SecItemDelete, doesn’t require its key material, and thus a delete will succeed when the item is otherwise unavailable. That last point is a significant pitfall. I regularly see keychain code like this: Read an item holding a critical user credential. If that works, use that credential. If it fails, delete the item and start from a ‘factory reset’ state. The problem is that, if your code ends up running in the background unexpectedly, step 1 fails with errSecInteractionNotAllowed and you turn around and delete the user’s credential. Ouch! Note Even if you didn’t write this code, you might have inherited it from a keychain wrapper library. See Think Before Wrapping, below. There are two paths forward here: If you don’t expect this code to work in the background, check for the errSecInteractionNotAllowed error and non-destructively cancel the operation in that case. If you expect this code to be running in the background, switch to a different data protection level. WARNING For the second path, the most obvious fix is to move from kSecAttrAccessibleWhenUnlocked to kSecAttrAccessibleAfterFirstUnlock. However, this is not a panacea. It’s possible that your app might end up running before first unlock [3]. So, if you choose the second path, you must also make sure to follow the advice for the first path. You can determine whether the device is unlocked using the isProtectedDataAvailable property and its associated notifications. However, it’s best not to use this property as part of your core code, because such preflighting is fundamentally racy. Rather, perform the operation and handle the error gracefully. It might make sense to use isProtectedDataAvailable property as part of debugging, logging, and diagnostic code. [1] For file data protection there’s an entitlement (com.apple.developer.default-data-protection) that controls the default data protection level. There’s no such entitlement for the keychain. That’s actually a good thing! In my experience the file data protection entitlement is an ongoing source of grief. See this thread if you’re curious. [2] This might seem like an odd error but it’s actually pretty reasonable: The operation needs some key material that’s currently unavailable. Only a user action can provide that key material. But the data protection keychain will never prompt the user to unlock their device. Thus you get an error instead. [3] iOS generally avoids running third-party code before first unlock, but there are circumstances where that can happen. The obvious legitimate example of this is a VoIP app, where the user expects their phone to ring even if they haven’t unlocked it since the last restart. There are also other less legitimate examples of this, including historical bugs that caused apps to launch in the background before first unlock. Best Practices With the pitfalls out of the way, let’s talk about best practices. Less Painful Dictionaries I look at a lot of keychain code and it’s amazing how much of it is way more painful than it needs to be. The biggest offender here is the dictionaries. Here are two tips to minimise the pain. First, don’t use CFDictionary. It’s seriously ugly. While the SecItem API is defined in terms of CFDictionary, you don’t have to work with CFDictionary directly. Rather, use NSDictionary and take advantage of the toll-free bridge. For example, consider this CFDictionary code: CFTypeRef keys[4] = { kSecClass, kSecAttrService, kSecMatchLimit, kSecReturnAttributes, }; static const int kTen = 10; CFNumberRef ten = CFNumberCreate(NULL, kCFNumberIntType, &kTen); CFAutorelease(ten); CFTypeRef values[4] = { kSecClassGenericPassword, CFSTR("AYS"), ten, kCFBooleanTrue, }; CFDictionaryRef query = CFDictionaryCreate( NULL, keys, values, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks ); Note This might seem rather extreme but I’ve literally seen code like this, and worse, while helping developers. Contrast this to the equivalent NSDictionary code: NSDictionary * query = @{ (__bridge NSString *) kSecClass: (__bridge NSString *) kSecClassGenericPassword, (__bridge NSString *) kSecAttrService: @"AYS", (__bridge NSString *) kSecMatchLimit: @10, (__bridge NSString *) kSecReturnAttributes: @YES, }; Wow, that’s so much better. Second, if you’re working in Swift, take advantage of its awesome ability to create NSDictionary values from Swift dictionary literals. Here’s the equivalent code in Swift: let query = [ kSecClass: kSecClassGenericPassword, kSecAttrService: "AYS", kSecMatchLimit: 10, kSecReturnAttributes: true, ] as NSDictionary Nice! Avoid Reusing Dictionaries I regularly see folks reuse dictionaries for different SecItem calls. For example, they might have code like this: var copyResult: CFTypeRef? = nil let dict = [ kSecClass: kSecClassGenericPassword, kSecAttrService: "AYS", kSecAttrAccount: "mrgumby", kSecReturnData: true, ] as NSMutableDictionary var err = SecItemCopyMatching(dict, &copyResult) if err == errSecItemNotFound { dict[kSecValueData] = Data("opendoor".utf8) err = SecItemAdd(dict, nil) } This specific example will work, but it’s easy to spot the logic error. kSecReturnData is a return type property and it makes no sense to pass it to a SecItemAdd call whose second parameter is nil. I’m not sure why folks do this. I think it’s because they think that constructing dictionaries is expensive. Regardless, this pattern can lead to all sorts of weird problems. For example, it’s the leading cause of the issue described in the Queries and the Uniqueness Constraints section, above. My advice is that you use a new dictionary for each call. That prevents state from one call accidentally leaking into a subsequent call. For example, I’d rewrite the above as: var copyResult: CFTypeRef? = nil let query = [ kSecClass: kSecClassGenericPassword, kSecAttrService: "AYS", kSecAttrAccount: "mrgumby", kSecReturnData: true, ] as NSMutableDictionary var err = SecItemCopyMatching(query, &copyResult) if err == errSecItemNotFound { let add = [ kSecClass: kSecClassGenericPassword, kSecAttrService: "AYS", kSecAttrAccount: "mrgumby", kSecValueData: Data("opendoor".utf8), ] as NSMutableDictionary err = SecItemAdd(add, nil) } It’s a bit longer, but it’s much easier to track the flow. And if you want to eliminate the repetition, use a helper function: func makeDict() -> NSMutableDictionary { [ kSecClass: kSecClassGenericPassword, kSecAttrService: "AYS", kSecAttrAccount: "mrgumby", ] as NSMutableDictionary } var copyResult: CFTypeRef? = nil let query = makeDict() query[kSecReturnData] = true var err = SecItemCopyMatching(query, &copyResult) if err == errSecItemNotFound { let add = makeDict() query[kSecValueData] = Data("opendoor".utf8) err = SecItemAdd(add, nil) } Think Before Wrapping A lot of folks look at the SecItem API and immediately reach for a wrapper library. A keychain wrapper library might seem like a good idea but there are some serious downsides: It adds another dependency to your project. Different subsystems within your project may use different wrappers. The wrapper can obscure the underlying API. Indeed, its entire raison d’être is to obscure the underlying API. This is problematic if things go wrong. I regularly talk to folks with hard-to-debug keychain problems and the conversation goes something like this: Quinn: What attributes do you use in the query dictionary? J R Developer: What’s a query dictionary? Quinn: OK, so what error are you getting back? J R Developer: It throws WrapperKeychainFailedError. That’s not helpful )-: If you do use a wrapper, make sure it has diagnostic support that includes the values passed to and from the SecItem API. Also make sure that, when it fails, it returns an error that includes the underlying keychain error code. These benefits will be particularly useful if you encounter a keychain problem that only shows up in the field. Wrappers must choose whether to be general or specific. A general wrapper may be harder to understand than the equivalent SecItem calls, and it’ll certainly contain a lot of complex code. On the other hand, a specific wrapper may have a model of the keychain that doesn’t align with your requirements. I recommend that you think twice before using a keychain wrapper. Personally I find the SecItem API relatively easy to call, assuming that: I use the techniques shown in Less Painful Dictionaries, above, to avoid having to deal with CFDictionary. I use my secCall(…) helpers to simplify error handling. For the code, see Calling Security Framework from Swift. If you’re not prepared to take the SecItem API neat, consider writing your own wrapper, one that’s tightly focused on the requirements of your project. For example, in my VPN apps I use the wrapper from this post, which does exactly what I need in about 100 lines of code. Prefer to Update Of the four SecItem functions, SecItemUpdate is the most neglected. Rather than calling SecItemUpdate I regularly see folks delete and then re-add the item. This is a shame because SecItemUpdate has some important benefits: It preserves persistent references. If you delete and then re-add the item, you get a new item with a new persistent reference. It’s well aligned with the fundamental database nature of the keychain. It forces you to think about which attributes uniquely identify your item and which items can be updated without changing the item’s identity. For a cool example of its power, check out Transfer Items Between Keychain Access Groups, below. Understand These Key Attributes Key items have a number of attributes that are similarly named, and it’s important to keep them straight. I created a cheat sheet for this, namely, SecItem attributes for keys. You wouldn’t believe how often I consult this! Starting from Scratch Sometimes it’s useful to be able to start from scratch. Imagine, for example, you’ve been rapidly iterating on some keychain code and you’re not sure whether your current code is compatible with items created by your earlier code. To simplify things, use SecItemDelete to delete all the existing items: _ = SecItemDelete([ kSecClass: kSecClassGenericPassword, kSecUseDataProtectionKeychain: true, ] as NSDictionary) WARNING This code is obviously dangerous. Read the discussion below to learn more. This deletes all generic password items that your app has access to. To delete items in a different keychain item class, change the value for the kSecClass attribute. This code uses kSecUseDataProtectionKeychain. On iOS there is only one keychain, so this is a no-op. On macOS it limits the effect to the data protection keychain. Without it, the call will delete items in file-based keychains as well. This is very dangerous because those items might belong to other apps, or the system. If you want to use this technique in a Mac product that uses the file-based keychain, don’t use this code. Rather, write code that carefully targets your app’s keychain items. Alternatively, avoid this code and instead delete the items using Keychain Access or the security tool. For more about keychains on the Mac, see TN3137 On Mac keychain APIs and implementations. I often invoke this code from my app’s debug UI. For example, in a Mac app I might have a Debug menu with a Reset Keychain menu item. I typically compile that code out of the release build. However, you might choose to leave it in your final product. For example, you might have a ‘secret’ way to enable the debug UI [1] so that you can use it to help users with problems. In that case, make sure your debug UI informs the user of the potential consequences of this action. If you’re working on a big app, it might have different subsystems that user the keychain in different ways. A debug action like this might make sense for your subsystem but not for all the others. In that case, coordinate this work with the owners of any other subsystems that use the keychain. [1] If your app ships on the App Store, make sure that App Review knows about your debug UI. Investigating Complex Attributes Some attributes have values where the format is not obvious. For example, the kSecAttrIssuer attributed is documented as: The corresponding value is of type CFData and contains the X.500 issuer name of a certificate. What exactly does that mean? If I want to search the keychain for all certificates issued by a specific certificate authority, what value should I supply? One way to figure this out is to add a certificate to the keychain, read the attributes back, and then dump the kSecAttrIssuer value. For example: let cert: SecCertificate = … let attrs = try secCall { SecItemAdd([ kSecValueRef: cert, kSecReturnAttributes: true, ] as NSDictionary, $0) } as! [String: Any] let issuer = attrs[kSecAttrIssuer as String] as! NSData print((issuer as NSData).debugDescription) // prints: <3110300e 06035504 030c074d 6f757365 4341310b 30090603 55040613 024742> Those bytes represent the contents of a X.509 Name ASN.1 structure with DER encoding. This is without the outer SEQUENCE element, so if you dump it as ASN.1 you’ll get a nice dump of the first SET and then a warning about extra stuff at the end of the file: % xxd issuer.asn1 00000000: 3110 300e 0603 5504 030c 074d 6f75 7365 1.0...U....Mouse 00000010: 4341 310b 3009 0603 5504 0613 0247 42 CA1.0...U....GB % dumpasn1 -p issuer.asn1 SET { SEQUENCE { OBJECT IDENTIFIER commonName (2 5 4 3) UTF8String 'MouseCA' } } Warning: Further data follows ASN.1 data at position 18. Note For details on the Name structure, see section 4.1.2.4 of RFC 5280. Amusingly, if you run the same test against the file-based keychain you’ll… crash. OK, that’s not amusing. It turns out that the code above doesn’t work when targeting the file-based keychain because SecItemAdd doesn’t return a dictionary but rather an array of dictionaries (r. 21111543). Once you get past that, however, you’ll see it print: <301f3110 300e0603 5504030c 074d6f75 73654341 310b3009 06035504 06130247 42> Which is different! Dumping it as ASN.1 shows that it’s the full Name structure, including the outer SEQUENCE element: % xxd issuer-file-based.asn1 00000000: 301f 3110 300e 0603 5504 030c 074d 6f75 0.1.0...U....Mou 00000010: 7365 4341 310b 3009 0603 5504 0613 0247 seCA1.0...U....G 00000020: 42 B % dumpasn1 -p issuer-file-based.asn1 SEQUENCE { SET { SEQUENCE { OBJECT IDENTIFIER commonName (2 5 4 3) UTF8String 'MouseCA' } } SET { SEQUENCE { OBJECT IDENTIFIER countryName (2 5 4 6) PrintableString 'GB' } } } This difference in behaviour between the data protection and file-based keychains is a known bug (r. 26391756) but in this case it’s handy because the file-based keychain behaviour makes it easier to understand the data protection keychain behaviour. Import, Then Add It’s possible to import data directly into the keychain. For example, you might use this code to add a certificate: let certData: Data = … try secCall { SecItemAdd([ kSecClass: kSecClassCertificate, kSecValueData: certData, ] as NSDictionary, nil) } However, it’s better to import the data and then add the resulting credential reference. For example: let certData: Data = … let cert = try secCall { SecCertificateCreateWithData(nil, certData as NSData) } try secCall { SecItemAdd([ kSecValueRef: cert, ] as NSDictionary, nil) } There are two advantages to this: If you get an error, you know whether the problem was with the import step or the add step. It ensures that the resulting keychain item has the correct attributes. This is especially important for keys. These can be packaged in a wide range of formats, so it’s vital to know whether you’re interpreting the key data correctly. I see a lot of code that adds key data directly to the keychain. That’s understandable because, back in the day, this was the only way to import a key on iOS. Fortunately, that’s not been the case since the introduction of SecKeyCreateWithData in iOS 10 and aligned releases. For more information about importing keys, see Importing Cryptographic Keys. App Groups on the Mac Sharing access to keychain items among a collection of apps explains that three entitlements determine your keychain access: keychain-access-groups application-identifier (com.apple.application-identifier on macOS) com.apple.security.application-groups In the discussion of the last item says: You can use app group names as keychain access group names, without adding them to the Keychain access groups entitlement. That’s true, but it’s also potentially misleading. This affordance works all the time on iOS and its child platforms. But on the Mac it only works if your entitlements are validated by a provisioning profile. For more on that topic, see App Groups: macOS vs iOS: Working Towards Harmony. Transfer Items Between Keychain Access Groups In some cases you might want to move a bunch of keychain items from one app group to another, for example, when preparing for an App ID prefix change. This is easier than you might first think. For example, to move all the generic password items for a particular service between oldGroup and newGroup, run this code: try secCall { SecItemUpdate([ kSecClass: kSecClassGenericPassword, kSecUseDataProtectionKeychain: true, kSecAttrAccessGroup: oldGroup, kSecAttrService: "MyService", ] as NSDictionary, [ kSecAttrAccessGroup: newGroup, ] as NSDictionary) } This snippet highlights both the power and the subtlety of the SecItem API. The first parameter to SecItemUpdate is a pure query dictionary. It selects all the generic password items for MyService that are in the old keychain access group. In contrast, the second parameter is an update dictionary, which in this case just changes a single attribute. See SecItem: Fundamentals for a deeper explanation of these concepts. This call is atomic from your perspective [1]. The call will either fail or all the selected items will move as one. IMPORTANT Bulk operations like this are risky. That’s not because the keychain item will do the wrong thing, but rather because you have to be very careful what you ask for. If, for example, your query dictionary matches more than you intended, you might end up moving items unexpectedly. Be careful when crafting this code, and test it thoroughly. [1] It may even be atomic in a wider sense, given that the keychain is currently implemented as an SQLite database. Command-Line Tools Access to the data protection keychain is mediated by various entitlements, as described in Sharing access to keychain items among a collection of apps. Those entitlements are restricted, that is, they must be authorised by a provisioning profile. This is fine for apps, app extensions, and system extensions, which are all bundled code; they exist within an app-like bundle structure. However, it’s problematic for command-line tools on the Mac, which are non-bundled executables. There’s no obvious way for such executables to include a provisioning profile (r. 125850707). For more about provisioning profiles, see TN3125 Inside Code Signing: Provisioning Profiles. For more about bundled code, see Creating distribution-signed code for macOS. If you’re creating a non-bundled executable for the Mac, first consider its execution context. If it runs as a launchd daemon, or outside of a user login context in some other way, it can’t use the data protection keychain. See TN3137 On Mac keychain APIs and implementations for more about that. If the executable is a command-line tool that’s typically run by the user, in Terminal or over SSH, it can use the data protection keychain. However: You have to embed the tool in an app-like wrapper. For more about that, see Signing a daemon with a restricted entitlement. If the tool is run via SSH, the user’s data protection keychain might be locked. To resolve this, the user must explicitly unlock their login keychain using the security tool. Note While the login keychain is a file-based keychain, unlocking it in this way also unlocks the data protection keychain. In-memory Plug-ins An in-memory plug-in is a native plug-in that’s loaded directly into the host process as a Mach-O bundle or shared library. For example, macOS screen savers are in-memory plug-ins. Note In-memory plug-ins are quite old school. Modern plug-ins are packaged as app extensions. If you’re created a Mac app that supports plug-ins, support app extension plug-ins by adopting ExtensionKit. From the keychain perspective, an in-memory plug-in is indistinguishable from the host app. This has both pros and cons: It can access all the keychain items that the host app has access to, in either the file-based or data protection keychains. It can’t access additional keychain items. For example, you can’t grant your in-memory plug-in access to a keychain access group that’s used by other apps that you create. I’ll leave it up to you to decide which of these is a pro and which is a con (-: Revision History 2026-07-02 Clarified the terminology in the Keys Aren’t Stored in the Secure Enclave section. 2026-05-21 Enhanced the code snippet in the Lost Keychain Items, Redux section. 2026-04-27 Added the Command-Line Tools and In-memory Plug-ins sections. 2026-04-15 Significantly expanded the example in the Lost Keychain Items section. 2026-04-14 Added the Starting from Scratch section. 2026-04-02 Added the Transfer Items Between Keychain Access Groups section. Updated the App Groups on the Mac section to account for recent changes to app groups on the Mac. Made other minor editorial changes. 2025-06-29 Added the Data Protection and Background Execution section. Made other minor editorial changes. 2025-02-03 Added another specific example to the Careful With that Shim, Mac Developer section. 2025-01-29 Added somes specific examples to the Careful With that Shim, Mac Developer section. 2025-01-23 Added the Import, Then Add section. 2024-08-29 Added a discussion of identity formation to the Digital Identities Aren’t Real section. 2024-04-11 Added the App Groups on the Mac section. 2023-10-25 Added the Lost Keychain Items and Lost Keychain Items, Redux sections. 2023-09-22 Made minor editorial changes. 2023-09-12 Fixed various bugs in the revision history. Added the Erroneous Attributes section. 2023-02-22 Fixed the link to the VPNKeychain post. Corrected the name of the Context Matters section. Added the Investigating Complex Attributes section. 2023-01-28 First posted.
Replies
0
Boosts
0
Views
5.3k
Activity
Jul ’26
Sign in with Apple for web - is a published App Store app required, or just an App ID?
I'm implementing Sign in with Apple for a web-only service (Services ID associated with a primary App ID, plus a private key for the client secret JWT). Before I finalize the setup I want to confirm one prerequisite, because two of Apple's own pages give different answers. "Configuring your environment for Sign in with Apple" states that to authenticate users with a web service you must have an existing app in the App Store that uses Sign in with Apple: https://developer.apple.com/documentation/signinwithapple/configuring-your-environment-for-sign-in-with-apple "Configure Sign in with Apple for the web" (Account Help) only says to associate your website with an existing primary App ID enabled for Sign in with Apple — i.e., just a registered identifier, with no mention of a published app: https://developer.apple.com/help/account/capabilities/configure-sign-in-with-apple-for-the-web/ My question: for a web-only service, is a published (or in-review) App Store app actually required, or is a registered App ID identifier with the Sign in with Apple capability enabled sufficient on its own? And if a published app isn't required, is the "existing app in the App Store" wording just meant to describe the App ID rather than a live listing? I've contacted Developer Support and was pointed back to the general docs, which are the source of the contradiction rather than the answer, so I'm hoping an engineer or someone who has shipped a web-only setup can confirm which prerequisite governs. Thanks in advance.
Replies
0
Boosts
0
Views
188
Activity
Jul ’26
https://app-site-association.cdn-apple.com/a/v1/* 404 Not Found
% curl -v https://app-site-association.cdn-apple.com/a/v1/zfcs.bankts.cn Host app-site-association.cdn-apple.com:443 was resolved. IPv6: (none) IPv4: 218.92.226.151, 119.101.148.193, 218.92.226.6, 115.152.217.3 Trying 218.92.226.151:443... Connected to app-site-association.cdn-apple.com (218.92.226.151) port 443 ALPN: curl offers h2,http/1.1 (304) (OUT), TLS handshake, Client hello (1): CAfile: /etc/ssl/cert.pem CApath: none (304) (IN), TLS handshake, Server hello (2): (304) (IN), TLS handshake, Unknown (8): (304) (IN), TLS handshake, Certificate (11): (304) (IN), TLS handshake, CERT verify (15): (304) (IN), TLS handshake, Finished (20): (304) (OUT), TLS handshake, Finished (20): SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384 / [blank] / UNDEF ALPN: server accepted http/1.1 Server certificate: subject: C=US; ST=California; O=Apple Inc.; CN=app-site-association.cdn-apple.com start date: Sep 25 13:58:08 2025 GMT expire date: Mar 31 17:44:25 2026 GMT subjectAltName: host "app-site-association.cdn-apple.com" matched cert's "app-site-association.cdn-apple.com" issuer: CN=Apple Public Server RSA CA 11 - G1; O=Apple Inc.; ST=California; C=US SSL certificate verify ok. using HTTP/1.x GET /a/v1/zfcs.bankts.cn HTTP/1.1 Host: app-site-association.cdn-apple.com User-Agent: curl/8.7.1 Accept: / Request completely sent off < HTTP/1.1 404 Not Found < Content-Type: text/plain; charset=utf-8 < Content-Length: 10 < Connection: keep-alive < Server: nginx < Date: Wed, 04 Feb 2026 02:26:00 GMT < Expires: Wed, 04 Feb 2026 02:26:10 GMT < Age: 24 < Apple-Failure-Details: {"cause":"context deadline exceeded (Client.Timeout exceeded while awaiting headers)"} < Apple-Failure-Reason: SWCERR00301 Timeout < Apple-From: https://zfcs.bankts.cn/.well-known/apple-app-site-association < Apple-Try-Direct: true < Vary: Accept-Encoding < Via: https/1.1 jptyo12-3p-pst-003.ts.apple.com (acdn/3.16363), http/1.1 jptyo12-3p-pac-043.ts.apple.com (acdn/3.16363), https/1.1 jptyo12-3p-pfe-002.ts.apple.com (acdn/3.16363) < X-Cache: MISS KS-CLOUD < CDNUUID: 736dc646-57fb-43c9-aa0d-eedad3a534f8-1154605242 < x-link-via: yancmp83:443;xmmp02:443;fzct321:443; < x-b2f-cs-cache: no-cache < X-Cache-Status: MISS from KS-CLOUD-FZ-CT-321-35 < X-Cache-Status: MISS from KS-CLOUD-XM-MP-02-16 < X-Cache-Status: MISS from KS-CLOUD-YANC-MP-83-15 < X-KSC-Request-ID: c4a640c815640ee93c263a357ee919d6 < CDN-Server: KSFTF < X-Cdn-Request-ID: c4a640c815640ee93c263a357ee919d6 < Not Found Connection #0 to host app-site-association.cdn-apple.com left intact
Replies
2
Boosts
0
Views
470
Activity
Jul ’26
DCAppAttestService.isSupported always returns false on macOS 27
I've been implementing App Attest on macOS 27 following the WWDC 2026 Session 201 announcement. DCAppAttestService.shared.isSupported always returns false on my M4 Mac running macOS 27.0 (26A5368g), even with the correct entitlement and a valid provisioning profile. What I have set up (correctly, as far as I can tell) com.apple.developer.devicecheck.app-attest-opt-in capability enabled in the Developer Portal (value CDhash) Entitlement present in both the binary and the embedded provisioning profile Developer ID signed, ProvisionsAllDevices: true The problem DCAppAttestService.shared.isSupported returns false from every process type I tested: An EndpointSecurity system extension A launchd daemon A sandboxed app running in user session generateKey() fails with com.apple.devicecheck.error code 1 (featureUnsupported). Root cause? (from devicecheckd logs) I see these logs devicecheckd: [com.apple.devicecheck:aai] FeatureFlagsManager.m:35 Mac feature flag enabled { enabled=1 }. devicecheckd: (AppAttestInternal) [com.apple.appattest:secl] SecurityController.swift:44 Failed to fetch value for entitlement. { entitlement=com.apple.devicecheck.daemon-client } devicecheckd: (AppAttestInternal) [com.apple.appattest:aahl] AppAttestHandler.swift:48 Client connection is ineligible. { clientUUID=nil } So the feature IS active in macOS 27 (Mac feature flag enabled=1), but devicecheckd immediately rejects any connecting process that doesn't hold the private entitlement com.apple.devicecheck.daemon-client. What is com.apple.devicecheck.daemon-client? Searching public entitlement databases shows this entitlement exists on iOSbut no macOS binary appears to hold it in any public database. It's not available to third-party developers via the Developer Portal. This check in SecurityController.swift:44 appears to be new in this beta. Questions Is com.apple.devicecheck.daemon-client the correct mechanism for third-party developers to use App Attest on macOS 27, or is this an internal gating mechanism that will be replaced/removed before GM? Is App Attest on macOS 27 fully available to third-party developers in this seed, or is it still restricted to Apple-internal testing? Is there a different entitlement or provisioning capability that third-party developers should request to allow DCAppAttestService.isSupported to return true?
Replies
1
Boosts
0
Views
420
Activity
Jun ’26
SecurityAgent taking focus for plugin in macOS 26.1
We have a custom SecurityAgentPlugin that is triggered by multiple authorizationdb entries. Some customers report that the SecurityAgent process takes window focus even though no UI or windows are displayed. Our plugin explicitly ignores the _securityAgent user and does not show any UI for that user. However, in macOS 26.1, it appears that the plugin still causes the SecurityAgent to take focus as soon as it is triggered. Is this a change in macOS 26.1 or a bug? Can we do anything to prevent "focus stealing"?
Replies
29
Boosts
4
Views
9.2k
Activity
Jun ’26
SecurityAgent stealing keyboard focus on macOS 26 Tahoe — confirmed chain via exec logs
Environment: MacBook Pro 14-inch Nov 2023 (Apple Silicon M3) macOS 26.5 (25F71) and 26.5.1 (25F80) MDM: Kandji/Iru enrolled BeyondTrust EPM-M 26.1.1495 Confirmed chain via epsext exec logs: The Kandji/Iru Parameter Agent (kandji-parameter-agent) calls /usr/sbin/systemsetup -getusingnetworktime every 15 minutes. Each invocation requests the system.preferences authorization right, waking authd → writeconfig.xpc → SecurityAgent. SecurityAgent then opens a SkyLight connection, calls SetFrontProcess, causes the active window to resign key appearance, and closes — all within 3–15 seconds. Cross-referenced against user-reported times: User reported focus steal at 10:13, 10:58, 11:13, 11:43 (CDT). SecurityAgent fired at exactly those times to the second in our WindowServer logs. Key finding: The systemsetup call itself is not the root issue — it's doing its job. The problem is that on macOS 26 Tahoe, this auth request causes SecurityAgent to grab keyboard focus as a side effect, which it should not do for a background/silent authorization check with no user interaction required. BeyondTrust KB0023327 documents the PMCAdapter event as a separate but related trigger. Both are symptoms of the same underlying SecurityAgent behaviour change in macOS 26.
Replies
1
Boosts
0
Views
393
Activity
Jun ’26
How do I get MacOS to stop using my SecurityAgentPlugin from the StagedPlugins folder?
When I install a new version in /Library/Security/SecurityAgentPlugins, the Mac keeps loading from the StagedPlugins folder. Probably due to the previous version crashing at some point.
Replies
5
Boosts
0
Views
477
Activity
Jun ’26
Unable to trigger .matchedExcludedCredentials for passkey
Hi everyone, was hoping I could get some help. Recently I've been trying to implement passkeys on my app and one of the use cases was that we don't allow users to create duplicated passkeys from the same device they are on. I passed the excludedCredentials into the registration and tried creating a passkey twice, on the 2nd time I am unable to create a passkey but instead of triggering the .matchedExcludedCredentials from the ASAuthorizationError like I hoped, I get a WKErrorDomain code 8 with the localized description At least one credential matches an entry of the excludeCredentials list in the platform attached authenticator. Been debugging but I still couldn't find the answer as to why the AS error is not triggered.
Replies
0
Boosts
0
Views
252
Activity
Jun ’26