From 3416fee4eb2d4d4f9cc9f2876b2d8b3a7a0f8510 Mon Sep 17 00:00:00 2001
From: Jakub Korytko <jakub@korytko.me>
Date: Fri, 28 Nov 2025 16:08:16 +0100
Subject: [PATCH 1/7] Split expo-secure-store patch

---
 patches/expo-secure-store/details.md          |  72 +-
 ...re+14.2.4+001+enable-device-fallback.patch | 388 ++++++++++
 ...e-store+14.2.4+002+return-auth-type.patch} | 685 ++++--------------
 ...2.4+003+force-authentication-on-save.patch | 129 ++++
 ...cure-store+14.2.4+004+fail-on-update.patch |  99 +++
 ...ce-read-authentication-on-simulators.patch |  78 ++
 6 files changed, 905 insertions(+), 546 deletions(-)
 create mode 100644 patches/expo-secure-store/expo-secure-store+14.2.4+001+enable-device-fallback.patch
 rename patches/expo-secure-store/{expo-secure-store+14.2.4+001+additional-config-options.patch => expo-secure-store+14.2.4+002+return-auth-type.patch} (57%)
 create mode 100644 patches/expo-secure-store/expo-secure-store+14.2.4+003+force-authentication-on-save.patch
 create mode 100644 patches/expo-secure-store/expo-secure-store+14.2.4+004+fail-on-update.patch
 create mode 100644 patches/expo-secure-store/expo-secure-store+14.2.4+005+force-read-authentication-on-simulators.patch

diff --git a/patches/expo-secure-store/details.md b/patches/expo-secure-store/details.md
index e9ef686113ea..aaf1bb1ab02f 100644
--- a/patches/expo-secure-store/details.md
+++ b/patches/expo-secure-store/details.md
@@ -1,22 +1,72 @@
 # `expo-secure-store` patches
 
-### [expo-secure-store+14.2.4+001+additional-config-options.patch](expo-secure-store+14.2.4+001+additional-config-options.patch)
+### [expo-secure-store+14.2.4+001+enable-device-fallback.patch](expo-secure-store+14.2.4+001+enable-device-fallback.patch)
 
 - Reason:
 
     ```
-    Expo SecureStore is a great library, but it lacks some of the functionality we need, i.e.:
+    We need to enable users to use any device screen lock instead of biometrics.
+    This is not enabled in the SecureStore, so this patch changes the required input to either biometrics or screen lock knowledge if the 'enableDeviceFallback' flag is set to true.
+    Additionally, support for screen locks can be checked using the new 'canUseDeviceCredentialsAuthentication' method. 
+    ```
+
+- Upstream PR/issue: TBA
+- E/App issue: https://github.com/Expensify/App/issues/75225
+- PR introducing patch: https://github.com/Expensify/App/pull/76288
+
+### [expo-secure-store+14.2.4+002+return-auth-type.patch](expo-secure-store+14.2.4+002+return-auth-type.patch)
+
+- Reason:
+
+    ```
+    This patch makes the read and write methods return the authentication type used to access the store.
+    It uses a pre-defined constant that mimics an enum and can also be imported directly from the app.
+    ```
+
+- Upstream PR/issue: TBA
+- E/App issue: https://github.com/Expensify/App/issues/75225
+- PR introducing patch: https://github.com/Expensify/App/pull/76288
+
+### [expo-secure-store+14.2.4+003+force-authentication-on-save.patch](expo-secure-store+14.2.4+003+force-authentication-on-save.patch)
+
+- Reason:
+
+    ```
+    The iOS does not require authentication when a value is saved to the keychain. We cannot force iOS to do so.
+    However, to maintain consistency with Android, setting the 'forceAuthenticationOnSave' flag to true results in a prompt appearing
+    before the value is saved. This only works in the asynchronous version of the save method.
+    ```
 
-    - Returning the type of authentication used for accessing the store
-    - Allowing the fallback to device credentials
-    - Enabling use of device credentials if no biometrics are enrolled/supported.
-    - Enforcing iOS to always ask for the authentication when accessing the store (by default iOS skips the authentication if, for example, user unlocked the screen recently with biometrics)
-    - Letting user know if the key entry is already stored instead of overwriting it without any feedback
+- Upstream PR/issue: TBA
+- E/App issue: https://github.com/Expensify/App/issues/75225
+- PR introducing patch: https://github.com/Expensify/App/pull/76288
 
+### [expo-secure-store+14.2.4+004+fail-on-update.patch](expo-secure-store+14.2.4+004+fail-on-update.patch)
+
+- Reason:
 
-    This is fixed with this patch, but instead of hardcoding solution for above needs, the patch adds it into the SecureStore options
     ```
+    If a value already exists in the SecureStore and a new one is saved, the existing value is simply overwritten.
+    To avoid unexpected behaviour, set the 'failOnUpdate' flag to true to trigger an error when the given key is already in the store.
+    ```
+
+- Upstream PR/issue: TBA
+- E/App issue: https://github.com/Expensify/App/issues/75225
+- PR introducing patch: https://github.com/Expensify/App/pull/76288
+
+### [expo-secure-store+14.2.4+005+force-read-authentication-on-simulators.patch](expo-secure-store+14.2.4+005+force-read-authentication-on-simulators.patch)
+
+- Reason:
+
+    ```
+    The LocalAuthentication behaves slightly differently on iOS simulators.
+    In numerous cases, the authentication prompts are skipped on simulators (as opposed to real devices).
+    Setting 'forceReadAuthenticationOnSimulators' flag to true forces the prompt to appear on simulators when a value with the `requireAuthentication` flag set to true is read.
+    The flag is added is purely for testing the app on simulators, in cases where the prompt does not appear when the value is read.
+    It has no effect on real devices.
+    ```
+
+- Upstream PR/issue: TBA
+- E/App issue: https://github.com/Expensify/App/issues/75225
+- PR introducing patch: https://github.com/Expensify/App/pull/76288
 
-- Upstream PR/issue: 🛑
-- E/App issue: No issue, this patch adjust the library for our needs.
-- PR introducing patch: https://github.com/Expensify/App/pull/69863
\ No newline at end of file
diff --git a/patches/expo-secure-store/expo-secure-store+14.2.4+001+enable-device-fallback.patch b/patches/expo-secure-store/expo-secure-store+14.2.4+001+enable-device-fallback.patch
new file mode 100644
index 000000000000..9a0b432e66d9
--- /dev/null
+++ b/patches/expo-secure-store/expo-secure-store+14.2.4+001+enable-device-fallback.patch
@@ -0,0 +1,388 @@
+diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/AuthenticationHelper.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/AuthenticationHelper.kt
+index a281b88..4a1a009 100644
+--- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/AuthenticationHelper.kt
++++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/AuthenticationHelper.kt
+@@ -2,6 +2,7 @@ package expo.modules.securestore
+ 
+ import android.annotation.SuppressLint
+ import android.app.Activity
++import android.app.KeyguardManager
+ import android.content.Context
+ import android.os.Build
+ import androidx.biometric.BiometricManager
+@@ -19,9 +20,9 @@ class AuthenticationHelper(
+ ) {
+   private var isAuthenticating = false
+ 
+-  suspend fun authenticateCipher(cipher: Cipher, requiresAuthentication: Boolean, title: String): Cipher {
++  suspend fun authenticateCipher(cipher: Cipher, requiresAuthentication: Boolean, title: String, enableDeviceFallback: Boolean): Cipher {
+     if (requiresAuthentication) {
+-      return openAuthenticationPrompt(cipher, title).cryptoObject?.cipher
++      return openAuthenticationPrompt(cipher, title, enableDeviceFallback).cryptoObject?.cipher
+         ?: throw AuthenticationException("Couldn't get cipher from authentication result")
+     }
+     return cipher
+@@ -29,7 +30,8 @@ class AuthenticationHelper(
+ 
+   private suspend fun openAuthenticationPrompt(
+     cipher: Cipher,
+-    title: String
++    title: String,
++    enableDeviceFallback: Boolean
+   ): BiometricPrompt.AuthenticationResult {
+     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+       throw AuthenticationException("Biometric authentication requires Android API 23")
+@@ -41,11 +43,16 @@ class AuthenticationHelper(
+     isAuthenticating = true
+ 
+     try {
+-      assertBiometricsSupport()
++      if (enableDeviceFallback) {
++        assertDeviceSecurity()
++      } else {
++        assertBiometricsSupport()
++      }
++
+       val fragmentActivity = getCurrentActivity() as? FragmentActivity
+         ?: throw AuthenticationException("Cannot display biometric prompt when the app is not in the foreground")
+ 
+-      val authenticationPrompt = AuthenticationPrompt(fragmentActivity, context, title)
++      val authenticationPrompt = AuthenticationPrompt(fragmentActivity, context, title, enableDeviceFallback)
+ 
+       return withContext(Dispatchers.Main.immediate) {
+         return@withContext authenticationPrompt.authenticate(cipher)
+@@ -56,6 +63,14 @@ class AuthenticationHelper(
+     }
+   }
+ 
++  fun assertDeviceSecurity() {
++    val manager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
++    val isSecure = manager.isDeviceSecure
++    if (!isSecure) {
++      throw AuthenticationException("No authentication method available")
++    }
++  }
++
+   fun assertBiometricsSupport() {
+     val biometricManager = BiometricManager.from(context)
+     @SuppressLint("SwitchIntDef") // BiometricManager.BIOMETRIC_SUCCESS shouldn't do anything
+diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/AuthenticationPrompt.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/AuthenticationPrompt.kt
+index e5729cc..2543b0c 100644
+--- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/AuthenticationPrompt.kt
++++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/AuthenticationPrompt.kt
+@@ -1,5 +1,7 @@
+ package expo.modules.securestore
+ 
++import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
++import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
+ import android.content.Context
+ import androidx.biometric.BiometricPrompt
+ import androidx.biometric.BiometricPrompt.PromptInfo
+@@ -11,11 +13,12 @@ import kotlin.coroutines.resume
+ import kotlin.coroutines.resumeWithException
+ import kotlin.coroutines.suspendCoroutine
+ 
+-class AuthenticationPrompt(private val currentActivity: FragmentActivity, context: Context, title: String) {
++class AuthenticationPrompt(private val currentActivity: FragmentActivity, context: Context, title: String, enableDeviceFallback: Boolean) {
++  private var authType: Int = if (enableDeviceFallback) BIOMETRIC_STRONG or DEVICE_CREDENTIAL else BIOMETRIC_STRONG
+   private var executor: Executor = ContextCompat.getMainExecutor(context)
+   private var promptInfo = PromptInfo.Builder()
+     .setTitle(title)
+-    .setNegativeButtonText(context.getString(android.R.string.cancel))
++    .setAllowedAuthenticators(authType)
+     .build()
+ 
+   suspend fun authenticate(cipher: Cipher): BiometricPrompt.AuthenticationResult? =
+diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt
+index 0fef884..3f21552 100644
+--- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt
++++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt
+@@ -75,6 +75,15 @@ open class SecureStoreModule : Module() {
+       }
+     }
+ 
++    Function("canUseDeviceCredentialsAuthentication") {
++      return@Function try {
++        authenticationHelper.assertDeviceSecurity()
++        true
++      } catch (e: AuthenticationException) {
++        false
++      }
++    }
++
+     OnCreate {
+       authenticationHelper = AuthenticationHelper(reactContext, appContext.legacyModuleRegistry)
+       hybridAESEncryptor = HybridAESEncryptor(reactContext, mAESEncryptor)
+@@ -201,7 +210,7 @@ open class SecureStoreModule : Module() {
+        back a value.
+        */
+       val secretKeyEntry: SecretKeyEntry = getOrCreateKeyEntry(SecretKeyEntry::class.java, mAESEncryptor, options, options.requireAuthentication)
+-      val encryptedItem = mAESEncryptor.createEncryptedItem(value, secretKeyEntry, options.requireAuthentication, options.authenticationPrompt, authenticationHelper)
++      val encryptedItem = mAESEncryptor.createEncryptedItem(value, secretKeyEntry, options.requireAuthentication, options.authenticationPrompt, authenticationHelper, options.enableDeviceFallback)
+       encryptedItem.put(SCHEME_PROPERTY, AESEncryptor.NAME)
+       saveEncryptedItem(encryptedItem, prefs, keychainAwareKey, options.requireAuthentication, options.keychainService)
+ 
+@@ -343,7 +352,11 @@ open class SecureStoreModule : Module() {
+     return getKeyEntry(keyStoreEntryClass, encryptor, options, requireAuthentication) ?: run {
+       // Android won't allow us to generate the keys if the device doesn't support biometrics or no biometrics are enrolled
+       if (requireAuthentication) {
+-        authenticationHelper.assertBiometricsSupport()
++        if (options.enableDeviceFallback) {
++          authenticationHelper.assertDeviceSecurity()
++        } else {
++          authenticationHelper.assertBiometricsSupport()
++        }
+       }
+       encryptor.initializeKeyStoreEntry(keyStore, options)
+     }
+diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt
+index 79a600f..0455fd6 100644
+--- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt
++++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt
+@@ -8,5 +8,6 @@ class SecureStoreOptions(
+   // Prompt can't be an empty string
+   @Field var authenticationPrompt: String = " ",
+   @Field var keychainService: String = SecureStoreModule.DEFAULT_KEYSTORE_ALIAS,
+-  @Field var requireAuthentication: Boolean = false
++  @Field var requireAuthentication: Boolean = false,
++  @Field var enableDeviceFallback: Boolean = false
+ ) : Record, Serializable
+diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/AESEncryptor.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/AESEncryptor.kt
+index 3a12dc9..1cd187f 100644
+--- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/AESEncryptor.kt
++++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/AESEncryptor.kt
+@@ -1,9 +1,10 @@
+ package expo.modules.securestore.encryptors
+ 
+-import android.annotation.TargetApi
++import android.os.Build
+ import android.security.keystore.KeyGenParameterSpec
+ import android.security.keystore.KeyProperties
+ import android.util.Base64
++import androidx.annotation.RequiresApi
+ import expo.modules.securestore.AuthenticationHelper
+ import expo.modules.securestore.DecryptException
+ import expo.modules.securestore.SecureStoreModule
+@@ -50,17 +51,22 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
+     return "${getKeyStoreAlias(options)}:$suffix"
+   }
+ 
+-  @TargetApi(23)
++  @RequiresApi(Build.VERSION_CODES.R)
+   @Throws(GeneralSecurityException::class)
+   override fun initializeKeyStoreEntry(keyStore: KeyStore, options: SecureStoreOptions): KeyStore.SecretKeyEntry {
+     val extendedKeystoreAlias = getExtendedKeyStoreAlias(options, options.requireAuthentication)
+     val keyPurposes = KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
+ 
++    val authType =
++      if (options.enableDeviceFallback) KeyProperties.AUTH_BIOMETRIC_STRONG or KeyProperties.AUTH_DEVICE_CREDENTIAL
++      else KeyProperties.AUTH_BIOMETRIC_STRONG
++
+     val algorithmSpec: AlgorithmParameterSpec = KeyGenParameterSpec.Builder(extendedKeystoreAlias, keyPurposes)
+       .setKeySize(AES_KEY_SIZE_BITS)
+       .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+       .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+       .setUserAuthenticationRequired(options.requireAuthentication)
++      .setUserAuthenticationParameters(0, authType)
+       .build()
+ 
+     val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, keyStore.provider)
+@@ -78,14 +84,15 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
+     keyStoreEntry: KeyStore.SecretKeyEntry,
+     requireAuthentication: Boolean,
+     authenticationPrompt: String,
+-    authenticationHelper: AuthenticationHelper
++    authenticationHelper: AuthenticationHelper,
++    enableDeviceFallback: Boolean,
+   ): JSONObject {
+     val secretKey = keyStoreEntry.secretKey
+     val cipher = Cipher.getInstance(AES_CIPHER)
+     cipher.init(Cipher.ENCRYPT_MODE, secretKey)
+ 
+     val gcmSpec = cipher.parameters.getParameterSpec(GCMParameterSpec::class.java)
+-    val authenticatedCipher = authenticationHelper.authenticateCipher(cipher, requireAuthentication, authenticationPrompt)
++    val authenticatedCipher = authenticationHelper.authenticateCipher(cipher, requireAuthentication, authenticationPrompt, enableDeviceFallback)
+ 
+     return createEncryptedItemWithCipher(plaintextValue, authenticatedCipher, gcmSpec)
+   }
+@@ -128,7 +135,7 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
+       throw DecryptException("Authentication tag length must be at least $MIN_GCM_AUTHENTICATION_TAG_LENGTH bits long", key, options.keychainService)
+     }
+     cipher.init(Cipher.DECRYPT_MODE, keyStoreEntry.secretKey, gcmSpec)
+-    val unlockedCipher = authenticationHelper.authenticateCipher(cipher, requiresAuthentication, options.authenticationPrompt)
++    val unlockedCipher = authenticationHelper.authenticateCipher(cipher, requiresAuthentication, options.authenticationPrompt, options.enableDeviceFallback)
+     return String(unlockedCipher.doFinal(ciphertextBytes), StandardCharsets.UTF_8)
+   }
+ 
+diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/HybridAESEncryptor.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/HybridAESEncryptor.kt
+index fb42599..5f8bbfd 100644
+--- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/HybridAESEncryptor.kt
++++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/HybridAESEncryptor.kt
+@@ -69,7 +69,8 @@ class HybridAESEncryptor(private var mContext: Context, private val mAESEncrypto
+     keyStoreEntry: KeyStore.PrivateKeyEntry,
+     requireAuthentication: Boolean,
+     authenticationPrompt: String,
+-    authenticationHelper: AuthenticationHelper
++    authenticationHelper: AuthenticationHelper,
++    enableDeviceFallback: Boolean,
+   ): JSONObject {
+     // This should never be called after we dropped Android SDK 22 support.
+     throw EncryptException(
+diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/KeyBasedEncryptor.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/KeyBasedEncryptor.kt
+index e493467..39459ff 100644
+--- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/KeyBasedEncryptor.kt
++++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/KeyBasedEncryptor.kt
+@@ -25,7 +25,8 @@ interface KeyBasedEncryptor<E : KeyStore.Entry> {
+     keyStoreEntry: E,
+     requireAuthentication: Boolean,
+     authenticationPrompt: String,
+-    authenticationHelper: AuthenticationHelper
++    authenticationHelper: AuthenticationHelper,
++    enableDeviceFallback: Boolean,
+   ): JSONObject
+ 
+   @Throws(GeneralSecurityException::class, JSONException::class)
+diff --git a/node_modules/expo-secure-store/build/SecureStore.d.ts b/node_modules/expo-secure-store/build/SecureStore.d.ts
+index d5cd157..be9acfe 100644
+--- a/node_modules/expo-secure-store/build/SecureStore.d.ts
++++ b/node_modules/expo-secure-store/build/SecureStore.d.ts
+@@ -78,6 +78,16 @@ export type SecureStoreOptions = {
+      * @platform ios
+      */
+     accessGroup?: string;
++    /**
++     * This flag enables users to authenticate using Lock Screen Knowledge Factor (e.g. PIN, pattern or password).
++     * For sensitive apps, it is recommended not having biometric fall back to such factor.
++     * @see: https://developer.android.com/security/fraud-prevention/authentication
++     *
++     * @default false
++     * @platform android
++     * @platform ios
++     */
++    enableDeviceFallback?: boolean;
+ };
+ /**
+  * Returns whether the SecureStore API is enabled on the current device. This does not check the app
+@@ -148,4 +158,11 @@ export declare function getItem(key: string, options?: SecureStoreOptions): stri
+  * @platform ios
+  */
+ export declare function canUseBiometricAuthentication(): boolean;
++/**
++ * Checks whether any device credentials are configured on the device.
++ * @return `true` if the device has device credentials configured. Otherwise, returns `false`.
++ * @platform android
++ * @platform ios
++ */
++export declare function canUseDeviceCredentialsAuthentication(): boolean;
+ //# sourceMappingURL=SecureStore.d.ts.map
+diff --git a/node_modules/expo-secure-store/build/SecureStore.js b/node_modules/expo-secure-store/build/SecureStore.js
+index 4d87b38..e11bd94 100644
+--- a/node_modules/expo-secure-store/build/SecureStore.js
++++ b/node_modules/expo-secure-store/build/SecureStore.js
+@@ -143,6 +143,15 @@ export function getItem(key, options = {}) {
+ export function canUseBiometricAuthentication() {
+     return ExpoSecureStore.canUseBiometricAuthentication();
+ }
++/**
++ * Checks whether any device credentials are configured on the device.
++ * @return `true` if the device has device credentials configured. Otherwise, returns `false`.
++ * @platform android
++ * @platform ios
++ */
++export function canUseDeviceCredentialsAuthentication() {
++    return ExpoSecureStore.canUseDeviceCredentialsAuthentication();
++}
+ function ensureValidKey(key) {
+     if (!isValidKey(key)) {
+         throw new Error(`Invalid key provided to SecureStore. Keys must not be empty and contain only alphanumeric characters, ".", "-", and "_".`);
+diff --git a/node_modules/expo-secure-store/ios/SecureStoreModule.swift b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
+index 439b08d..1268979 100644
+--- a/node_modules/expo-secure-store/ios/SecureStoreModule.swift
++++ b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
+@@ -66,6 +66,14 @@ public final class SecureStoreModule: Module {
+       return isBiometricsSupported
+       #endif
+     }
++
++    Function("canUseDeviceCredentialsAuthentication") { () -> Bool in
++      return areDeviceCredentialsEnabled()
++    }
++  }
++
++  private func areDeviceCredentialsEnabled() -> Bool {
++    return LAContext().canEvaluatePolicy(LAPolicy.deviceOwnerAuthentication, error: nil)
+   }
+ 
+   private func get(with key: String, options: SecureStoreOptions) throws -> String? {
+@@ -99,12 +107,17 @@ public final class SecureStoreModule: Module {
+     if !options.requireAuthentication {
+       setItemQuery[kSecAttrAccessible as String] = accessibility
+     } else {
+-      guard let _ = Bundle.main.infoDictionary?["NSFaceIDUsageDescription"] as? String else {
+-        throw MissingPlistKeyException()
++      if (!options.enableDeviceFallback) {
++        guard let _ = Bundle.main.infoDictionary?["NSFaceIDUsageDescription"] as? String else {
++          throw MissingPlistKeyException()
++        }
+       }
+ 
+       var error: Unmanaged<CFError>? = nil
+-      guard let accessOptions = SecAccessControlCreateWithFlags(kCFAllocatorDefault, accessibility, .biometryCurrentSet, &error) else {
++
++      let accessControlFlag: SecAccessControlCreateFlags = options.enableDeviceFallback ? .userPresence : .biometryCurrentSet
++
++      guard let accessOptions = SecAccessControlCreateWithFlags(kCFAllocatorDefault, accessibility, accessControlFlag, &error) else {
+         let errorCode = error.map { CFErrorGetCode($0.takeRetainedValue()) }
+         throw SecAccessControlError(errorCode)
+       }
+diff --git a/node_modules/expo-secure-store/ios/SecureStoreOptions.swift b/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
+index 7e3fa4d..c9e843b 100644
+--- a/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
++++ b/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
+@@ -15,4 +15,7 @@ internal struct SecureStoreOptions: Record {
+ 
+   @Field
+   var accessGroup: String?
++
++  @Field
++  var enableDeviceFallback: Bool = false
+ }
+diff --git a/node_modules/expo-secure-store/src/SecureStore.ts b/node_modules/expo-secure-store/src/SecureStore.ts
+index ee43e04..4f253a1 100644
+--- a/node_modules/expo-secure-store/src/SecureStore.ts
++++ b/node_modules/expo-secure-store/src/SecureStore.ts
+@@ -102,6 +102,17 @@ export type SecureStoreOptions = {
+    * @platform ios
+    */
+   accessGroup?: string;
++
++  /**
++   * This flag enables users to authenticate using Lock Screen Knowledge Factor (e.g. PIN, pattern or password).
++   * For sensitive apps, it is recommended not having biometric fall back to such factor.
++   * @see: https://developer.android.com/security/fraud-prevention/authentication
++   *
++   * @default false
++   * @platform android
++   * @platform ios
++   */
++  enableDeviceFallback?: boolean;
+ };
+ 
+ // @needsAudit
+@@ -226,6 +237,16 @@ export function canUseBiometricAuthentication(): boolean {
+   return ExpoSecureStore.canUseBiometricAuthentication();
+ }
+ 
++/**
++ * Checks whether any device credentials are configured on the device.
++ * @return `true` if the device has device credentials configured. Otherwise, returns `false`.
++ * @platform android
++ * @platform ios
++ */
++export function canUseDeviceCredentialsAuthentication(): boolean {
++  return ExpoSecureStore.canUseDeviceCredentialsAuthentication();
++}
++
+ function ensureValidKey(key: string) {
+   if (!isValidKey(key)) {
+     throw new Error(
diff --git a/patches/expo-secure-store/expo-secure-store+14.2.4+001+additional-config-options.patch b/patches/expo-secure-store/expo-secure-store+14.2.4+002+return-auth-type.patch
similarity index 57%
rename from patches/expo-secure-store/expo-secure-store+14.2.4+001+additional-config-options.patch
rename to patches/expo-secure-store/expo-secure-store+14.2.4+002+return-auth-type.patch
index d766eb5c0716..6a0171d3e5d0 100644
--- a/patches/expo-secure-store/expo-secure-store+14.2.4+001+additional-config-options.patch
+++ b/patches/expo-secure-store/expo-secure-store+14.2.4+002+return-auth-type.patch
@@ -1,25 +1,17 @@
 diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/AuthenticationHelper.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/AuthenticationHelper.kt
-index a281b88..dc84eb4 100644
+index 4a1a009..980ef0a 100644
 --- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/AuthenticationHelper.kt
 +++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/AuthenticationHelper.kt
-@@ -2,6 +2,7 @@ package expo.modules.securestore
-
- import android.annotation.SuppressLint
- import android.app.Activity
-+import android.app.KeyguardManager
- import android.content.Context
- import android.os.Build
- import androidx.biometric.BiometricManager
-@@ -19,17 +20,18 @@ class AuthenticationHelper(
+@@ -20,12 +20,12 @@ class AuthenticationHelper(
  ) {
    private var isAuthenticating = false
 
--  suspend fun authenticateCipher(cipher: Cipher, requiresAuthentication: Boolean, title: String): Cipher {
+-  suspend fun authenticateCipher(cipher: Cipher, requiresAuthentication: Boolean, title: String, enableDeviceFallback: Boolean): Cipher {
 -    if (requiresAuthentication) {
--      return openAuthenticationPrompt(cipher, title).cryptoObject?.cipher
+-      return openAuthenticationPrompt(cipher, title, enableDeviceFallback).cryptoObject?.cipher
 -        ?: throw AuthenticationException("Couldn't get cipher from authentication result")
-+  suspend fun authenticateCipher(cipher: Cipher, title: String, enableCredentialsAlternative: Boolean): BiometricPrompt.AuthenticationResult {
-+    val promptResult = openAuthenticationPrompt(cipher, title, enableCredentialsAlternative)
++  suspend fun authenticateCipher(cipher: Cipher, title: String, enableDeviceFallback: Boolean): BiometricPrompt.AuthenticationResult {
++    val promptResult = openAuthenticationPrompt(cipher, title, enableDeviceFallback)
 +    if (promptResult.cryptoObject?.cipher == null) {
 +      throw AuthenticationException("Couldn't get cipher from authentication result")
      }
@@ -28,76 +20,8 @@ index a281b88..dc84eb4 100644
    }
 
    private suspend fun openAuthenticationPrompt(
-     cipher: Cipher,
--    title: String
-+    title: String,
-+    enableCredentialsAlternative: Boolean
-   ): BiometricPrompt.AuthenticationResult {
-     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
-       throw AuthenticationException("Biometric authentication requires Android API 23")
-@@ -41,11 +43,16 @@ class AuthenticationHelper(
-     isAuthenticating = true
-
-     try {
--      assertBiometricsSupport()
-+      if (enableCredentialsAlternative) {
-+        assertDeviceSecurity()
-+      } else {
-+        assertBiometricsSupport()
-+      }
-+
-       val fragmentActivity = getCurrentActivity() as? FragmentActivity
-         ?: throw AuthenticationException("Cannot display biometric prompt when the app is not in the foreground")
-
--      val authenticationPrompt = AuthenticationPrompt(fragmentActivity, context, title)
-+      val authenticationPrompt = AuthenticationPrompt(fragmentActivity, context, title, enableCredentialsAlternative)
-
-       return withContext(Dispatchers.Main.immediate) {
-         return@withContext authenticationPrompt.authenticate(cipher)
-@@ -56,6 +63,14 @@ class AuthenticationHelper(
-     }
-   }
-
-+  fun assertDeviceSecurity() {
-+    val manager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
-+    val isSecure = manager.isDeviceSecure
-+    if (!isSecure) {
-+      throw AuthenticationException("No authentication method available")
-+    }
-+  }
-+
-   fun assertBiometricsSupport() {
-     val biometricManager = BiometricManager.from(context)
-     @SuppressLint("SwitchIntDef") // BiometricManager.BIOMETRIC_SUCCESS shouldn't do anything
-diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/AuthenticationPrompt.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/AuthenticationPrompt.kt
-index e5729cc..0fd4242 100644
---- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/AuthenticationPrompt.kt
-+++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/AuthenticationPrompt.kt
-@@ -1,5 +1,7 @@
- package expo.modules.securestore
-
-+import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
-+import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
- import android.content.Context
- import androidx.biometric.BiometricPrompt
- import androidx.biometric.BiometricPrompt.PromptInfo
-@@ -11,11 +13,12 @@ import kotlin.coroutines.resume
- import kotlin.coroutines.resumeWithException
- import kotlin.coroutines.suspendCoroutine
-
--class AuthenticationPrompt(private val currentActivity: FragmentActivity, context: Context, title: String) {
-+class AuthenticationPrompt(private val currentActivity: FragmentActivity, context: Context, title: String, enableCredentialsAlternative: Boolean) {
-+  private var authType: Int = if (enableCredentialsAlternative) BIOMETRIC_STRONG or DEVICE_CREDENTIAL else BIOMETRIC_STRONG
-   private var executor: Executor = ContextCompat.getMainExecutor(context)
-   private var promptInfo = PromptInfo.Builder()
-     .setTitle(title)
--    .setNegativeButtonText(context.getString(android.R.string.cancel))
-+    .setAllowedAuthenticators(authType)
-     .build()
-
-   suspend fun authenticate(cipher: Cipher): BiometricPrompt.AuthenticationResult? =
 diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt
-index 0fef884..822caa5 100644
+index 3f21552..866d47d 100644
 --- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt
 +++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt
 @@ -36,23 +36,23 @@ open class SecureStoreModule : Module() {
@@ -128,7 +52,7 @@ index 0fef884..822caa5 100644
        }
      }
 
-@@ -85,7 +85,7 @@ open class SecureStoreModule : Module() {
+@@ -94,7 +94,7 @@ open class SecureStoreModule : Module() {
      }
    }
 
@@ -137,7 +61,7 @@ index 0fef884..822caa5 100644
      // We use a SecureStore-specific shared preferences file, which lets us do things like enumerate
      // its entries or clear all of them
      val prefs: SharedPreferences = getSharedPreferences()
-@@ -95,10 +95,10 @@ open class SecureStoreModule : Module() {
+@@ -104,10 +104,10 @@ open class SecureStoreModule : Module() {
      } else if (prefs.contains(key)) { // For backwards-compatibility try to read using the old key format
        return readJSONEncodedItem(key, prefs, options)
      }
@@ -150,7 +74,7 @@ index 0fef884..822caa5 100644
      val keychainAwareKey = createKeychainAwareKey(key, options.keychainService)
 
      val legacyEncryptedItemString = prefs.getString(key, null)
-@@ -117,7 +117,7 @@ open class SecureStoreModule : Module() {
+@@ -126,7 +126,7 @@ open class SecureStoreModule : Module() {
        ""
      }
 
@@ -159,28 +83,23 @@ index 0fef884..822caa5 100644
 
      val encryptedItem: JSONObject = try {
        JSONObject(encryptedItemString)
-@@ -140,22 +140,26 @@ open class SecureStoreModule : Module() {
+@@ -149,13 +149,13 @@ open class SecureStoreModule : Module() {
                  "This situation occurs when the app is reinstalled. The value will be removed to avoid future errors. Returning null"
              )
              deleteItemImpl(key, options)
 -            return null
 +            return SecureStoreFeedback(null)
            }
-+
            return mAESEncryptor.decryptItem(key, encryptedItem, secretKeyEntry, options, authenticationHelper)
          }
-+
          HybridAESEncryptor.NAME -> {
            val privateKeyEntry = getKeyEntryCompat(PrivateKeyEntry::class.java, hybridAESEncryptor, options, requireAuthentication, usesKeystoreSuffix)
 -            ?: return null
 +            ?: return SecureStoreFeedback(null)
-+
            return hybridAESEncryptor.decryptItem(key, encryptedItem, privateKeyEntry, options, authenticationHelper)
          }
-+
          else -> {
-           throw DecryptException("The item for key $key in SecureStore has an unknown encoding scheme $scheme)", key, options.keychainService)
-         }
+@@ -164,7 +164,7 @@ open class SecureStoreModule : Module() {
        }
      } catch (e: KeyPermanentlyInvalidatedException) {
        Log.w(TAG, "The requested key has been permanently invalidated. Returning null")
@@ -189,7 +108,7 @@ index 0fef884..822caa5 100644
      } catch (e: BadPaddingException) {
        // The key from the KeyStore is unable to decode the entry. This is because a new key was generated, but the entries are encrypted using the old one.
        // This usually means that the user has reinstalled the app. We can safely remove the old value and return null as it's impossible to decrypt it.
-@@ -165,7 +169,7 @@ open class SecureStoreModule : Module() {
+@@ -174,7 +174,7 @@ open class SecureStoreModule : Module() {
            "The entry in shared preferences is out of sync with the keystore. It will be removed, returning null."
        )
        deleteItemImpl(key, options)
@@ -198,7 +117,7 @@ index 0fef884..822caa5 100644
      } catch (e: GeneralSecurityException) {
        throw (DecryptException(e.message, key, options.keychainService, e))
      } catch (e: CodedException) {
-@@ -175,7 +179,7 @@ open class SecureStoreModule : Module() {
+@@ -184,7 +184,7 @@ open class SecureStoreModule : Module() {
      }
    }
 
@@ -207,34 +126,26 @@ index 0fef884..822caa5 100644
      val keychainAwareKey = createKeychainAwareKey(key, options.keychainService)
      val prefs: SharedPreferences = getSharedPreferences()
 
-@@ -184,7 +188,11 @@ open class SecureStoreModule : Module() {
+@@ -193,7 +193,7 @@ open class SecureStoreModule : Module() {
        if (!success) {
          throw WriteException("Could not write a null value to SecureStore", key, options.keychainService)
        }
 -      return
 +      return SecureStoreAuthType.NONE
-+    }
-+
-+    if (prefs.contains(keychainAwareKey) && options.failOnDuplicate) {
-+      throw WriteException("Key already exists", key, options.keychainService)
      }
 
      try {
-@@ -200,8 +208,11 @@ open class SecureStoreModule : Module() {
-        use in the encrypted JSON item so that we know how to decode and decrypt it when reading
+@@ -210,7 +210,8 @@ open class SecureStoreModule : Module() {
         back a value.
         */
--      val secretKeyEntry: SecretKeyEntry = getOrCreateKeyEntry(SecretKeyEntry::class.java, mAESEncryptor, options, options.requireAuthentication)
--      val encryptedItem = mAESEncryptor.createEncryptedItem(value, secretKeyEntry, options.requireAuthentication, options.authenticationPrompt, authenticationHelper)
-+      val secretKeyEntry: SecretKeyEntry = getOrCreateKeyEntry(
-+        SecretKeyEntry::class.java, mAESEncryptor, options, options.requireAuthentication
-+      )
-+      val encryptResult = mAESEncryptor.createEncryptedItem(value, secretKeyEntry, options.requireAuthentication, options.authenticationPrompt, authenticationHelper, options.enableCredentialsAlternative)
+       val secretKeyEntry: SecretKeyEntry = getOrCreateKeyEntry(SecretKeyEntry::class.java, mAESEncryptor, options, options.requireAuthentication)
+-      val encryptedItem = mAESEncryptor.createEncryptedItem(value, secretKeyEntry, options.requireAuthentication, options.authenticationPrompt, authenticationHelper, options.enableDeviceFallback)
++      val encryptResult = mAESEncryptor.createEncryptedItem(value, secretKeyEntry, options.requireAuthentication, options.authenticationPrompt, authenticationHelper, options.enableDeviceFallback)
 +      val encryptedItem = encryptResult.value
        encryptedItem.put(SCHEME_PROPERTY, AESEncryptor.NAME)
        saveEncryptedItem(encryptedItem, prefs, keychainAwareKey, options.requireAuthentication, options.keychainService)
 
-@@ -209,6 +220,9 @@ open class SecureStoreModule : Module() {
+@@ -218,6 +219,9 @@ open class SecureStoreModule : Module() {
        if (prefs.contains(key)) {
          prefs.edit().remove(key).apply()
        }
@@ -244,21 +155,8 @@ index 0fef884..822caa5 100644
      } catch (e: KeyPermanentlyInvalidatedException) {
        if (!keyIsInvalidated) {
          Log.w(TAG, "Key has been invalidated, retrying with the key deleted")
-@@ -343,7 +357,11 @@ open class SecureStoreModule : Module() {
-     return getKeyEntry(keyStoreEntryClass, encryptor, options, requireAuthentication) ?: run {
-       // Android won't allow us to generate the keys if the device doesn't support biometrics or no biometrics are enrolled
-       if (requireAuthentication) {
--        authenticationHelper.assertBiometricsSupport()
-+        if (options.enableCredentialsAlternative) {
-+          authenticationHelper.assertDeviceSecurity()
-+        } else {
-+          authenticationHelper.assertBiometricsSupport()
-+        }
-       }
-       encryptor.initializeKeyStoreEntry(keyStore, options)
-     }
 diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt
-index 79a600f..ad78439 100644
+index 0455fd6..d38650c 100644
 --- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt
 +++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt
 @@ -1,5 +1,6 @@
@@ -268,14 +166,9 @@ index 79a600f..ad78439 100644
  import expo.modules.kotlin.records.Field
  import expo.modules.kotlin.records.Record
  import java.io.Serializable
-@@ -8,5 +9,39 @@ class SecureStoreOptions(
-   // Prompt can't be an empty string
-   @Field var authenticationPrompt: String = " ",
-   @Field var keychainService: String = SecureStoreModule.DEFAULT_KEYSTORE_ALIAS,
--  @Field var requireAuthentication: Boolean = false
-+  @Field var requireAuthentication: Boolean = false,
-+  @Field var failOnDuplicate: Boolean = false,
-+  @Field var enableCredentialsAlternative: Boolean = false
+@@ -11,3 +12,35 @@ class SecureStoreOptions(
+   @Field var requireAuthentication: Boolean = false,
+   @Field var enableDeviceFallback: Boolean = false
  ) : Record, Serializable
 +
 +enum class SecureStoreAuthType(index: Int) {
@@ -309,20 +202,14 @@ index 79a600f..ad78439 100644
 +  /** Used to return easily convertible values to JS code */
 +  @Field var values: Pair<T, Int> = Pair(value, authType.ordinal)
 +}
-\ No newline at end of file
 diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/AESEncryptor.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/AESEncryptor.kt
-index 3a12dc9..d5b797b 100644
+index 1cd187f..44c84c3 100644
 --- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/AESEncryptor.kt
 +++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/AESEncryptor.kt
-@@ -1,11 +1,14 @@
- package expo.modules.securestore.encryptors
-
--import android.annotation.TargetApi
-+import android.os.Build
- import android.security.keystore.KeyGenParameterSpec
+@@ -5,8 +5,10 @@ import android.security.keystore.KeyGenParameterSpec
  import android.security.keystore.KeyProperties
  import android.util.Base64
-+import androidx.annotation.RequiresApi
+ import androidx.annotation.RequiresApi
 +import androidx.biometric.BiometricPrompt
  import expo.modules.securestore.AuthenticationHelper
  import expo.modules.securestore.DecryptException
@@ -330,64 +217,34 @@ index 3a12dc9..d5b797b 100644
  import expo.modules.securestore.SecureStoreModule
  import expo.modules.securestore.SecureStoreOptions
  import org.json.JSONException
-@@ -50,17 +53,24 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
-     return "${getKeyStoreAlias(options)}:$suffix"
-   }
-
--  @TargetApi(23)
-+  @RequiresApi(Build.VERSION_CODES.R)
-   @Throws(GeneralSecurityException::class)
--  override fun initializeKeyStoreEntry(keyStore: KeyStore, options: SecureStoreOptions): KeyStore.SecretKeyEntry {
-+  override fun initializeKeyStoreEntry(
-+    keyStore: KeyStore, options: SecureStoreOptions
-+  ): KeyStore.SecretKeyEntry {
-     val extendedKeystoreAlias = getExtendedKeyStoreAlias(options, options.requireAuthentication)
-     val keyPurposes = KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
-
-+    val authType =
-+      if (options.enableCredentialsAlternative) KeyProperties.AUTH_BIOMETRIC_STRONG or KeyProperties.AUTH_DEVICE_CREDENTIAL
-+      else KeyProperties.AUTH_BIOMETRIC_STRONG
-+
-     val algorithmSpec: AlgorithmParameterSpec = KeyGenParameterSpec.Builder(extendedKeystoreAlias, keyPurposes)
-       .setKeySize(AES_KEY_SIZE_BITS)
-       .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
-       .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
-       .setUserAuthenticationRequired(options.requireAuthentication)
-+      .setUserAuthenticationParameters(0, authType)
-       .build()
-
-     val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, keyStore.provider)
-@@ -78,16 +88,25 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
-     keyStoreEntry: KeyStore.SecretKeyEntry,
-     requireAuthentication: Boolean,
+@@ -86,15 +88,23 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
      authenticationPrompt: String,
--    authenticationHelper: AuthenticationHelper
+     authenticationHelper: AuthenticationHelper,
+     enableDeviceFallback: Boolean,
 -  ): JSONObject {
-+    authenticationHelper: AuthenticationHelper,
-+    enableCredentialsAlternative: Boolean,
 +  ): SecureStoreFeedback<JSONObject> {
      val secretKey = keyStoreEntry.secretKey
      val cipher = Cipher.getInstance(AES_CIPHER)
      cipher.init(Cipher.ENCRYPT_MODE, secretKey)
 
      val gcmSpec = cipher.parameters.getParameterSpec(GCMParameterSpec::class.java)
--    val authenticatedCipher = authenticationHelper.authenticateCipher(cipher, requireAuthentication, authenticationPrompt)
+-    val authenticatedCipher = authenticationHelper.authenticateCipher(cipher, requireAuthentication, authenticationPrompt, enableDeviceFallback)
 +    var promptResult: BiometricPrompt.AuthenticationResult? = null
 +    val authenticatedCipher: Cipher
-+
+
+-    return createEncryptedItemWithCipher(plaintextValue, authenticatedCipher, gcmSpec)
 +    if (requireAuthentication) {
-+      promptResult = authenticationHelper.authenticateCipher(cipher, authenticationPrompt, enableCredentialsAlternative)
++      promptResult = authenticationHelper.authenticateCipher(cipher, authenticationPrompt, enableDeviceFallback)
 +      authenticatedCipher = promptResult.cryptoObject?.cipher ?: cipher
 +    } else {
 +      authenticatedCipher = cipher
 +    }
-
--    return createEncryptedItemWithCipher(plaintextValue, authenticatedCipher, gcmSpec)
++
 +    return SecureStoreFeedback(createEncryptedItemWithCipher(plaintextValue, authenticatedCipher, gcmSpec), promptResult)
    }
 
    internal fun createEncryptedItemWithCipher(
-@@ -114,7 +133,7 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
+@@ -121,7 +131,7 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
      keyStoreEntry: KeyStore.SecretKeyEntry,
      options: SecureStoreOptions,
      authenticationHelper: AuthenticationHelper
@@ -396,18 +253,18 @@ index 3a12dc9..d5b797b 100644
      val ciphertext = encryptedItem.getString(CIPHERTEXT_PROPERTY)
      val ivString = encryptedItem.getString(IV_PROPERTY)
      val authenticationTagLength = encryptedItem.getInt(GCM_AUTHENTICATION_TAG_LENGTH_PROPERTY)
-@@ -128,8 +147,18 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
+@@ -135,8 +145,18 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
        throw DecryptException("Authentication tag length must be at least $MIN_GCM_AUTHENTICATION_TAG_LENGTH bits long", key, options.keychainService)
      }
      cipher.init(Cipher.DECRYPT_MODE, keyStoreEntry.secretKey, gcmSpec)
--    val unlockedCipher = authenticationHelper.authenticateCipher(cipher, requiresAuthentication, options.authenticationPrompt)
+-    val unlockedCipher = authenticationHelper.authenticateCipher(cipher, requiresAuthentication, options.authenticationPrompt, options.enableDeviceFallback)
 -    return String(unlockedCipher.doFinal(ciphertextBytes), StandardCharsets.UTF_8)
 +
 +    var promptResult: BiometricPrompt.AuthenticationResult? = null
 +    val unlockedCipher: Cipher
 +
 +    if (requiresAuthentication) {
-+      promptResult = authenticationHelper.authenticateCipher(cipher, options.authenticationPrompt, options.enableCredentialsAlternative)
++      promptResult = authenticationHelper.authenticateCipher(cipher, options.authenticationPrompt, options.enableDeviceFallback)
 +      unlockedCipher = promptResult.cryptoObject?.cipher ?: cipher
 +    } else {
 +      unlockedCipher = cipher
@@ -418,7 +275,7 @@ index 3a12dc9..d5b797b 100644
 
    companion object {
 diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/HybridAESEncryptor.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/HybridAESEncryptor.kt
-index fb42599..0055b62 100644
+index 5f8bbfd..0b3ae0a 100644
 --- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/HybridAESEncryptor.kt
 +++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/HybridAESEncryptor.kt
 @@ -7,6 +7,7 @@ import android.util.Base64
@@ -429,31 +286,26 @@ index fb42599..0055b62 100644
  import expo.modules.securestore.SecureStoreModule
  import expo.modules.securestore.SecureStoreOptions
  import org.json.JSONException
-@@ -69,8 +70,9 @@ class HybridAESEncryptor(private var mContext: Context, private val mAESEncrypto
-     keyStoreEntry: KeyStore.PrivateKeyEntry,
-     requireAuthentication: Boolean,
+@@ -71,7 +72,7 @@ class HybridAESEncryptor(private var mContext: Context, private val mAESEncrypto
      authenticationPrompt: String,
--    authenticationHelper: AuthenticationHelper
+     authenticationHelper: AuthenticationHelper,
+     enableDeviceFallback: Boolean,
 -  ): JSONObject {
-+    authenticationHelper: AuthenticationHelper,
-+    enableCredentialsAlternative: Boolean,
 +  ): SecureStoreFeedback<JSONObject> {
      // This should never be called after we dropped Android SDK 22 support.
      throw EncryptException(
        "HybridAESEncryption should not be used on Android SDK >= 23. This shouldn't happen. " +
-@@ -86,8 +88,8 @@ class HybridAESEncryptor(private var mContext: Context, private val mAESEncrypto
-     encryptedItem: JSONObject,
+@@ -88,7 +89,7 @@ class HybridAESEncryptor(private var mContext: Context, private val mAESEncrypto
      keyStoreEntry: KeyStore.PrivateKeyEntry,
      options: SecureStoreOptions,
--    authenticationHelper: AuthenticationHelper
+     authenticationHelper: AuthenticationHelper
 -  ): String {
-+    authenticationHelper: AuthenticationHelper,
 +  ): SecureStoreFeedback<String> {
      // Decrypt the encrypted symmetric key
      val encryptedSecretKeyString = encryptedItem.getString(ENCRYPTED_SECRET_KEY_PROPERTY)
      val encryptedSecretKeyBytes = Base64.decode(encryptedSecretKeyString, Base64.DEFAULT)
 diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/KeyBasedEncryptor.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/KeyBasedEncryptor.kt
-index e493467..c880ee0 100644
+index 39459ff..66ba986 100644
 --- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/KeyBasedEncryptor.kt
 +++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/KeyBasedEncryptor.kt
 @@ -1,6 +1,7 @@
@@ -464,29 +316,24 @@ index e493467..c880ee0 100644
  import expo.modules.securestore.SecureStoreOptions
  import org.json.JSONException
  import org.json.JSONObject
-@@ -25,8 +26,9 @@ interface KeyBasedEncryptor<E : KeyStore.Entry> {
-     keyStoreEntry: E,
-     requireAuthentication: Boolean,
+@@ -27,7 +28,7 @@ interface KeyBasedEncryptor<E : KeyStore.Entry> {
      authenticationPrompt: String,
--    authenticationHelper: AuthenticationHelper
+     authenticationHelper: AuthenticationHelper,
+     enableDeviceFallback: Boolean,
 -  ): JSONObject
-+    authenticationHelper: AuthenticationHelper,
-+    enableCredentialsAlternative: Boolean,
 +  ): SecureStoreFeedback<JSONObject>
 
    @Throws(GeneralSecurityException::class, JSONException::class)
    suspend fun decryptItem(
-@@ -34,6 +36,6 @@ interface KeyBasedEncryptor<E : KeyStore.Entry> {
-     encryptedItem: JSONObject,
+@@ -36,5 +37,5 @@ interface KeyBasedEncryptor<E : KeyStore.Entry> {
      keyStoreEntry: E,
      options: SecureStoreOptions,
--    authenticationHelper: AuthenticationHelper
+     authenticationHelper: AuthenticationHelper
 -  ): String
-+    authenticationHelper: AuthenticationHelper,
 +  ): SecureStoreFeedback<String>
  }
 diff --git a/node_modules/expo-secure-store/build/SecureStore.d.ts b/node_modules/expo-secure-store/build/SecureStore.d.ts
-index d5cd157..ca1cd76 100644
+index 835a4d4..77895d0 100644
 --- a/node_modules/expo-secure-store/build/SecureStore.d.ts
 +++ b/node_modules/expo-secure-store/build/SecureStore.d.ts
 @@ -1,4 +1,53 @@
@@ -543,53 +390,7 @@ index d5cd157..ca1cd76 100644
  /**
   * The data in the keychain item cannot be accessed after a restart until the device has been
   * unlocked once by the user. This may be useful if you need to access the item when the phone
-@@ -78,6 +127,45 @@ export type SecureStoreOptions = {
-      * @platform ios
-      */
-     accessGroup?: string;
-+
-+    /**
-+     * If value already exists, throw an error
-+     *
-+     * On Android it throws an error before the prompt for authentication.
-+     * On iOS it throws an error after authentication.
-+     */
-+    failOnDuplicate?: boolean;
-+    /**
-+     * On iOS, the system sometimes skips authentication if it has been done recently.
-+     * This also applies to actions such as unlocking the screen.
-+     * Setting this flag to true will ensure that authentication is always required before accessing the store.
-+     *
-+     * Use this wisely, when the user did not authenticated recently, setting this to true will lead to prompt
-+     * being displayed twice when accessing the store value.
-+     * The best use-case for this flag is when testing the behavior on iOS simulator,
-+     * since the auth there may not be prompted at all without this flag.
-+     *
-+     * @warning This flag works only for async version of the save/read SecureStore methods.
-+     * @platform ios
-+     */
-+    alwaysAskForAuth?: boolean;
-+    /**
-+     * On iOS, the system does not ask for auth when saving the value to the SecureStore.
-+     * The Android however, displays the authentication prompt when saving the value.
-+     * To keep the behavior on every platform similar as much as possible,
-+     * setting this flag to true will ensure that authentication is required when saving a value to the store.
-+     *
-+     * @warning This flag works only for async version of the save/read SecureStore methods.
-+     * @platform ios
-+     */
-+    askForAuthOnSave?: boolean;
-+    /**
-+     * On Android, there is often no fallback option if biometrics fail.
-+     * Therefore, we may want to allow the user to authenticate using credentials if they prefer to.
-+     *
-+     * @platform android
-+     */
-+    enableCredentialsAlternative?: boolean;
- };
- /**
-  * Returns whether the SecureStore API is enabled on the current device. This does not check the app
-@@ -109,7 +197,7 @@ export declare function deleteItemAsync(key: string, options?: SecureStoreOption
+@@ -119,7 +168,7 @@ export declare function deleteItemAsync(key: string, options?: SecureStoreOption
   * > After a key has been invalidated, it becomes impossible to read its value.
   * > This only applies to values stored with `requireAuthentication` set to `true`.
   */
@@ -598,7 +399,7 @@ index d5cd157..ca1cd76 100644
  /**
   * Stores a key–value pair.
   *
-@@ -119,7 +207,7 @@ export declare function getItemAsync(key: string, options?: SecureStoreOptions):
+@@ -129,7 +178,7 @@ export declare function getItemAsync(key: string, options?: SecureStoreOptions):
   *
   * @return A promise that rejects if value cannot be stored on the device.
   */
@@ -607,7 +408,7 @@ index d5cd157..ca1cd76 100644
  /**
   * Stores a key–value pair synchronously.
   * > **Note:** This function blocks the JavaScript thread, so the application may not be interactive when the `requireAuthentication` option is set to `true` until the user authenticates.
-@@ -129,7 +217,7 @@ export declare function setItemAsync(key: string, value: string, options?: Secur
+@@ -139,7 +188,7 @@ export declare function setItemAsync(key: string, value: string, options?: Secur
   * @param options An [`SecureStoreOptions`](#securestoreoptions) object.
   *
   */
@@ -616,7 +417,7 @@ index d5cd157..ca1cd76 100644
  /**
   * Synchronously reads the stored value associated with the provided key.
   * > **Note:** This function blocks the JavaScript thread, so the application may not be interactive when reading a value with `requireAuthentication`
-@@ -140,7 +228,7 @@ export declare function setItem(key: string, value: string, options?: SecureStor
+@@ -150,7 +199,7 @@ export declare function setItem(key: string, value: string, options?: SecureStor
   * @return Previously stored value. It resolves with `null` if there is no entry
   * for the given key or if the key has been invalidated.
   */
@@ -626,7 +427,7 @@ index d5cd157..ca1cd76 100644
   * Checks if the value can be saved with `requireAuthentication` option enabled.
   * @return `true` if the device supports biometric authentication and the enrolled method is sufficiently secure. Otherwise, returns `false`. Always returns false on tvOS.
 diff --git a/node_modules/expo-secure-store/build/SecureStore.js b/node_modules/expo-secure-store/build/SecureStore.js
-index 4d87b38..4399e5e 100644
+index e11bd94..2371b88 100644
 --- a/node_modules/expo-secure-store/build/SecureStore.js
 +++ b/node_modules/expo-secure-store/build/SecureStore.js
 @@ -1,6 +1,52 @@
@@ -692,65 +493,16 @@ index 4d87b38..4399e5e 100644
  /**
   * Stores a key–value pair synchronously.
 diff --git a/node_modules/expo-secure-store/ios/SecureStoreModule.swift b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
-index 439b08d..98dab3e 100644
+index 1268979..e4c3f2b 100644
 --- a/node_modules/expo-secure-store/ios/SecureStoreModule.swift
 +++ b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
-@@ -4,6 +4,42 @@ import LocalAuthentication
- #endif
- import Security
-
-+@available(iOS 11.2, *)
-+public enum AuthType: Int, @unchecked Sendable {
-+  /// The device does not support biometry.
-+  case none = 0
-+
-+  /// The device supports device credentials
-+  case credentials = 1
-+
-+  /// Generic type, not specified whether it was a faceID or touchID
-+  case biometrics = 2
-+
-+  /// The device supports Face ID.
-+  case faceID = 3
-+
-+  /// The device supports Touch ID.
-+  case touchID = 4
-+
-+  /// The device supports Optic ID
-+  case opticID = 5
-+}
-+
-+struct SecureStoreFeedback<T> {
-+  var value: T
-+  var authType: Int = AuthType.none.rawValue
-+  var values: Array<Any> {
-+    get { return [value, authType] }
-+  }
-+}
-+
-+struct SecureStoreRuntimeError: LocalizedError {
-+  let errorDescription: String?
-+  init(_ description: String) {
-+    self.errorDescription = description
-+  }
-+}
-+
- public final class SecureStoreModule: Module {
-   public func definition() -> ModuleDefinition {
-     Name("ExpoSecureStore")
-@@ -18,57 +54,137 @@ public final class SecureStoreModule: Module {
+@@ -18,28 +18,40 @@ public final class SecureStoreModule: Module {
        "WHEN_UNLOCKED_THIS_DEVICE_ONLY": SecureStoreAccessible.whenUnlockedThisDeviceOnly.rawValue
      ])
 
 -    AsyncFunction("getValueWithKeyAsync") { (key: String, options: SecureStoreOptions) -> String? in
 -      return try get(with: key, options: options)
 +    AsyncFunction("getValueWithKeyAsync") { (key: String, options: SecureStoreOptions) in
-+      if options.requireAuthentication && options.alwaysAskForAuth {
-+        try await askForAuth(
-+          prompt: options.authenticationPrompt
-+          ?? "Authentication required"
-+        )
-+      }
 +      return getSecureStoreFeedback(value: try get(with: key, options: options)).values
      }
 
@@ -767,15 +519,6 @@ index 439b08d..98dab3e 100644
        }
 
 -      return try set(value: value, with: key, options: options)
-+      if options.requireAuthentication && (
-+        options.alwaysAskForAuth || options.askForAuthOnSave
-+      ) {
-+        try await askForAuth(
-+          prompt: options.authenticationPrompt
-+          ?? "Authentication required"
-+        )
-+      }
-+
 +      let result = try set(value: value, with: key, options: options)
 +
 +      if !result {
@@ -802,84 +545,78 @@ index 439b08d..98dab3e 100644
      }
 
      AsyncFunction("deleteValueWithKeyAsync") { (key: String, options: SecureStoreOptions) in
--      let noAuthSearchDictionary = query(with: key, options: options, requireAuthentication: false)
--      let authSearchDictionary = query(with: key, options: options, requireAuthentication: true)
--      let legacySearchDictionary = query(with: key, options: options)
-+      let noAuthSearchDictionary = try query(with: key, options: options, requireAuthentication: false)
-+      let authSearchDictionary = try query(with: key, options: options, requireAuthentication: true)
-+      let legacySearchDictionary = try query(with: key, options: options)
-
-       SecItemDelete(legacySearchDictionary as CFDictionary)
-       SecItemDelete(authSearchDictionary as CFDictionary)
-       SecItemDelete(noAuthSearchDictionary as CFDictionary)
+@@ -53,18 +65,7 @@ public final class SecureStoreModule: Module {
      }
 
--    Function("canUseBiometricAuthentication") {() -> Bool in
+     Function("canUseBiometricAuthentication") {() -> Bool in
 -      #if os(tvOS)
 -      return false
 -      #else
 -      let context = LAContext()
 -      var error: NSError?
 -      let isBiometricsSupported: Bool = context.canEvaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, error: &error)
-+    Function("canUseBiometricAuthentication") { () -> Bool in
-+      return areBiometricsEnabled()
-+    }
-+  }
-
+-
 -      if error != nil {
 -        return false
 -      }
 -      return isBiometricsSupported
 -      #endif
-+  private func askForAuth(prompt: String) async throws {
-+    let context = LAContext()
-+    context.localizedReason = prompt
-+
-+    guard context.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil) else {
-+      throw SecureStoreRuntimeError("No authentication method available")
++      return areBiometricsEnabled()
      }
-+
-+    let result = try await context.evaluatePolicy(
-+      LAPolicy.deviceOwnerAuthentication,
-+      localizedReason: context.localizedReason
-+    )
-+
-+    if !result {
-+      throw SecureStoreRuntimeError("Unable to authenticate")
-+    }
-+  }
-+
+
+     Function("canUseDeviceCredentialsAuthentication") { () -> Bool in
+@@ -72,6 +73,31 @@ public final class SecureStoreModule: Module {
+     }
+   }
+
 +  private func getAuthType() -> AuthType {
 +    if !areBiometricsEnabled() {return AuthType.credentials}
 +    let biometryType = LAContext().biometryType
 +
 +    switch biometryType {
-+    case .faceID: return .faceID
-+    case .touchID: return .touchID
-+    case .opticID: return .opticID // available since iOS 17
-+    case .none: fallthrough // this one continues to the next line
-+    @unknown default: return .credentials
++      case .faceID: return .faceID
++      case .touchID: return .touchID
++      case .opticID: return .opticID // available since iOS 17
++      case .none: fallthrough // this one continues to the next line
++      @unknown default: return .credentials
 +    }
 +  }
 +
 +  private func getSecureStoreFeedback<T>(value: T) -> SecureStoreFeedback<T> {
 +    return SecureStoreFeedback(value: value, authType: getAuthType().rawValue)
++  }
++
++  private func areBiometricsEnabled() -> Bool {
++    #if os(tvOS)
++      return false
++    #else
++      return LAContext().canEvaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, error: nil)
++    #endif
++  }
++
+   private func areDeviceCredentialsEnabled() -> Bool {
+     return LAContext().canEvaluatePolicy(LAPolicy.deviceOwnerAuthentication, error: nil)
+   }
+@@ -96,6 +122,29 @@ public final class SecureStoreModule: Module {
+     return nil
    }
 
--  private func get(with key: String, options: SecureStoreOptions) throws -> String? {
-+  private func getAccessControl(accessibility: CFString) throws -> SecAccessControl
-+  {
-+    guard
-+      Bundle.main.infoDictionary?["NSFaceIDUsageDescription"] as? String
-+        != nil
-+    else {
++  private func NSFaceIDUsageEntryGuard(options: SecureStoreOptions) throws {
++    if (options.enableDeviceFallback) {
++        return;
++    }
++
++    guard let _ = Bundle.main.infoDictionary?["NSFaceIDUsageDescription"] as? String else {
 +      throw MissingPlistKeyException()
 +    }
++  }
 +
++  private func getAccessOptions(options: SecureStoreOptions, accessibility: CFString) throws -> SecAccessControl {
 +    var error: Unmanaged<CFError>? = nil
-+    guard
-+      let accessOptions = SecAccessControlCreateWithFlags(kCFAllocatorDefault, accessibility, .userPresence, &error)
-+    else {
++
++    let accessControlFlag: SecAccessControlCreateFlags = options.enableDeviceFallback ? .userPresence : .biometryCurrentSet
++
++    guard let accessOptions = SecAccessControlCreateWithFlags(kCFAllocatorDefault, accessibility, accessControlFlag, &error) else {
 +      let errorCode = error.map { CFErrorGetCode($0.takeRetainedValue()) }
 +      throw SecAccessControlError(errorCode)
 +    }
@@ -887,147 +624,72 @@ index 439b08d..98dab3e 100644
 +    return accessOptions
 +  }
 +
-+  private func areBiometricsEnabled() -> Bool {
-+    #if os(tvOS)
-+    return false
-+    #else
-+    return LAContext().canEvaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, error: nil)
-+    #endif
-+  }
-+
-+  private func get(with key: String, options: SecureStoreOptions) throws -> String?
-+  {
-     guard let key = validate(for: key) else {
-       throw InvalidKeyException()
-     }
-@@ -89,7 +205,7 @@ public final class SecureStoreModule: Module {
-   }
-
    private func set(value: String, with key: String, options: SecureStoreOptions) throws -> Bool {
--    var setItemQuery = query(with: key, options: options, requireAuthentication: options.requireAuthentication)
-+    var setItemQuery = try query(with: key, options: options, requireAuthentication: options.requireAuthentication)
-
-     let valueData = value.data(using: .utf8)
-     setItemQuery[kSecValueData as String] = valueData
-@@ -98,17 +214,6 @@ public final class SecureStoreModule: Module {
+     var setItemQuery = query(with: key, options: options, requireAuthentication: options.requireAuthentication)
 
+@@ -107,21 +156,8 @@ public final class SecureStoreModule: Module {
      if !options.requireAuthentication {
        setItemQuery[kSecAttrAccessible as String] = accessibility
--    } else {
--      guard let _ = Bundle.main.infoDictionary?["NSFaceIDUsageDescription"] as? String else {
--        throw MissingPlistKeyException()
+     } else {
+-      if (!options.enableDeviceFallback) {
+-        guard let _ = Bundle.main.infoDictionary?["NSFaceIDUsageDescription"] as? String else {
+-          throw MissingPlistKeyException()
+-        }
 -      }
 -
 -      var error: Unmanaged<CFError>? = nil
--      guard let accessOptions = SecAccessControlCreateWithFlags(kCFAllocatorDefault, accessibility, .biometryCurrentSet, &error) else {
+-
+-      let accessControlFlag: SecAccessControlCreateFlags = options.enableDeviceFallback ? .userPresence : .biometryCurrentSet
+-
+-      guard let accessOptions = SecAccessControlCreateWithFlags(kCFAllocatorDefault, accessibility, accessControlFlag, &error) else {
 -        let errorCode = error.map { CFErrorGetCode($0.takeRetainedValue()) }
 -        throw SecAccessControlError(errorCode)
 -      }
 -      setItemQuery[kSecAttrAccessControl as String] = accessOptions
++      try NSFaceIDUsageEntryGuard(options: options)
++      setItemQuery[kSecAttrAccessControl as String] = try getAccessOptions(options: options, accessibility: accessibility)
      }
 
      let status = SecItemAdd(setItemQuery as CFDictionary, nil)
-@@ -116,10 +221,13 @@ public final class SecureStoreModule: Module {
-     switch status {
-     case errSecSuccess:
-       // On success we want to remove the other key alias and legacy key (if they exist) to avoid conflicts during reads
--      SecItemDelete(query(with: key, options: options) as CFDictionary)
--      SecItemDelete(query(with: key, options: options, requireAuthentication: !options.requireAuthentication) as CFDictionary)
-+      SecItemDelete(try query(with: key, options: options) as CFDictionary)
-+      SecItemDelete(try query(with: key, options: options,requireAuthentication: !options.requireAuthentication) as CFDictionary)
-       return true
-     case errSecDuplicateItem:
-+      if options.failOnDuplicate {
-+        throw SecureStoreRuntimeError("Key already exists")
-+      }
-       return try update(value: value, with: key, options: options)
-     default:
-       throw KeyChainException(status)
-@@ -127,15 +235,11 @@ public final class SecureStoreModule: Module {
-   }
-
-   private func update(value: String, with key: String, options: SecureStoreOptions) throws -> Bool {
--    var query = query(with: key, options: options, requireAuthentication: options.requireAuthentication)
-+    let query = try query(with: key, options: options, requireAuthentication: options.requireAuthentication)
-
-     let valueData = value.data(using: .utf8)
-     let updateDictionary = [kSecValueData as String: valueData]
-
--    if let authPrompt = options.authenticationPrompt {
--      query[kSecUseOperationPrompt as String] = authPrompt
--    }
--
-     let status = SecItemUpdate(query as CFDictionary, updateDictionary as CFDictionary)
-
-     if status == errSecSuccess {
-@@ -146,15 +250,11 @@ public final class SecureStoreModule: Module {
-   }
-
-   private func searchKeyChain(with key: String, options: SecureStoreOptions, requireAuthentication: Bool? = nil) throws -> Data? {
--    var query = query(with: key, options: options, requireAuthentication: requireAuthentication)
-+    var query = try query(with: key, options: options, requireAuthentication: requireAuthentication)
-
-     query[kSecMatchLimit as String] = kSecMatchLimitOne
-     query[kSecReturnData as String] = kCFBooleanTrue
-
--    if let authPrompt = options.authenticationPrompt {
--      query[kSecUseOperationPrompt as String] = authPrompt
--    }
--
-     var item: CFTypeRef?
-     let status = SecItemCopyMatching(query as CFDictionary, &item)
-
-@@ -171,21 +271,28 @@ public final class SecureStoreModule: Module {
-     }
-   }
-
--  private func query(with key: String, options: SecureStoreOptions, requireAuthentication: Bool? = nil) -> [String: Any] {
-+  private func query(with key: String, options: SecureStoreOptions, requireAuthentication: Bool? = nil) throws -> [String: Any] {
-     var service = options.keychainService ?? "app"
-     if let requireAuthentication {
-       service.append(":\(requireAuthentication ? "auth" : "no-auth")")
-     }
-
-     let encodedKey = Data(key.utf8)
-+    let accessibility = attributeWith(options: options)
-
-     var query: [String: Any] = [
-       kSecClass as String: kSecClassGenericPassword,
-       kSecAttrService as String: service,
-       kSecAttrGeneric as String: encodedKey,
--      kSecAttrAccount as String: encodedKey
-+      kSecAttrAccount as String: encodedKey,
-     ]
-
-+    if options.requireAuthentication {
-+      query[kSecAttrAccessControl as String] = try getAccessControl(
-+        accessibility: accessibility
-+      )
-+    }
-+
-     if let accessGroup = options.accessGroup {
-       query[kSecAttrAccessGroup as String] = accessGroup
-     }
 diff --git a/node_modules/expo-secure-store/ios/SecureStoreOptions.swift b/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
-index 7e3fa4d..481b1fe 100644
+index c9e843b..13e15be 100644
 --- a/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
 +++ b/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
-@@ -15,4 +15,13 @@ internal struct SecureStoreOptions: Record {
-
+@@ -19,3 +19,32 @@ internal struct SecureStoreOptions: Record {
    @Field
-   var accessGroup: String?
+   var enableDeviceFallback: Bool = false
+ }
 +
-+  @Field
-+  var failOnDuplicate: Bool = false
++@available(iOS 11.2, *)
++public enum AuthType: Int, @unchecked Sendable {
++  /// The device does not support biometry.
++  case none = 0
 +
-+  @Field
-+  var alwaysAskForAuth: Bool = false
++  /// The device supports device credentials
++  case credentials = 1
 +
-+  @Field
-+  var askForAuthOnSave: Bool = false
- }
++  /// Generic type, not specified whether it was a faceID or touchID
++  case biometrics = 2
++
++  /// The device supports Face ID.
++  case faceID = 3
++
++  /// The device supports Touch ID.
++  case touchID = 4
++
++  /// The device supports Optic ID
++  case opticID = 5
++}
++
++struct SecureStoreFeedback<T> {
++  var value: T
++  var authType: Int = AuthType.none.rawValue
++  var values: Array<Any> {
++    get { return [value, authType] }
++  }
++}
 diff --git a/node_modules/expo-secure-store/src/SecureStore.ts b/node_modules/expo-secure-store/src/SecureStore.ts
-index ee43e04..ccfd2a9 100644
+index 4f253a1..c1cc118 100644
 --- a/node_modules/expo-secure-store/src/SecureStore.ts
 +++ b/node_modules/expo-secure-store/src/SecureStore.ts
 @@ -3,6 +3,54 @@ import { byteCountOverLimit, VALUE_BYTES_LIMIT } from './byteCounter';
@@ -1085,53 +747,7 @@ index ee43e04..ccfd2a9 100644
  // @needsAudit
  /**
   * The data in the keychain item cannot be accessed after a restart until the device has been
-@@ -102,6 +150,45 @@ export type SecureStoreOptions = {
-    * @platform ios
-    */
-   accessGroup?: string;
-+
-+  /**
-+   * If value already exists, throw an error
-+   *
-+   * On Android it throws an error before the prompt for authentication.
-+   * On iOS it throws an error after authentication.
-+   */
-+  failOnDuplicate?: boolean;
-+  /**
-+   * On iOS, the system sometimes skips authentication if it has been done recently.
-+   * This also applies to actions such as unlocking the screen.
-+   * Setting this flag to true will ensure that authentication is always required before accessing the store.
-+   *
-+   * Use this wisely, when the user did not authenticated recently, setting this to true will lead to prompt
-+   * being displayed twice when accessing the store value.
-+   * The best use-case for this flag is when testing the behavior on iOS simulator,
-+   * since the auth there may not be prompted at all without this flag.
-+   *
-+   * @warning This flag works only for async version of the save/read SecureStore methods.
-+   * @platform ios
-+   */
-+  alwaysAskForAuth?: boolean;
-+  /**
-+   * On iOS, the system does not ask for auth when saving the value to the SecureStore.
-+   * The Android however, displays the authentication prompt when saving the value.
-+   * To keep the behavior on every platform similar as much as possible,
-+   * setting this flag to true will ensure that authentication is required when saving a value to the store.
-+   *
-+   * @warning This flag works only for async version of the save/read SecureStore methods.
-+   * @platform ios
-+   */
-+  askForAuthOnSave?: boolean;
-+  /**
-+   * On Android, there is often no fallback option if biometrics fail.
-+   * Therefore, we may want to allow the user to authenticate using credentials if they prefer to.
-+   *
-+   * @platform android
-+   */
-+  enableCredentialsAlternative?: boolean;
- };
-
- // @needsAudit
-@@ -151,7 +238,7 @@ export async function deleteItemAsync(
+@@ -162,7 +210,7 @@ export async function deleteItemAsync(
  export async function getItemAsync(
    key: string,
    options: SecureStoreOptions = {}
@@ -1140,7 +756,7 @@ index ee43e04..ccfd2a9 100644
    ensureValidKey(key);
    return await ExpoSecureStore.getValueWithKeyAsync(key, options);
  }
-@@ -170,15 +257,15 @@ export async function setItemAsync(
+@@ -181,7 +229,7 @@ export async function setItemAsync(
    key: string,
    value: string,
    options: SecureStoreOptions = {}
@@ -1149,8 +765,7 @@ index ee43e04..ccfd2a9 100644
    ensureValidKey(key);
    if (!isValidValue(value)) {
      throw new Error(
--      `Invalid value provided to SecureStore. Values must be strings; consider JSON-encoding your values if they are serializable.`
-+      `Invalid value provided to SecureStore. Values must be strings; consider JSON-encoding your values if they are serializable.`,
+@@ -189,7 +237,7 @@ export async function setItemAsync(
      );
    }
 
@@ -1159,7 +774,7 @@ index ee43e04..ccfd2a9 100644
  }
 
  /**
-@@ -190,7 +277,7 @@ export async function setItemAsync(
+@@ -201,7 +249,7 @@ export async function setItemAsync(
   * @param options An [`SecureStoreOptions`](#securestoreoptions) object.
   *
   */
@@ -1168,7 +783,7 @@ index ee43e04..ccfd2a9 100644
    ensureValidKey(key);
    if (!isValidValue(value)) {
      throw new Error(
-@@ -211,7 +298,7 @@ export function setItem(key: string, value: string, options: SecureStoreOptions
+@@ -222,7 +270,7 @@ export function setItem(key: string, value: string, options: SecureStoreOptions
   * @return Previously stored value. It resolves with `null` if there is no entry
   * for the given key or if the key has been invalidated.
   */
diff --git a/patches/expo-secure-store/expo-secure-store+14.2.4+003+force-authentication-on-save.patch b/patches/expo-secure-store/expo-secure-store+14.2.4+003+force-authentication-on-save.patch
new file mode 100644
index 000000000000..bc17960405a2
--- /dev/null
+++ b/patches/expo-secure-store/expo-secure-store+14.2.4+003+force-authentication-on-save.patch
@@ -0,0 +1,129 @@
+diff --git a/node_modules/expo-secure-store/build/SecureStore.d.ts b/node_modules/expo-secure-store/build/SecureStore.d.ts
+index 77895d0..2bb1b8f 100644
+--- a/node_modules/expo-secure-store/build/SecureStore.d.ts
++++ b/node_modules/expo-secure-store/build/SecureStore.d.ts
+@@ -137,6 +137,20 @@ export type SecureStoreOptions = {
+      * @platform ios
+      */
+     enableDeviceFallback?: boolean;
++    /**
++     * On iOS, the system does not ask for auth when saving the value to the SecureStore.
++     * The Android however, displays the authentication prompt when saving the value.
++     * To keep the behavior on every platform similar as much as possible,
++     * setting this flag to true will ensure that authentication is required when saving a value to the store.
++     *
++     * @warning: This flag only works for the asynchronous version of the SecureStore save method.
++     * It should only be considered an improvement to the user experience; it does not prevent the user
++     * from saving the value without authenticating using other methods (e.g. by directly modifying the keychain).
++     * For it to take effect, the 'requireAuthentication' flag must be set to true.
++     * @default false
++     * @platform ios
++     */
++    forceAuthenticationOnSave?: boolean;
+ };
+ /**
+  * Returns whether the SecureStore API is enabled on the current device. This does not check the app
+diff --git a/node_modules/expo-secure-store/ios/SecureStoreModule.swift b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
+index e4c3f2b..9848dc1 100644
+--- a/node_modules/expo-secure-store/ios/SecureStoreModule.swift
++++ b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
+@@ -31,6 +31,10 @@ public final class SecureStoreModule: Module {
+         throw InvalidKeyException()
+       }
+ 
++      if options.requireAuthentication && options.forceAuthenticationOnSave {
++        try await triggerPolicy(options: options)
++      }
++
+       let result = try set(value: value, with: key, options: options)
+ 
+       if !result {
+@@ -73,6 +77,32 @@ public final class SecureStoreModule: Module {
+     }
+   }
+ 
++  @MainActor
++  private func triggerPolicy(options: SecureStoreOptions) async throws {
++    let isPolicyAvailable = options.enableDeviceFallback ? areDeviceCredentialsEnabled() : areBiometricsEnabled()
++
++    guard isPolicyAvailable else {
++      throw SecureStoreRuntimeError("No authentication method available")
++    }
++
++    let localAuthPolicy: LAPolicy = options.enableDeviceFallback ? .deviceOwnerAuthentication : .deviceOwnerAuthenticationWithBiometrics
++    let localizedReason: String = options.authenticationPrompt ?? "Authentication required"
++
++    let success: Bool = try await withCheckedThrowingContinuation { continuation in
++      LAContext().evaluatePolicy(localAuthPolicy, localizedReason: localizedReason) { success, error in
++        if let error = error {
++          continuation.resume(throwing: error)
++        } else {
++          continuation.resume(returning: success)
++        }
++      }
++    }
++    
++    guard success else {
++      throw SecureStoreRuntimeError("Unable to authenticate")
++    }
++  }
++
+   private func getAuthType() -> AuthType {
+     if !areBiometricsEnabled() {return AuthType.credentials}
+     let biometryType = LAContext().biometryType
+@@ -269,3 +299,4 @@ public final class SecureStoreModule: Module {
+     return key
+   }
+ }
++
+diff --git a/node_modules/expo-secure-store/ios/SecureStoreOptions.swift b/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
+index 13e15be..504c36b 100644
+--- a/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
++++ b/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
+@@ -18,6 +18,9 @@ internal struct SecureStoreOptions: Record {
+ 
+   @Field
+   var enableDeviceFallback: Bool = false
++
++  @Field
++  var forceAuthenticationOnSave: Bool = false
+ }
+ 
+ @available(iOS 11.2, *)
+@@ -48,3 +51,10 @@ struct SecureStoreFeedback<T> {
+     get { return [value, authType] }
+   }
+ }
++
++struct SecureStoreRuntimeError: LocalizedError {
++  let errorDescription: String?
++  init(_ description: String) {
++    self.errorDescription = description
++  }
++}
+diff --git a/node_modules/expo-secure-store/src/SecureStore.ts b/node_modules/expo-secure-store/src/SecureStore.ts
+index c1cc118..1f2ed59 100644
+--- a/node_modules/expo-secure-store/src/SecureStore.ts
++++ b/node_modules/expo-secure-store/src/SecureStore.ts
+@@ -161,6 +161,21 @@ export type SecureStoreOptions = {
+    * @platform ios
+    */
+   enableDeviceFallback?: boolean;
++
++  /**
++   * On iOS, the system does not ask for auth when saving the value to the SecureStore.
++   * The Android however, displays the authentication prompt when saving the value.
++   * To keep the behavior on every platform similar as much as possible,
++   * setting this flag to true will ensure that authentication is required when saving a value to the store.
++   *
++   * @warning: This flag only works for the asynchronous version of the SecureStore save method.
++   * It should only be considered an improvement to the user experience; it does not prevent the user
++   * from saving the value without authenticating using other methods (e.g. by directly modifying the keychain).
++   * For it to take effect, the 'requireAuthentication' flag must be set to true.
++   * @default false
++   * @platform ios
++   */
++  forceAuthenticationOnSave?: boolean;
+ };
+ 
+ // @needsAudit
diff --git a/patches/expo-secure-store/expo-secure-store+14.2.4+004+fail-on-update.patch b/patches/expo-secure-store/expo-secure-store+14.2.4+004+fail-on-update.patch
new file mode 100644
index 000000000000..8b912f97d1e1
--- /dev/null
+++ b/patches/expo-secure-store/expo-secure-store+14.2.4+004+fail-on-update.patch
@@ -0,0 +1,99 @@
+diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt
+index 866d47d..1be213f 100644
+--- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt
++++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt
+@@ -196,6 +196,10 @@ open class SecureStoreModule : Module() {
+       return SecureStoreAuthType.NONE
+     }
+ 
++    if (prefs.contains(keychainAwareKey) && options.failOnUpdate) {
++      throw WriteException("Key already exists", key, options.keychainService)
++    }
++
+     try {
+       if (keyIsInvalidated) {
+         // Invalidated keys will block writing even though it's not possible to re-validate them
+diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt
+index d38650c..3ce02db 100644
+--- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt
++++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt
+@@ -10,6 +10,7 @@ class SecureStoreOptions(
+   @Field var authenticationPrompt: String = " ",
+   @Field var keychainService: String = SecureStoreModule.DEFAULT_KEYSTORE_ALIAS,
+   @Field var requireAuthentication: Boolean = false,
++  @Field var failOnUpdate: Boolean = false,
+   @Field var enableDeviceFallback: Boolean = false
+ ) : Record, Serializable
+ 
+diff --git a/node_modules/expo-secure-store/build/SecureStore.d.ts b/node_modules/expo-secure-store/build/SecureStore.d.ts
+index d6aa1c8..b920e4d 100644
+--- a/node_modules/expo-secure-store/build/SecureStore.d.ts
++++ b/node_modules/expo-secure-store/build/SecureStore.d.ts
+@@ -151,6 +151,17 @@ export type SecureStoreOptions = {
+      * @platform ios
+      */
+     forceAuthenticationOnSave?: boolean;
++
++    /**
++     * If the key has already been stored, the save function will throw an error instead of overwriting it.
++     * The behaviour differs slightly depending on the platform.
++     * On Android, an error is thrown before the authentication prompt. On iOS, an error is thrown after authentication.
++     *
++     * @default false
++     * @platform ios
++     * @platform android
++     */
++    failOnUpdate?: boolean;
+ };
+ /**
+  * Returns whether the SecureStore API is enabled on the current device. This does not check the app
+diff --git a/node_modules/expo-secure-store/ios/SecureStoreModule.swift b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
+index 9848dc1..3ecabf8 100644
+--- a/node_modules/expo-secure-store/ios/SecureStoreModule.swift
++++ b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
+@@ -199,6 +199,9 @@ public final class SecureStoreModule: Module {
+       SecItemDelete(query(with: key, options: options, requireAuthentication: !options.requireAuthentication) as CFDictionary)
+       return true
+     case errSecDuplicateItem:
++      if options.failOnUpdate {
++        throw SecureStoreRuntimeError("Key already exists")
++      }
+       return try update(value: value, with: key, options: options)
+     default:
+       throw KeyChainException(status)
+diff --git a/node_modules/expo-secure-store/ios/SecureStoreOptions.swift b/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
+index 504c36b..4c490b1 100644
+--- a/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
++++ b/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
+@@ -21,6 +21,9 @@ internal struct SecureStoreOptions: Record {
+ 
+   @Field
+   var forceAuthenticationOnSave: Bool = false
++
++  @Field
++  var failOnUpdate: Bool = false
+ }
+ 
+ @available(iOS 11.2, *)
+diff --git a/node_modules/expo-secure-store/src/SecureStore.ts b/node_modules/expo-secure-store/src/SecureStore.ts
+index 1f2ed59..db64fd2 100644
+--- a/node_modules/expo-secure-store/src/SecureStore.ts
++++ b/node_modules/expo-secure-store/src/SecureStore.ts
+@@ -176,6 +176,17 @@ export type SecureStoreOptions = {
+    * @platform ios
+    */
+   forceAuthenticationOnSave?: boolean;
++
++  /**
++   * If the key has already been stored, the save function will throw an error instead of overwriting it.
++   * The behaviour differs slightly depending on the platform.
++   * On Android, an error is thrown before the authentication prompt. On iOS, an error is thrown after authentication.
++   *
++   * @default false
++   * @platform ios
++   * @platform android
++   */
++  failOnUpdate?: boolean;
+ };
+ 
+ // @needsAudit
diff --git a/patches/expo-secure-store/expo-secure-store+14.2.4+005+force-read-authentication-on-simulators.patch b/patches/expo-secure-store/expo-secure-store+14.2.4+005+force-read-authentication-on-simulators.patch
new file mode 100644
index 000000000000..100829ae041c
--- /dev/null
+++ b/patches/expo-secure-store/expo-secure-store+14.2.4+005+force-read-authentication-on-simulators.patch
@@ -0,0 +1,78 @@
+diff --git a/node_modules/expo-secure-store/build/SecureStore.d.ts b/node_modules/expo-secure-store/build/SecureStore.d.ts
+index bf5290c..cc981d3 100644
+--- a/node_modules/expo-secure-store/build/SecureStore.d.ts
++++ b/node_modules/expo-secure-store/build/SecureStore.d.ts
+@@ -162,6 +162,19 @@ export type SecureStoreOptions = {
+      * @platform android
+      */
+     failOnUpdate?: boolean;
++
++    /**
++     * The LocalAuthentication behaves slightly differently on iOS simulators.
++     * In numerous cases, the authentication prompts are skipped on simulators (as opposed to real devices).
++     * Setting this flag to true forces the prompt to appear on simulators when a value with the `requireAuthentication` flag set to true is read.
++     * This is purely for testing the app on simulators, in cases where the prompt does not appear when the value is read.
++     * This has no effect on real devices.
++     *
++     * @warning: This flag only works for the asynchronous version of the SecureStore read method.
++     * @default false
++     * @platform ios
++     */
++    forceReadAuthenticationOnSimulators?: boolean;
+ };
+ /**
+  * Returns whether the SecureStore API is enabled on the current device. This does not check the app
+diff --git a/node_modules/expo-secure-store/ios/SecureStoreModule.swift b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
+index 4bfe51f..cbc4615 100644
+--- a/node_modules/expo-secure-store/ios/SecureStoreModule.swift
++++ b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
+@@ -19,6 +19,11 @@ public final class SecureStoreModule: Module {
+     ])
+ 
+     AsyncFunction("getValueWithKeyAsync") { (key: String, options: SecureStoreOptions) in
++      #if targetEnvironment(simulator)
++        if options.requireAuthentication && options.forceReadAuthenticationOnSimulators {
++          try await triggerPolicy(options: options)
++        }
++      #endif
+       return getSecureStoreFeedback(value: try get(with: key, options: options)).values
+     }
+ 
+diff --git a/node_modules/expo-secure-store/ios/SecureStoreOptions.swift b/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
+index 4c490b1..aee90ed 100644
+--- a/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
++++ b/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
+@@ -24,6 +24,9 @@ internal struct SecureStoreOptions: Record {
+ 
+   @Field
+   var failOnUpdate: Bool = false
++
++  @Field
++  var forceReadAuthenticationOnSimulators: Bool = false
+ }
+ 
+ @available(iOS 11.2, *)
+diff --git a/node_modules/expo-secure-store/src/SecureStore.ts b/node_modules/expo-secure-store/src/SecureStore.ts
+index db64fd2..69541ce 100644
+--- a/node_modules/expo-secure-store/src/SecureStore.ts
++++ b/node_modules/expo-secure-store/src/SecureStore.ts
+@@ -187,6 +187,19 @@ export type SecureStoreOptions = {
+    * @platform android
+    */
+   failOnUpdate?: boolean;
++
++  /**
++   * The LocalAuthentication behaves slightly differently on iOS simulators.
++   * In numerous cases, the authentication prompts are skipped on simulators (as opposed to real devices).
++   * Setting this flag to true forces the prompt to appear on simulators when a value with the `requireAuthentication` flag set to true is read.
++   * This is purely for testing the app on simulators, in cases where the prompt does not appear when the value is read.
++   * This has no effect on real devices.
++   *
++   * @warning: This flag only works for the asynchronous version of the SecureStore read method.
++   * @default false
++   * @platform ios
++   */
++  forceReadAuthenticationOnSimulators?: boolean;
+ };
+ 
+ // @needsAudit

From 5feffab709fab42e5229a74aa9db98c75e98c7d6 Mon Sep 17 00:00:00 2001
From: Jakub Korytko <jakub@korytko.me>
Date: Tue, 2 Dec 2025 12:42:58 +0100
Subject: [PATCH 2/7] Improve expo-secure-store patches

---
 ...re-store+14.2.4+002+return-auth-type.patch | 936 ++++++++++++------
 ...2.4+003+force-authentication-on-save.patch |  77 +-
 ...cure-store+14.2.4+004+fail-on-update.patch |  71 +-
 ...ce-read-authentication-on-simulators.patch |  61 +-
 4 files changed, 770 insertions(+), 375 deletions(-)

diff --git a/patches/expo-secure-store/expo-secure-store+14.2.4+002+return-auth-type.patch b/patches/expo-secure-store/expo-secure-store+14.2.4+002+return-auth-type.patch
index 6a0171d3e5d0..8689d910d7eb 100644
--- a/patches/expo-secure-store/expo-secure-store+14.2.4+002+return-auth-type.patch
+++ b/patches/expo-secure-store/expo-secure-store+14.2.4+002+return-auth-type.patch
@@ -5,7 +5,7 @@ index 4a1a009..980ef0a 100644
 @@ -20,12 +20,12 @@ class AuthenticationHelper(
  ) {
    private var isAuthenticating = false
-
+ 
 -  suspend fun authenticateCipher(cipher: Cipher, requiresAuthentication: Boolean, title: String, enableDeviceFallback: Boolean): Cipher {
 -    if (requiresAuthentication) {
 -      return openAuthenticationPrompt(cipher, title, enableDeviceFallback).cryptoObject?.cipher
@@ -18,124 +18,140 @@ index 4a1a009..980ef0a 100644
 -    return cipher
 +    return promptResult
    }
-
+ 
    private suspend fun openAuthenticationPrompt(
 diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt
-index 3f21552..866d47d 100644
+index 3f21552..d209efc 100644
 --- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt
 +++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt
 @@ -36,23 +36,23 @@ open class SecureStoreModule : Module() {
-
+ 
      AsyncFunction("setValueWithKeyAsync") Coroutine { value: String?, key: String?, options: SecureStoreOptions ->
        key ?: throw NullKeyException()
 -      return@Coroutine setItemImpl(key, value, options, false)
-+      return@Coroutine setItemImpl(key, value, options, false).ordinal
++      return@Coroutine narrowSecureStoreFeedback(SecureStoreFeedbackAction.SET, setItemImpl(key, value, options, false), options)
      }
-
+ 
      AsyncFunction("getValueWithKeyAsync") Coroutine { key: String, options: SecureStoreOptions ->
 -      return@Coroutine getItemImpl(key, options)
-+      return@Coroutine getItemImpl(key, options).values
++      return@Coroutine narrowSecureStoreFeedback(SecureStoreFeedbackAction.GET,getItemImpl(key, options), options)
      }
-
+ 
      Function("setValueWithKeySync") { value: String?, key: String?, options: SecureStoreOptions ->
        key ?: throw NullKeyException()
        return@Function runBlocking {
 -        return@runBlocking setItemImpl(key, value, options, keyIsInvalidated = false)
-+        return@runBlocking setItemImpl(key, value, options, keyIsInvalidated = false).ordinal
++        return@runBlocking narrowSecureStoreFeedback(SecureStoreFeedbackAction.SET, setItemImpl(key, value, options, keyIsInvalidated = false), options).value
        }
      }
-
+ 
      Function("getValueWithKeySync") { key: String, options: SecureStoreOptions ->
        return@Function runBlocking {
 -        return@runBlocking getItemImpl(key, options)
-+        return@runBlocking getItemImpl(key, options).values
++        return@runBlocking narrowSecureStoreFeedback(SecureStoreFeedbackAction.GET, getItemImpl(key, options), options).value
        }
      }
-
-@@ -94,7 +94,7 @@ open class SecureStoreModule : Module() {
+ 
+@@ -94,7 +94,23 @@ open class SecureStoreModule : Module() {
      }
    }
-
+ 
 -  private suspend fun getItemImpl(key: String, options: SecureStoreOptions): String? {
-+  private suspend fun getItemImpl(key: String, options: SecureStoreOptions): SecureStoreFeedback<out String?> {
++  private suspend fun <T> narrowSecureStoreFeedback(action: String, feedback: SecureStoreOriginalFeedback<T>, options: SecureStoreOptions): SecureStoreNarrowedFeedback<T> {
++    if (!options.returnUsedAuthenticationType) {
++      return feedback
++    }
++
++    if (action == SecureStoreFeedbackAction.GET) {
++      return SecureStoreGetFeedback(feedback.source, feedback.authenticationResult)
++    }
++
++    if (action == SecureStoreFeedbackAction.SET) {
++      return SecureStoreSetFeedback(feedback.source, feedback.authenticationResult)
++    }
++
++    return feedback
++  }
++
++  private suspend fun getItemImpl(key: String, options: SecureStoreOptions): SecureStoreOriginalFeedback<out String?> {
      // We use a SecureStore-specific shared preferences file, which lets us do things like enumerate
      // its entries or clear all of them
      val prefs: SharedPreferences = getSharedPreferences()
-@@ -104,10 +104,10 @@ open class SecureStoreModule : Module() {
+@@ -104,10 +120,10 @@ open class SecureStoreModule : Module() {
      } else if (prefs.contains(key)) { // For backwards-compatibility try to read using the old key format
        return readJSONEncodedItem(key, prefs, options)
      }
 -    return null
-+    return SecureStoreFeedback(null)
++    return SecureStoreOriginalFeedback(null)
    }
-
+ 
 -  private suspend fun readJSONEncodedItem(key: String, prefs: SharedPreferences, options: SecureStoreOptions): String? {
-+  private suspend fun readJSONEncodedItem(key: String, prefs: SharedPreferences, options: SecureStoreOptions): SecureStoreFeedback<out String?> {
++  private suspend fun readJSONEncodedItem(key: String, prefs: SharedPreferences, options: SecureStoreOptions): SecureStoreOriginalFeedback<out String?> {
      val keychainAwareKey = createKeychainAwareKey(key, options.keychainService)
-
+ 
      val legacyEncryptedItemString = prefs.getString(key, null)
-@@ -126,7 +126,7 @@ open class SecureStoreModule : Module() {
+@@ -126,7 +142,7 @@ open class SecureStoreModule : Module() {
        ""
      }
-
+ 
 -    encryptedItemString ?: return null
-+    encryptedItemString ?: return SecureStoreFeedback(null)
-
++    encryptedItemString ?: return SecureStoreOriginalFeedback(null)
+ 
      val encryptedItem: JSONObject = try {
        JSONObject(encryptedItemString)
-@@ -149,13 +149,13 @@ open class SecureStoreModule : Module() {
+@@ -149,13 +165,13 @@ open class SecureStoreModule : Module() {
                  "This situation occurs when the app is reinstalled. The value will be removed to avoid future errors. Returning null"
              )
              deleteItemImpl(key, options)
 -            return null
-+            return SecureStoreFeedback(null)
++            return SecureStoreOriginalFeedback(null)
            }
            return mAESEncryptor.decryptItem(key, encryptedItem, secretKeyEntry, options, authenticationHelper)
          }
          HybridAESEncryptor.NAME -> {
            val privateKeyEntry = getKeyEntryCompat(PrivateKeyEntry::class.java, hybridAESEncryptor, options, requireAuthentication, usesKeystoreSuffix)
 -            ?: return null
-+            ?: return SecureStoreFeedback(null)
++            ?: return SecureStoreOriginalFeedback(null)
            return hybridAESEncryptor.decryptItem(key, encryptedItem, privateKeyEntry, options, authenticationHelper)
          }
          else -> {
-@@ -164,7 +164,7 @@ open class SecureStoreModule : Module() {
+@@ -164,7 +180,7 @@ open class SecureStoreModule : Module() {
        }
      } catch (e: KeyPermanentlyInvalidatedException) {
        Log.w(TAG, "The requested key has been permanently invalidated. Returning null")
 -      return null
-+      return SecureStoreFeedback(null)
++      return SecureStoreOriginalFeedback(null)
      } catch (e: BadPaddingException) {
        // The key from the KeyStore is unable to decode the entry. This is because a new key was generated, but the entries are encrypted using the old one.
        // This usually means that the user has reinstalled the app. We can safely remove the old value and return null as it's impossible to decrypt it.
-@@ -174,7 +174,7 @@ open class SecureStoreModule : Module() {
+@@ -174,7 +190,7 @@ open class SecureStoreModule : Module() {
            "The entry in shared preferences is out of sync with the keystore. It will be removed, returning null."
        )
        deleteItemImpl(key, options)
 -      return null
-+      return SecureStoreFeedback(null)
++      return SecureStoreOriginalFeedback(null)
      } catch (e: GeneralSecurityException) {
        throw (DecryptException(e.message, key, options.keychainService, e))
      } catch (e: CodedException) {
-@@ -184,7 +184,7 @@ open class SecureStoreModule : Module() {
+@@ -184,7 +200,7 @@ open class SecureStoreModule : Module() {
      }
    }
-
+ 
 -  private suspend fun setItemImpl(key: String, value: String?, options: SecureStoreOptions, keyIsInvalidated: Boolean) {
-+  private suspend fun setItemImpl(key: String, value: String?, options: SecureStoreOptions, keyIsInvalidated: Boolean): SecureStoreAuthType {
++  private suspend fun setItemImpl(key: String, value: String?, options: SecureStoreOptions, keyIsInvalidated: Boolean): SecureStoreOriginalFeedback<out JSONObject?> {
      val keychainAwareKey = createKeychainAwareKey(key, options.keychainService)
      val prefs: SharedPreferences = getSharedPreferences()
-
-@@ -193,7 +193,7 @@ open class SecureStoreModule : Module() {
+ 
+@@ -193,7 +209,7 @@ open class SecureStoreModule : Module() {
        if (!success) {
          throw WriteException("Could not write a null value to SecureStore", key, options.keychainService)
        }
 -      return
-+      return SecureStoreAuthType.NONE
++      return SecureStoreOriginalFeedback(null)
      }
-
+ 
      try {
-@@ -210,7 +210,8 @@ open class SecureStoreModule : Module() {
+@@ -210,7 +226,8 @@ open class SecureStoreModule : Module() {
         back a value.
         */
        val secretKeyEntry: SecretKeyEntry = getOrCreateKeyEntry(SecretKeyEntry::class.java, mAESEncryptor, options, options.requireAuthentication)
@@ -144,31 +160,35 @@ index 3f21552..866d47d 100644
 +      val encryptedItem = encryptResult.value
        encryptedItem.put(SCHEME_PROPERTY, AESEncryptor.NAME)
        saveEncryptedItem(encryptedItem, prefs, keychainAwareKey, options.requireAuthentication, options.keychainService)
-
-@@ -218,6 +219,9 @@ open class SecureStoreModule : Module() {
+ 
+@@ -218,6 +235,9 @@ open class SecureStoreModule : Module() {
        if (prefs.contains(key)) {
          prefs.edit().remove(key).apply()
        }
 +
-+      return encryptResult.authType
++      return encryptResult
 +
      } catch (e: KeyPermanentlyInvalidatedException) {
        if (!keyIsInvalidated) {
          Log.w(TAG, "Key has been invalidated, retrying with the key deleted")
 diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt
-index 0455fd6..d38650c 100644
+index 0455fd6..4e30440 100644
 --- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt
 +++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt
 @@ -1,5 +1,6 @@
  package expo.modules.securestore
-
+ 
 +import androidx.biometric.BiometricPrompt
  import expo.modules.kotlin.records.Field
  import expo.modules.kotlin.records.Record
  import java.io.Serializable
-@@ -11,3 +12,35 @@ class SecureStoreOptions(
+@@ -9,5 +10,58 @@ class SecureStoreOptions(
+   @Field var authenticationPrompt: String = " ",
+   @Field var keychainService: String = SecureStoreModule.DEFAULT_KEYSTORE_ALIAS,
    @Field var requireAuthentication: Boolean = false,
-   @Field var enableDeviceFallback: Boolean = false
+-  @Field var enableDeviceFallback: Boolean = false
++  @Field var enableDeviceFallback: Boolean = false,
++  @Field var returnUsedAuthenticationType: Boolean = false
  ) : Record, Serializable
 +
 +enum class SecureStoreAuthType(index: Int) {
@@ -180,9 +200,16 @@ index 0455fd6..d38650c 100644
 +  NONE(0)
 +}
 +
-+data class SecureStoreFeedback<T>(
-+  val value: T,
-+  val authenticationResult: BiometricPrompt.AuthenticationResult? = null
++open class SecureStoreFeedbackAction {
++  companion object {
++    const val GET = "GET"
++    const val SET = "SET"
++  }
++}
++
++abstract class SecureStoreFeedback<T, R>(
++  val source: T,
++  val authenticationResult: BiometricPrompt.AuthenticationResult?
 +) {
 +  @Field var authType: SecureStoreAuthType = when (authenticationResult?.authenticationType) {
 +    BiometricPrompt.AUTHENTICATION_RESULT_TYPE_UNKNOWN -> {
@@ -198,68 +225,82 @@ index 0455fd6..d38650c 100644
 +      SecureStoreAuthType.NONE
 +    }
 +  }
++  abstract val value: R
++}
 +
++class SecureStoreGetFeedback<T>(source: T, authenticationResult: BiometricPrompt.AuthenticationResult? = null): SecureStoreFeedback<T, Pair<T, Int>>(source, authenticationResult) {
 +  /** Used to return easily convertible values to JS code */
-+  @Field var values: Pair<T, Int> = Pair(value, authType.ordinal)
++  @Field override var value: Pair<T, Int> = Pair(source, authType.ordinal)
++}
++
++class SecureStoreSetFeedback<T>(source: T, authenticationResult: BiometricPrompt.AuthenticationResult? = null): SecureStoreFeedback<T, Int>(source, authenticationResult) {
++  @Field override var value: Int = authType.ordinal
++}
++
++class SecureStoreOriginalFeedback<T>(source: T, authenticationResult: BiometricPrompt.AuthenticationResult? = null): SecureStoreFeedback<T, T>(source, authenticationResult) {
++  @Field override var value: T = source
 +}
++
++typealias SecureStoreNarrowedFeedback<T> = SecureStoreFeedback<T, out Any?>
 diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/AESEncryptor.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/AESEncryptor.kt
-index 1cd187f..44c84c3 100644
+index 1cd187f..d927ff1 100644
 --- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/AESEncryptor.kt
 +++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/AESEncryptor.kt
-@@ -5,8 +5,10 @@ import android.security.keystore.KeyGenParameterSpec
+@@ -5,10 +5,12 @@ import android.security.keystore.KeyGenParameterSpec
  import android.security.keystore.KeyProperties
  import android.util.Base64
  import androidx.annotation.RequiresApi
 +import androidx.biometric.BiometricPrompt
  import expo.modules.securestore.AuthenticationHelper
  import expo.modules.securestore.DecryptException
-+import expo.modules.securestore.SecureStoreFeedback
  import expo.modules.securestore.SecureStoreModule
  import expo.modules.securestore.SecureStoreOptions
++import expo.modules.securestore.SecureStoreOriginalFeedback
  import org.json.JSONException
+ import org.json.JSONObject
+ import java.nio.charset.StandardCharsets
 @@ -86,15 +88,23 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
      authenticationPrompt: String,
      authenticationHelper: AuthenticationHelper,
      enableDeviceFallback: Boolean,
 -  ): JSONObject {
-+  ): SecureStoreFeedback<JSONObject> {
++  ): SecureStoreOriginalFeedback<JSONObject> {
      val secretKey = keyStoreEntry.secretKey
      val cipher = Cipher.getInstance(AES_CIPHER)
      cipher.init(Cipher.ENCRYPT_MODE, secretKey)
-
+ 
      val gcmSpec = cipher.parameters.getParameterSpec(GCMParameterSpec::class.java)
 -    val authenticatedCipher = authenticationHelper.authenticateCipher(cipher, requireAuthentication, authenticationPrompt, enableDeviceFallback)
 +    var promptResult: BiometricPrompt.AuthenticationResult? = null
 +    val authenticatedCipher: Cipher
-
--    return createEncryptedItemWithCipher(plaintextValue, authenticatedCipher, gcmSpec)
++
 +    if (requireAuthentication) {
 +      promptResult = authenticationHelper.authenticateCipher(cipher, authenticationPrompt, enableDeviceFallback)
 +      authenticatedCipher = promptResult.cryptoObject?.cipher ?: cipher
 +    } else {
 +      authenticatedCipher = cipher
 +    }
-+
-+    return SecureStoreFeedback(createEncryptedItemWithCipher(plaintextValue, authenticatedCipher, gcmSpec), promptResult)
+ 
+-    return createEncryptedItemWithCipher(plaintextValue, authenticatedCipher, gcmSpec)
++    return SecureStoreOriginalFeedback(createEncryptedItemWithCipher(plaintextValue, authenticatedCipher, gcmSpec), promptResult)
    }
-
+ 
    internal fun createEncryptedItemWithCipher(
 @@ -121,7 +131,7 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
      keyStoreEntry: KeyStore.SecretKeyEntry,
      options: SecureStoreOptions,
      authenticationHelper: AuthenticationHelper
 -  ): String {
-+  ): SecureStoreFeedback<String> {
++  ): SecureStoreOriginalFeedback<String> {
      val ciphertext = encryptedItem.getString(CIPHERTEXT_PROPERTY)
      val ivString = encryptedItem.getString(IV_PROPERTY)
      val authenticationTagLength = encryptedItem.getInt(GCM_AUTHENTICATION_TAG_LENGTH_PROPERTY)
-@@ -135,8 +145,18 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
+@@ -135,8 +145,17 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
        throw DecryptException("Authentication tag length must be at least $MIN_GCM_AUTHENTICATION_TAG_LENGTH bits long", key, options.keychainService)
      }
      cipher.init(Cipher.DECRYPT_MODE, keyStoreEntry.secretKey, gcmSpec)
 -    val unlockedCipher = authenticationHelper.authenticateCipher(cipher, requiresAuthentication, options.authenticationPrompt, options.enableDeviceFallback)
 -    return String(unlockedCipher.doFinal(ciphertextBytes), StandardCharsets.UTF_8)
-+
 +    var promptResult: BiometricPrompt.AuthenticationResult? = null
 +    val unlockedCipher: Cipher
 +
@@ -270,28 +311,28 @@ index 1cd187f..44c84c3 100644
 +      unlockedCipher = cipher
 +    }
 +
-+    return SecureStoreFeedback(String(unlockedCipher.doFinal(ciphertextBytes), StandardCharsets.UTF_8), promptResult)
++    return SecureStoreOriginalFeedback(String(unlockedCipher.doFinal(ciphertextBytes), StandardCharsets.UTF_8), promptResult)
    }
-
+ 
    companion object {
 diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/HybridAESEncryptor.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/HybridAESEncryptor.kt
-index 5f8bbfd..0b3ae0a 100644
+index 5f8bbfd..7527001 100644
 --- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/HybridAESEncryptor.kt
 +++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/HybridAESEncryptor.kt
-@@ -7,6 +7,7 @@ import android.util.Base64
- import expo.modules.securestore.AuthenticationHelper
- import expo.modules.securestore.EncryptException
+@@ -9,6 +9,7 @@ import expo.modules.securestore.EncryptException
  import expo.modules.securestore.KeyStoreException
-+import expo.modules.securestore.SecureStoreFeedback
  import expo.modules.securestore.SecureStoreModule
  import expo.modules.securestore.SecureStoreOptions
++import expo.modules.securestore.SecureStoreOriginalFeedback
  import org.json.JSONException
+ import org.json.JSONObject
+ import java.security.GeneralSecurityException
 @@ -71,7 +72,7 @@ class HybridAESEncryptor(private var mContext: Context, private val mAESEncrypto
      authenticationPrompt: String,
      authenticationHelper: AuthenticationHelper,
      enableDeviceFallback: Boolean,
 -  ): JSONObject {
-+  ): SecureStoreFeedback<JSONObject> {
++  ): SecureStoreOriginalFeedback<JSONObject> {
      // This should never be called after we dropped Android SDK 22 support.
      throw EncryptException(
        "HybridAESEncryption should not be used on Android SDK >= 23. This shouldn't happen. " +
@@ -300,29 +341,29 @@ index 5f8bbfd..0b3ae0a 100644
      options: SecureStoreOptions,
      authenticationHelper: AuthenticationHelper
 -  ): String {
-+  ): SecureStoreFeedback<String> {
++  ): SecureStoreOriginalFeedback<String> {
      // Decrypt the encrypted symmetric key
      val encryptedSecretKeyString = encryptedItem.getString(ENCRYPTED_SECRET_KEY_PROPERTY)
      val encryptedSecretKeyBytes = Base64.decode(encryptedSecretKeyString, Base64.DEFAULT)
 diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/KeyBasedEncryptor.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/KeyBasedEncryptor.kt
-index 39459ff..66ba986 100644
+index 39459ff..a531aff 100644
 --- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/KeyBasedEncryptor.kt
 +++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/KeyBasedEncryptor.kt
-@@ -1,6 +1,7 @@
- package expo.modules.securestore.encryptors
-
+@@ -2,6 +2,7 @@ package expo.modules.securestore.encryptors
+ 
  import expo.modules.securestore.AuthenticationHelper
-+import expo.modules.securestore.SecureStoreFeedback
  import expo.modules.securestore.SecureStoreOptions
++import expo.modules.securestore.SecureStoreOriginalFeedback
  import org.json.JSONException
  import org.json.JSONObject
+ import java.security.GeneralSecurityException
 @@ -27,7 +28,7 @@ interface KeyBasedEncryptor<E : KeyStore.Entry> {
      authenticationPrompt: String,
      authenticationHelper: AuthenticationHelper,
      enableDeviceFallback: Boolean,
 -  ): JSONObject
-+  ): SecureStoreFeedback<JSONObject>
-
++  ): SecureStoreOriginalFeedback<JSONObject>
+ 
    @Throws(GeneralSecurityException::class, JSONException::class)
    suspend fun decryptItem(
 @@ -36,5 +37,5 @@ interface KeyBasedEncryptor<E : KeyStore.Entry> {
@@ -330,19 +371,21 @@ index 39459ff..66ba986 100644
      options: SecureStoreOptions,
      authenticationHelper: AuthenticationHelper
 -  ): String
-+  ): SecureStoreFeedback<String>
++  ): SecureStoreOriginalFeedback<String>
  }
 diff --git a/node_modules/expo-secure-store/build/SecureStore.d.ts b/node_modules/expo-secure-store/build/SecureStore.d.ts
-index 835a4d4..77895d0 100644
+index 835a4d4..7bfcc98 100644
 --- a/node_modules/expo-secure-store/build/SecureStore.d.ts
 +++ b/node_modules/expo-secure-store/build/SecureStore.d.ts
 @@ -1,4 +1,53 @@
++type EmptyObject = Record<string, never>;
++type SecureStoreSetFeedback<R extends SecureStoreOptions> = R['returnUsedAuthenticationType'] extends true ? AuthType : void;
++type SecureStoreGetFeedback<T, R extends SecureStoreOptions> = R['returnUsedAuthenticationType'] extends true ? [T | null, AuthType] : T | null;
  export type KeychainAccessibilityConstant = number;
-+
 +/**
 + * Authentication type returned by the SecureStore after reading item or saving it to the store.
 + */
-+export declare const AUTH_TYPE = {
++export declare const AUTH_TYPE: {
 +    /**
 +     * This is purely for backwards compatibility.
 +     * Although it is not listed as a return value of the getAuthenticationType() method,
@@ -351,86 +394,127 @@ index 835a4d4..77895d0 100644
 +     * @see https://developer.android.com/reference/androidx/biometric/BiometricPrompt#AUTHENTICATION_RESULT_TYPE_UNKNOWN()
 +     * @platform android
 +     */
-+    UNKNOWN: -1,
++    readonly UNKNOWN: -1;
 +    /**
 +     * Returned when the authentication fails
 +     * @platform android
 +     * @platform ios
 +     */
-+    NONE: 0,
++    readonly NONE: 0;
 +    /**
 +     * Generic type, not specified whether it was a passcode or pattern.
 +     * @platform android
 +     * @platform ios
 +     */
-+    CREDENTIALS: 1,
++    readonly CREDENTIALS: 1;
 +    /**
 +     * Generic type, not specified whether it was a face scan or a fingerprint
 +     * @platform android
 +     */
-+    BIOMETRICS: 2,
++    readonly BIOMETRICS: 2;
 +    /**
 +     * FaceID was used to authenticate
 +     * @platform ios
 +     */
-+    FACE_ID: 3,
++    readonly FACE_ID: 3;
 +    /**
 +     * TouchID was used to authenticate
 +     * @platform ios
 +     */
-+    TOUCH_ID: 4,
++    readonly TOUCH_ID: 4;
 +    /**
 +     * OpticID was used to authenticate (reserved by apple, used on Apple Vision Pro, not iOS)
 +     */
-+    OPTIC_ID: 5
-+} as const;
-+
++    readonly OPTIC_ID: 5;
++};
 +type AuthType = (typeof AUTH_TYPE)[keyof typeof AUTH_TYPE];
-+
  /**
   * The data in the keychain item cannot be accessed after a restart until the device has been
   * unlocked once by the user. This may be useful if you need to access the item when the phone
-@@ -119,7 +168,7 @@ export declare function deleteItemAsync(key: string, options?: SecureStoreOption
+@@ -59,6 +108,7 @@ export type SecureStoreOptions = {
+      * Warning: This option is not supported in Expo Go when biometric authentication is available due to a missing NSFaceIDUsageDescription.
+      * In release builds or when using continuous native generation, make sure to use the `expo-secure-store` config plugin.
+      *
++     * > **Note:** This library requires a real device for testing since emulators/simulators do not require biometric authentication when retrieving secrets, unlike real iOS devices.
+      */
+     requireAuthentication?: boolean;
+     /**
+@@ -88,6 +138,22 @@ export type SecureStoreOptions = {
+      * @platform ios
+      */
+     enableDeviceFallback?: boolean;
++    /**
++     * When this flag is set to true, the get methods of SecureStore will return a two-element array. The first value will be the original value returned when this flag is set to false.
++     * The second value is the authentication type used to read the value from the AUTH_TYPE object.
++     * As for the set function, the returned value will simply be AUTH_TYPE.
++     *
++     * @warning
++     * If the iOS device supports biometrics and the user falls back to device credentials, it will not be detected.
++     * This is not the case on Android, but we cannot specify the exact type of biometrics (e.g. fingerprint or face scan).
++     * Whether the type is detected correctly depends on the platform and its native implementation.
++     * This should be treated as more of a hint.
++     *
++     * @default false
++     * @platform android
++     * @platform ios
++     */
++    returnUsedAuthenticationType?: boolean;
+ };
+ /**
+  * Returns whether the SecureStore API is enabled on the current device. This does not check the app
+@@ -119,27 +185,27 @@ export declare function deleteItemAsync(key: string, options?: SecureStoreOption
   * > After a key has been invalidated, it becomes impossible to read its value.
   * > This only applies to values stored with `requireAuthentication` set to `true`.
   */
 -export declare function getItemAsync(key: string, options?: SecureStoreOptions): Promise<string | null>;
-+export declare function getItemAsync(key: string, options?: SecureStoreOptions): Promise<[string | null, AuthType]>;
++export declare function getItemAsync<R extends SecureStoreOptions>(key: string, options?: R | EmptyObject): Promise<SecureStoreGetFeedback<string, R>>;
  /**
   * Stores a key–value pair.
   *
-@@ -129,7 +178,7 @@ export declare function getItemAsync(key: string, options?: SecureStoreOptions):
+  * @param key The key to associate with the stored value. Keys may contain alphanumeric characters, `.`, `-`, and `_`.
+- * @param value The value to store. Size limit is 2048 bytes.
++ * @param value The value to store.
+  * @param options An [`SecureStoreOptions`](#securestoreoptions) object.
   *
   * @return A promise that rejects if value cannot be stored on the device.
   */
 -export declare function setItemAsync(key: string, value: string, options?: SecureStoreOptions): Promise<void>;
-+export declare function setItemAsync(key: string, value: string, options?: SecureStoreOptions): Promise<AuthType>;
++export declare function setItemAsync<R extends SecureStoreOptions>(key: string, value: string, options?: R | EmptyObject): Promise<SecureStoreSetFeedback<R>>;
  /**
   * Stores a key–value pair synchronously.
   * > **Note:** This function blocks the JavaScript thread, so the application may not be interactive when the `requireAuthentication` option is set to `true` until the user authenticates.
-@@ -139,7 +188,7 @@ export declare function setItemAsync(key: string, value: string, options?: Secur
+  *
+  * @param key The key to associate with the stored value. Keys may contain alphanumeric characters, `.`, `-`, and `_`.
+- * @param value The value to store. Size limit is 2048 bytes.
++ * @param value The value to store.
   * @param options An [`SecureStoreOptions`](#securestoreoptions) object.
   *
   */
 -export declare function setItem(key: string, value: string, options?: SecureStoreOptions): void;
-+export declare function setItem(key: string, value: string, options?: SecureStoreOptions): AuthType;
++export declare function setItem<R extends SecureStoreOptions>(key: string, value: string, options?: R | EmptyObject): SecureStoreSetFeedback<R>;
  /**
   * Synchronously reads the stored value associated with the provided key.
   * > **Note:** This function blocks the JavaScript thread, so the application may not be interactive when reading a value with `requireAuthentication`
-@@ -150,7 +199,7 @@ export declare function setItem(key: string, value: string, options?: SecureStor
+@@ -150,7 +216,7 @@ export declare function setItem(key: string, value: string, options?: SecureStor
   * @return Previously stored value. It resolves with `null` if there is no entry
   * for the given key or if the key has been invalidated.
   */
 -export declare function getItem(key: string, options?: SecureStoreOptions): string | null;
-+export declare function getItem(key: string, options?: SecureStoreOptions): [string | null, AuthType];
++export declare function getItem<R extends SecureStoreOptions>(key: string, options?: R | EmptyObject): SecureStoreGetFeedback<string, R>;
  /**
   * Checks if the value can be saved with `requireAuthentication` option enabled.
   * @return `true` if the device supports biometric authentication and the enrolled method is sufficiently secure. Otherwise, returns `false`. Always returns false on tvOS.
+@@ -165,4 +231,5 @@ export declare function canUseBiometricAuthentication(): boolean;
+  * @platform ios
+  */
+ export declare function canUseDeviceCredentialsAuthentication(): boolean;
++export {};
+ //# sourceMappingURL=SecureStore.d.ts.map
 diff --git a/node_modules/expo-secure-store/build/SecureStore.js b/node_modules/expo-secure-store/build/SecureStore.js
-index e11bd94..2371b88 100644
+index e11bd94..04569ac 100644
 --- a/node_modules/expo-secure-store/build/SecureStore.js
 +++ b/node_modules/expo-secure-store/build/SecureStore.js
-@@ -1,6 +1,52 @@
+@@ -1,6 +1,53 @@
  import ExpoSecureStore from './ExpoSecureStore';
  import { byteCountOverLimit, VALUE_BYTES_LIMIT } from './byteCounter';
  // @needsAudit
@@ -478,12 +562,22 @@ index e11bd94..2371b88 100644
 +    /**
 +     * OpticID was used to authenticate (reserved by apple, used on Apple Vision Pro, not iOS)
 +     */
-+    OPTIC_ID: 5
++    OPTIC_ID: 5,
 +};
++// @needsAudit
  /**
   * The data in the keychain item cannot be accessed after a restart until the device has been
   * unlocked once by the user. This may be useful if you need to access the item when the phone
-@@ -102,7 +148,7 @@ export async function setItemAsync(key, value, options = {}) {
+@@ -92,7 +139,7 @@ export async function getItemAsync(key, options = {}) {
+  * Stores a key–value pair.
+  *
+  * @param key The key to associate with the stored value. Keys may contain alphanumeric characters, `.`, `-`, and `_`.
+- * @param value The value to store. Size limit is 2048 bytes.
++ * @param value The value to store.
+  * @param options An [`SecureStoreOptions`](#securestoreoptions) object.
+  *
+  * @return A promise that rejects if value cannot be stored on the device.
+@@ -102,14 +149,14 @@ export async function setItemAsync(key, value, options = {}) {
      if (!isValidValue(value)) {
          throw new Error(`Invalid value provided to SecureStore. Values must be strings; consider JSON-encoding your values if they are serializable.`);
      }
@@ -492,62 +586,84 @@ index e11bd94..2371b88 100644
  }
  /**
   * Stores a key–value pair synchronously.
+  * > **Note:** This function blocks the JavaScript thread, so the application may not be interactive when the `requireAuthentication` option is set to `true` until the user authenticates.
+  *
+  * @param key The key to associate with the stored value. Keys may contain alphanumeric characters, `.`, `-`, and `_`.
+- * @param value The value to store. Size limit is 2048 bytes.
++ * @param value The value to store.
+  * @param options An [`SecureStoreOptions`](#securestoreoptions) object.
+  *
+  */
 diff --git a/node_modules/expo-secure-store/ios/SecureStoreModule.swift b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
-index 1268979..e4c3f2b 100644
+index 1268979..5280020 100644
 --- a/node_modules/expo-secure-store/ios/SecureStoreModule.swift
 +++ b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
-@@ -18,28 +18,40 @@ public final class SecureStoreModule: Module {
-       "WHEN_UNLOCKED_THIS_DEVICE_ONLY": SecureStoreAccessible.whenUnlockedThisDeviceOnly.rawValue
-     ])
-
+@@ -8,38 +8,44 @@ public final class SecureStoreModule: Module {
+   public func definition() -> ModuleDefinition {
+     Name("ExpoSecureStore")
+ 
+-    Constants([
+-      "AFTER_FIRST_UNLOCK": SecureStoreAccessible.afterFirstUnlock.rawValue,
+-      "AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY": SecureStoreAccessible.afterFirstUnlockThisDeviceOnly.rawValue,
+-      "ALWAYS": SecureStoreAccessible.always.rawValue,
+-      "WHEN_PASSCODE_SET_THIS_DEVICE_ONLY": SecureStoreAccessible.whenPasscodeSetThisDeviceOnly.rawValue,
+-      "ALWAYS_THIS_DEVICE_ONLY": SecureStoreAccessible.alwaysThisDeviceOnly.rawValue,
+-      "WHEN_UNLOCKED": SecureStoreAccessible.whenUnlocked.rawValue,
+-      "WHEN_UNLOCKED_THIS_DEVICE_ONLY": SecureStoreAccessible.whenUnlockedThisDeviceOnly.rawValue
+-    ])
+-
 -    AsyncFunction("getValueWithKeyAsync") { (key: String, options: SecureStoreOptions) -> String? in
 -      return try get(with: key, options: options)
++    Constant("AFTER_FIRST_UNLOCK") { SecureStoreAccessible.afterFirstUnlock.rawValue }
++    Constant("AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY") { SecureStoreAccessible.afterFirstUnlockThisDeviceOnly.rawValue }
++    Constant("ALWAYS") { SecureStoreAccessible.always.rawValue }
++    Constant("WHEN_PASSCODE_SET_THIS_DEVICE_ONLY") { SecureStoreAccessible.whenPasscodeSetThisDeviceOnly.rawValue }
++    Constant("ALWAYS_THIS_DEVICE_ONLY") { SecureStoreAccessible.alwaysThisDeviceOnly.rawValue }
++    Constant("WHEN_UNLOCKED") { SecureStoreAccessible.whenUnlocked.rawValue }
++    Constant("WHEN_UNLOCKED_THIS_DEVICE_ONLY") { SecureStoreAccessible.whenUnlockedThisDeviceOnly.rawValue }
++
 +    AsyncFunction("getValueWithKeyAsync") { (key: String, options: SecureStoreOptions) in
-+      return getSecureStoreFeedback(value: try get(with: key, options: options)).values
++      let result = try get(with: key, options: options)
++
++      return wrapResultWithFeedback(action: .get, result: result, options: options).value
      }
-
+ 
 -    Function("getValueWithKeySync") { (key: String, options: SecureStoreOptions) -> String? in
 -      return try get(with: key, options: options)
 +    Function("getValueWithKeySync") { (key: String, options: SecureStoreOptions) in
-+      return getSecureStoreFeedback(value: try get(with: key, options: options)).values
++      let result = try get(with: key, options: options)
++
++      return wrapResultWithFeedback(action: .get, result: result, options: options).value
      }
-
+ 
 -    AsyncFunction("setValueWithKeyAsync") { (value: String, key: String, options: SecureStoreOptions) -> Bool in
-+    AsyncFunction("setValueWithKeyAsync") { (value: String, key: String, options: SecureStoreOptions) -> Int in
++    AsyncFunction("setValueWithKeyAsync") { (value: String, key: String, options: SecureStoreOptions) in
        guard let key = validate(for: key) else {
          throw InvalidKeyException()
        }
-
+ 
 -      return try set(value: value, with: key, options: options)
 +      let result = try set(value: value, with: key, options: options)
 +
-+      if !result {
-+        return AuthType.none.rawValue
-+      }
-+
-+      return getSecureStoreFeedback(value: true).authType
++      return wrapResultWithFeedback(action: .set, result: result, options: options).value
      }
-
+ 
 -    Function("setValueWithKeySync") {(value: String, key: String, options: SecureStoreOptions) -> Bool in
-+    Function("setValueWithKeySync") {(value: String, key: String, options: SecureStoreOptions) -> Int in
++    Function("setValueWithKeySync") {(value: String, key: String, options: SecureStoreOptions) in
        guard let key = validate(for: key) else {
          throw InvalidKeyException()
        }
-
+ 
 -      return try set(value: value, with: key, options: options)
 +      let result = try set(value: value, with: key, options: options)
 +
-+      if !result {
-+        return AuthType.none.rawValue
-+      }
-+
-+      return getSecureStoreFeedback(value: true).authType
++      return wrapResultWithFeedback(action: .set, result: result, options: options).value
      }
-
+ 
      AsyncFunction("deleteValueWithKeyAsync") { (key: String, options: SecureStoreOptions) in
-@@ -53,18 +65,7 @@ public final class SecureStoreModule: Module {
+@@ -53,18 +59,7 @@ public final class SecureStoreModule: Module {
      }
-
+ 
      Function("canUseBiometricAuthentication") {() -> Bool in
 -      #if os(tvOS)
 -      return false
@@ -563,12 +679,12 @@ index 1268979..e4c3f2b 100644
 -      #endif
 +      return areBiometricsEnabled()
      }
-
+ 
      Function("canUseDeviceCredentialsAuthentication") { () -> Bool in
-@@ -72,6 +73,31 @@ public final class SecureStoreModule: Module {
-     }
+@@ -76,6 +71,41 @@ public final class SecureStoreModule: Module {
+     return LAContext().canEvaluatePolicy(LAPolicy.deviceOwnerAuthentication, error: nil)
    }
-
+ 
 +  private func getAuthType() -> AuthType {
 +    if !areBiometricsEnabled() {return AuthType.credentials}
 +    let biometryType = LAContext().biometryType
@@ -582,8 +698,18 @@ index 1268979..e4c3f2b 100644
 +    }
 +  }
 +
-+  private func getSecureStoreFeedback<T>(value: T) -> SecureStoreFeedback<T> {
-+    return SecureStoreFeedback(value: value, authType: getAuthType().rawValue)
++  private func wrapResultWithFeedback<T>(action: SecureStoreFeedbackAction, result: T, options: SecureStoreOptions) -> any SecureStoreFeedback {
++    let authType = getAuthType().rawValue
++
++    if (!options.returnUsedAuthenticationType) {
++      return SecureStoreOriginalFeedback(source: result, authType: authType)
++    }
++
++    if (action == .get) {
++      return SecureStoreGetFeedback(source: result, authType: authType)
++    }
++
++    return SecureStoreSetFeedback(source: result, authType: authType)
 +  }
 +
 +  private func areBiometricsEnabled() -> Bool {
@@ -594,71 +720,21 @@ index 1268979..e4c3f2b 100644
 +    #endif
 +  }
 +
-   private func areDeviceCredentialsEnabled() -> Bool {
-     return LAContext().canEvaluatePolicy(LAPolicy.deviceOwnerAuthentication, error: nil)
-   }
-@@ -96,6 +122,29 @@ public final class SecureStoreModule: Module {
-     return nil
-   }
-
-+  private func NSFaceIDUsageEntryGuard(options: SecureStoreOptions) throws {
-+    if (options.enableDeviceFallback) {
-+        return;
-+    }
-+
-+    guard let _ = Bundle.main.infoDictionary?["NSFaceIDUsageDescription"] as? String else {
-+      throw MissingPlistKeyException()
-+    }
-+  }
-+
-+  private func getAccessOptions(options: SecureStoreOptions, accessibility: CFString) throws -> SecAccessControl {
-+    var error: Unmanaged<CFError>? = nil
-+
-+    let accessControlFlag: SecAccessControlCreateFlags = options.enableDeviceFallback ? .userPresence : .biometryCurrentSet
-+
-+    guard let accessOptions = SecAccessControlCreateWithFlags(kCFAllocatorDefault, accessibility, accessControlFlag, &error) else {
-+      let errorCode = error.map { CFErrorGetCode($0.takeRetainedValue()) }
-+      throw SecAccessControlError(errorCode)
-+    }
-+
-+    return accessOptions
-+  }
-+
-   private func set(value: String, with key: String, options: SecureStoreOptions) throws -> Bool {
-     var setItemQuery = query(with: key, options: options, requireAuthentication: options.requireAuthentication)
-
-@@ -107,21 +156,8 @@ public final class SecureStoreModule: Module {
-     if !options.requireAuthentication {
-       setItemQuery[kSecAttrAccessible as String] = accessibility
-     } else {
--      if (!options.enableDeviceFallback) {
--        guard let _ = Bundle.main.infoDictionary?["NSFaceIDUsageDescription"] as? String else {
--          throw MissingPlistKeyException()
--        }
--      }
--
--      var error: Unmanaged<CFError>? = nil
--
--      let accessControlFlag: SecAccessControlCreateFlags = options.enableDeviceFallback ? .userPresence : .biometryCurrentSet
--
--      guard let accessOptions = SecAccessControlCreateWithFlags(kCFAllocatorDefault, accessibility, accessControlFlag, &error) else {
--        let errorCode = error.map { CFErrorGetCode($0.takeRetainedValue()) }
--        throw SecAccessControlError(errorCode)
--      }
--      setItemQuery[kSecAttrAccessControl as String] = accessOptions
-+      try NSFaceIDUsageEntryGuard(options: options)
-+      setItemQuery[kSecAttrAccessControl as String] = try getAccessOptions(options: options, accessibility: accessibility)
-     }
-
-     let status = SecItemAdd(setItemQuery as CFDictionary, nil)
+   private func get(with key: String, options: SecureStoreOptions) throws -> String? {
+     guard let key = validate(for: key) else {
+       throw InvalidKeyException()
 diff --git a/node_modules/expo-secure-store/ios/SecureStoreOptions.swift b/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
-index c9e843b..13e15be 100644
+index c9e843b..b95eca7 100644
 --- a/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
 +++ b/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
-@@ -19,3 +19,32 @@ internal struct SecureStoreOptions: Record {
+@@ -18,4 +18,71 @@ internal struct SecureStoreOptions: Record {
+ 
    @Field
    var enableDeviceFallback: Bool = false
- }
++
++  @Field
++  var returnUsedAuthenticationType: Bool = false
++}
 +
 +@available(iOS 11.2, *)
 +public enum AuthType: Int, @unchecked Sendable {
@@ -681,65 +757,109 @@ index c9e843b..13e15be 100644
 +  case opticID = 5
 +}
 +
-+struct SecureStoreFeedback<T> {
-+  var value: T
++public enum SecureStoreFeedbackAction: String {
++  case set
++  case get
++}
++
++protocol SecureStoreFeedback {
++  associatedtype Source
++  associatedtype Value
++  var source: Source { get }
++  var authType: Int { get }
++  var value: Value { get }
++}
++
++struct SecureStoreGetFeedback<T>: SecureStoreFeedback {
++  typealias Source = T
++  typealias Value = Array<Any>
++  var source: Source
++  var authType: Int = AuthType.none.rawValue
++  var value: Value {
++    get { return [source, authType] }
++  }
++}
++
++struct SecureStoreOriginalFeedback<T>: SecureStoreFeedback {
++  typealias Source = T
++  typealias Value = T
++  var source: Source
 +  var authType: Int = AuthType.none.rawValue
-+  var values: Array<Any> {
-+    get { return [value, authType] }
++  var value: Value {
++    get { return source }
 +  }
 +}
++
++struct SecureStoreSetFeedback<T>: SecureStoreFeedback {
++  typealias Source = T
++  typealias Value = Int
++  var source: Source
++  var authType: Int = AuthType.none.rawValue
++  var value: Value {
++    get { return authType }
++  }
+ }
 diff --git a/node_modules/expo-secure-store/src/SecureStore.ts b/node_modules/expo-secure-store/src/SecureStore.ts
-index 4f253a1..c1cc118 100644
+index 4f253a1..e32ebdf 100644
 --- a/node_modules/expo-secure-store/src/SecureStore.ts
 +++ b/node_modules/expo-secure-store/src/SecureStore.ts
-@@ -3,6 +3,54 @@ import { byteCountOverLimit, VALUE_BYTES_LIMIT } from './byteCounter';
-
+@@ -1,8 +1,63 @@
+ import ExpoSecureStore from './ExpoSecureStore';
+ import { byteCountOverLimit, VALUE_BYTES_LIMIT } from './byteCounter';
+ 
++type EmptyObject = Record<string, never>;
++type SecureStoreSetFeedback<R extends SecureStoreOptions> =
++    R['returnUsedAuthenticationType'] extends true ? AuthType : void;
++type SecureStoreGetFeedback<
++    T,
++    R extends SecureStoreOptions,
++> = R['returnUsedAuthenticationType'] extends true ? [T | null, AuthType] : T | null;
  export type KeychainAccessibilityConstant = number;
-
+ 
 +/**
 + * Authentication type returned by the SecureStore after reading item or saving it to the store.
 + */
 +export const AUTH_TYPE = {
-+  /**
-+   * This is purely for backwards compatibility.
-+   * Although it is not listed as a return value of the getAuthenticationType() method,
-+   * it is still present in the Android code.
-+   * @see https://developer.android.com/reference/android/hardware/biometrics/BiometricPrompt.AuthenticationResult#getAuthenticationType()
-+   * @see https://developer.android.com/reference/androidx/biometric/BiometricPrompt#AUTHENTICATION_RESULT_TYPE_UNKNOWN()
-+   * @platform android
-+   */
-+  UNKNOWN: -1,
-+  /**
-+   * Returned when the authentication fails
-+   * @platform android
-+   * @platform ios
-+   */
-+  NONE: 0,
-+  /**
-+   * Generic type, not specified whether it was a passcode or pattern.
-+   * @platform android
-+   * @platform ios
-+   */
-+  CREDENTIALS: 1,
-+  /**
-+   * Generic type, not specified whether it was a face scan or a fingerprint
-+   * @platform android
-+   */
-+  BIOMETRICS: 2,
-+  /**
-+   * FaceID was used to authenticate
-+   * @platform ios
-+   */
-+  FACE_ID: 3,
-+  /**
-+   * TouchID was used to authenticate
-+   * @platform ios
-+   */
-+  TOUCH_ID: 4,
-+  /**
-+   * OpticID was used to authenticate (reserved by apple, used on Apple Vision Pro, not iOS)
-+   */
-+  OPTIC_ID: 5
++    /**
++     * This is purely for backwards compatibility.
++     * Although it is not listed as a return value of the getAuthenticationType() method,
++     * it is still present in the Android code.
++     * @see https://developer.android.com/reference/android/hardware/biometrics/BiometricPrompt.AuthenticationResult#getAuthenticationType()
++     * @see https://developer.android.com/reference/androidx/biometric/BiometricPrompt#AUTHENTICATION_RESULT_TYPE_UNKNOWN()
++     * @platform android
++     */
++    UNKNOWN: -1,
++    /**
++     * Returned when the authentication fails
++     * @platform android
++     * @platform ios
++     */
++    NONE: 0,
++    /**
++     * Generic type, not specified whether it was a passcode or pattern.
++     * @platform android
++     * @platform ios
++     */
++    CREDENTIALS: 1,
++    /**
++     * Generic type, not specified whether it was a face scan or a fingerprint
++     * @platform android
++     */
++    BIOMETRICS: 2,
++    /**
++     * FaceID was used to authenticate
++     * @platform ios
++     */
++    FACE_ID: 3,
++    /**
++     * TouchID was used to authenticate
++     * @platform ios
++     */
++    TOUCH_ID: 4,
++    /**
++     * OpticID was used to authenticate (reserved by apple, used on Apple Vision Pro, not iOS)
++     */
++    OPTIC_ID: 5,
 +} as const;
 +
 +type AuthType = (typeof AUTH_TYPE)[keyof typeof AUTH_TYPE];
@@ -747,48 +867,312 @@ index 4f253a1..c1cc118 100644
  // @needsAudit
  /**
   * The data in the keychain item cannot be accessed after a restart until the device has been
-@@ -162,7 +210,7 @@ export async function deleteItemAsync(
- export async function getItemAsync(
-   key: string,
-   options: SecureStoreOptions = {}
+@@ -17,7 +72,7 @@ export const AFTER_FIRST_UNLOCK: KeychainAccessibilityConstant = ExpoSecureStore
+  * from a backup.
+  */
+ export const AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY: KeychainAccessibilityConstant =
+-  ExpoSecureStore.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY;
++    ExpoSecureStore.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY;
+ 
+ // @needsAudit
+ /**
+@@ -34,7 +89,7 @@ export const ALWAYS: KeychainAccessibilityConstant = ExpoSecureStore.ALWAYS;
+  * store an entry. If the user removes their passcode, the entry will be deleted.
+  */
+ export const WHEN_PASSCODE_SET_THIS_DEVICE_ONLY: KeychainAccessibilityConstant =
+-  ExpoSecureStore.WHEN_PASSCODE_SET_THIS_DEVICE_ONLY;
++    ExpoSecureStore.WHEN_PASSCODE_SET_THIS_DEVICE_ONLY;
+ 
+ // @needsAudit
+ /**
+@@ -43,7 +98,7 @@ export const WHEN_PASSCODE_SET_THIS_DEVICE_ONLY: KeychainAccessibilityConstant =
+  * @deprecated Use an accessibility level that provides some user protection, such as `AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY`.
+  */
+ export const ALWAYS_THIS_DEVICE_ONLY: KeychainAccessibilityConstant =
+-  ExpoSecureStore.ALWAYS_THIS_DEVICE_ONLY;
++    ExpoSecureStore.ALWAYS_THIS_DEVICE_ONLY;
+ 
+ // @needsAudit
+ /**
+@@ -57,62 +112,80 @@ export const WHEN_UNLOCKED: KeychainAccessibilityConstant = ExpoSecureStore.WHEN
+  * a backup.
+  */
+ export const WHEN_UNLOCKED_THIS_DEVICE_ONLY: KeychainAccessibilityConstant =
+-  ExpoSecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY;
++    ExpoSecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY;
+ 
+ // @needsAudit
+ export type SecureStoreOptions = {
+-  /**
+-   * - Android: Equivalent of the public/private key pair `Alias`.
+-   * - iOS: The item's service, equivalent to [`kSecAttrService`](https://developer.apple.com/documentation/security/ksecattrservice/).
+-   * > If the item is set with the `keychainService` option, it will be required to later fetch the value.
+-   */
+-  keychainService?: string;
+-  /**
+-   * Option responsible for enabling the usage of the user authentication methods available on the device while
+-   * accessing data stored in SecureStore.
+-   * - Android: Equivalent to [`setUserAuthenticationRequired(true)`](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setUserAuthenticationRequired(boolean))
+-   *   (requires API 23).
+-   * - iOS: Equivalent to [`biometryCurrentSet`](https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/2937192-biometrycurrentset).
+-   * Complete functionality is unlocked only with a freshly generated key - this would not work in tandem with the `keychainService`
+-   * value used for the others non-authenticated operations.
+-   *
+-   * This option works slightly differently across platforms: On Android, user authentication is required for all operations.
+-   * On iOS, the user is prompted to authenticate only when reading or updating an existing value (not when creating a new one).
+-   *
+-   * Warning: This option is not supported in Expo Go when biometric authentication is available due to a missing NSFaceIDUsageDescription.
+-   * In release builds or when using continuous native generation, make sure to use the `expo-secure-store` config plugin.
+-   *
+-   */
+-  requireAuthentication?: boolean;
+-  /**
+-   * Custom message displayed to the user while `requireAuthentication` option is turned on.
+-   */
+-  authenticationPrompt?: string;
+-  /**
+-   * Specifies when the stored entry is accessible, using iOS's `kSecAttrAccessible` property.
+-   * @see Apple's documentation on [keychain item accessibility](https://developer.apple.com/documentation/security/ksecattraccessible/).
+-   * @default SecureStore.WHEN_UNLOCKED
+-   * @platform ios
+-   */
+-  keychainAccessible?: KeychainAccessibilityConstant;
++    /**
++     * - Android: Equivalent of the public/private key pair `Alias`.
++     * - iOS: The item's service, equivalent to [`kSecAttrService`](https://developer.apple.com/documentation/security/ksecattrservice/).
++     * > If the item is set with the `keychainService` option, it will be required to later fetch the value.
++     */
++    keychainService?: string;
++    /**
++     * Option responsible for enabling the usage of the user authentication methods available on the device while
++     * accessing data stored in SecureStore.
++     * - Android: Equivalent to [`setUserAuthenticationRequired(true)`](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setUserAuthenticationRequired(boolean))
++     *   (requires API 23).
++     * - iOS: Equivalent to [`biometryCurrentSet`](https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/2937192-biometrycurrentset).
++     * Complete functionality is unlocked only with a freshly generated key - this would not work in tandem with the `keychainService`
++     * value used for the others non-authenticated operations.
++     *
++     * This option works slightly differently across platforms: On Android, user authentication is required for all operations.
++     * On iOS, the user is prompted to authenticate only when reading or updating an existing value (not when creating a new one).
++     *
++     * Warning: This option is not supported in Expo Go when biometric authentication is available due to a missing NSFaceIDUsageDescription.
++     * In release builds or when using continuous native generation, make sure to use the `expo-secure-store` config plugin.
++     *
++     * > **Note:** This library requires a real device for testing since emulators/simulators do not require biometric authentication when retrieving secrets, unlike real iOS devices.
++     */
++    requireAuthentication?: boolean;
++    /**
++     * Custom message displayed to the user while `requireAuthentication` option is turned on.
++     */
++    authenticationPrompt?: string;
++    /**
++     * Specifies when the stored entry is accessible, using iOS's `kSecAttrAccessible` property.
++     * @see Apple's documentation on [keychain item accessibility](https://developer.apple.com/documentation/security/ksecattraccessible/).
++     * @default SecureStore.WHEN_UNLOCKED
++     * @platform ios
++     */
++    keychainAccessible?: KeychainAccessibilityConstant;
++
++    /**
++     * Specifies the access group the stored entry belongs to.
++     * @see Apple's documentation on [Sharing access to keychain items among a collection of apps](https://developer.apple.com/documentation/security/sharing-access-to-keychain-items-among-a-collection-of-apps).
++     * @platform ios
++     */
++    accessGroup?: string;
+ 
+-  /**
+-   * Specifies the access group the stored entry belongs to.
+-   * @see Apple's documentation on [Sharing access to keychain items among a collection of apps](https://developer.apple.com/documentation/security/sharing-access-to-keychain-items-among-a-collection-of-apps).
+-   * @platform ios
+-   */
+-  accessGroup?: string;
++    /**
++     * This flag enables users to authenticate using Lock Screen Knowledge Factor (e.g. PIN, pattern or password).
++     * For sensitive apps, it is recommended not having biometric fall back to such factor.
++     * @see: https://developer.android.com/security/fraud-prevention/authentication
++     *
++     * @default false
++     * @platform android
++     * @platform ios
++     */
++    enableDeviceFallback?: boolean;
+ 
+-  /**
+-   * This flag enables users to authenticate using Lock Screen Knowledge Factor (e.g. PIN, pattern or password).
+-   * For sensitive apps, it is recommended not having biometric fall back to such factor.
+-   * @see: https://developer.android.com/security/fraud-prevention/authentication
+-   *
+-   * @default false
+-   * @platform android
+-   * @platform ios
+-   */
+-  enableDeviceFallback?: boolean;
++    /**
++     * When this flag is set to true, the get methods of SecureStore will return a two-element array. The first value will be the original value returned when this flag is set to false.
++     * The second value is the authentication type used to read the value from the AUTH_TYPE object.
++     * As for the set function, the returned value will simply be AUTH_TYPE.
++     *
++     * @warning
++     * If the iOS device supports biometrics and the user falls back to device credentials, it will not be detected.
++     * This is not the case on Android, but we cannot specify the exact type of biometrics (e.g. fingerprint or face scan).
++     * Whether the type is detected correctly depends on the platform and its native implementation.
++     * This should be treated as more of a hint.
++     *
++     * @default false
++     * @platform android
++     * @platform ios
++     */
++    returnUsedAuthenticationType?: boolean;
+ };
+ 
+ // @needsAudit
+@@ -124,7 +197,7 @@ export type SecureStoreOptions = {
+  * on the current device. Currently, this resolves `true` on Android and iOS only.
+  */
+ export async function isAvailableAsync(): Promise<boolean> {
+-  return !!ExpoSecureStore.getValueWithKeyAsync;
++    return !!ExpoSecureStore.getValueWithKeyAsync;
+ }
+ 
+ // @needsAudit
+@@ -137,12 +210,12 @@ export async function isAvailableAsync(): Promise<boolean> {
+  * @return A promise that rejects if the value can't be deleted.
+  */
+ export async function deleteItemAsync(
+-  key: string,
+-  options: SecureStoreOptions = {}
++    key: string,
++    options: SecureStoreOptions = {}
+ ): Promise<void> {
+-  ensureValidKey(key);
++    ensureValidKey(key);
+ 
+-  await ExpoSecureStore.deleteValueWithKeyAsync(key, options);
++    await ExpoSecureStore.deleteValueWithKeyAsync(key, options);
+ }
+ 
+ // @needsAudit
+@@ -159,12 +232,12 @@ export async function deleteItemAsync(
+  * > After a key has been invalidated, it becomes impossible to read its value.
+  * > This only applies to values stored with `requireAuthentication` set to `true`.
+  */
+-export async function getItemAsync(
+-  key: string,
+-  options: SecureStoreOptions = {}
 -): Promise<string | null> {
-+): Promise<[string | null, AuthType]> {
-   ensureValidKey(key);
-   return await ExpoSecureStore.getValueWithKeyAsync(key, options);
+-  ensureValidKey(key);
+-  return await ExpoSecureStore.getValueWithKeyAsync(key, options);
++export async function getItemAsync<R extends SecureStoreOptions>(
++    key: string,
++    options: R | EmptyObject = {}
++): Promise<SecureStoreGetFeedback<string, R>> {
++    ensureValidKey(key);
++    return await ExpoSecureStore.getValueWithKeyAsync(key, options);
  }
-@@ -181,7 +229,7 @@ export async function setItemAsync(
-   key: string,
-   value: string,
-   options: SecureStoreOptions = {}
+ 
+ // @needsAudit
+@@ -172,24 +245,24 @@ export async function getItemAsync(
+  * Stores a key–value pair.
+  *
+  * @param key The key to associate with the stored value. Keys may contain alphanumeric characters, `.`, `-`, and `_`.
+- * @param value The value to store. Size limit is 2048 bytes.
++ * @param value The value to store.
+  * @param options An [`SecureStoreOptions`](#securestoreoptions) object.
+  *
+  * @return A promise that rejects if value cannot be stored on the device.
+  */
+-export async function setItemAsync(
+-  key: string,
+-  value: string,
+-  options: SecureStoreOptions = {}
 -): Promise<void> {
-+): Promise<AuthType> {
-   ensureValidKey(key);
-   if (!isValidValue(value)) {
-     throw new Error(
-@@ -189,7 +237,7 @@ export async function setItemAsync(
-     );
-   }
-
+-  ensureValidKey(key);
+-  if (!isValidValue(value)) {
+-    throw new Error(
+-      `Invalid value provided to SecureStore. Values must be strings; consider JSON-encoding your values if they are serializable.`
+-    );
+-  }
++export async function setItemAsync<R extends SecureStoreOptions>(
++    key: string,
++    value: string,
++    options: R | EmptyObject = {}
++): Promise<SecureStoreSetFeedback<R>> {
++    ensureValidKey(key);
++    if (!isValidValue(value)) {
++        throw new Error(
++            `Invalid value provided to SecureStore. Values must be strings; consider JSON-encoding your values if they are serializable.`
++        );
++    }
+ 
 -  await ExpoSecureStore.setValueWithKeyAsync(value, key, options);
-+  return await ExpoSecureStore.setValueWithKeyAsync(value, key, options);
++    return await ExpoSecureStore.setValueWithKeyAsync(value, key, options);
  }
-
+ 
  /**
-@@ -201,7 +249,7 @@ export async function setItemAsync(
+@@ -197,19 +270,23 @@ export async function setItemAsync(
+  * > **Note:** This function blocks the JavaScript thread, so the application may not be interactive when the `requireAuthentication` option is set to `true` until the user authenticates.
+  *
+  * @param key The key to associate with the stored value. Keys may contain alphanumeric characters, `.`, `-`, and `_`.
+- * @param value The value to store. Size limit is 2048 bytes.
++ * @param value The value to store.
   * @param options An [`SecureStoreOptions`](#securestoreoptions) object.
   *
   */
 -export function setItem(key: string, value: string, options: SecureStoreOptions = {}): void {
-+export function setItem(key: string, value: string, options: SecureStoreOptions = {}): AuthType {
-   ensureValidKey(key);
-   if (!isValidValue(value)) {
-     throw new Error(
-@@ -222,7 +270,7 @@ export function setItem(key: string, value: string, options: SecureStoreOptions
+-  ensureValidKey(key);
+-  if (!isValidValue(value)) {
+-    throw new Error(
+-      `Invalid value provided to SecureStore. Values must be strings; consider JSON-encoding your values if they are serializable.`
+-    );
+-  }
++export function setItem<R extends SecureStoreOptions>(
++    key: string,
++    value: string,
++    options: R | EmptyObject = {}
++): SecureStoreSetFeedback<R> {
++    ensureValidKey(key);
++    if (!isValidValue(value)) {
++        throw new Error(
++            `Invalid value provided to SecureStore. Values must be strings; consider JSON-encoding your values if they are serializable.`
++        );
++    }
+ 
+-  return ExpoSecureStore.setValueWithKeySync(value, key, options);
++    return ExpoSecureStore.setValueWithKeySync(value, key, options);
+ }
+ 
+ /**
+@@ -222,9 +299,12 @@ export function setItem(key: string, value: string, options: SecureStoreOptions
   * @return Previously stored value. It resolves with `null` if there is no entry
   * for the given key or if the key has been invalidated.
   */
 -export function getItem(key: string, options: SecureStoreOptions = {}): string | null {
-+export function getItem(key: string, options: SecureStoreOptions = {}): [string | null, AuthType] {
-   ensureValidKey(key);
-   return ExpoSecureStore.getValueWithKeySync(key, options);
+-  ensureValidKey(key);
+-  return ExpoSecureStore.getValueWithKeySync(key, options);
++export function getItem<R extends SecureStoreOptions>(
++    key: string,
++    options: R | EmptyObject = {}
++): SecureStoreGetFeedback<string, R> {
++    ensureValidKey(key);
++    return ExpoSecureStore.getValueWithKeySync(key, options);
+ }
+ 
+ /**
+@@ -234,7 +314,7 @@ export function getItem(key: string, options: SecureStoreOptions = {}): string |
+  * @platform ios
+  */
+ export function canUseBiometricAuthentication(): boolean {
+-  return ExpoSecureStore.canUseBiometricAuthentication();
++    return ExpoSecureStore.canUseBiometricAuthentication();
+ }
+ 
+ /**
+@@ -244,7 +324,7 @@ export function canUseBiometricAuthentication(): boolean {
+  * @platform ios
+  */
+ export function canUseDeviceCredentialsAuthentication(): boolean {
+-  return ExpoSecureStore.canUseDeviceCredentialsAuthentication();
++    return ExpoSecureStore.canUseDeviceCredentialsAuthentication();
  }
+ 
+ function ensureValidKey(key: string) {
diff --git a/patches/expo-secure-store/expo-secure-store+14.2.4+003+force-authentication-on-save.patch b/patches/expo-secure-store/expo-secure-store+14.2.4+003+force-authentication-on-save.patch
index bc17960405a2..fc085c205b11 100644
--- a/patches/expo-secure-store/expo-secure-store+14.2.4+003+force-authentication-on-save.patch
+++ b/patches/expo-secure-store/expo-secure-store+14.2.4+003+force-authentication-on-save.patch
@@ -1,11 +1,11 @@
 diff --git a/node_modules/expo-secure-store/build/SecureStore.d.ts b/node_modules/expo-secure-store/build/SecureStore.d.ts
-index 77895d0..2bb1b8f 100644
+index 40ec045..be49e3b 100644
 --- a/node_modules/expo-secure-store/build/SecureStore.d.ts
 +++ b/node_modules/expo-secure-store/build/SecureStore.d.ts
-@@ -137,6 +137,20 @@ export type SecureStoreOptions = {
+@@ -154,6 +154,20 @@ export type SecureStoreOptions = {
       * @platform ios
       */
-     enableDeviceFallback?: boolean;
+     returnUsedAuthenticationType?: boolean;
 +    /**
 +     * On iOS, the system does not ask for auth when saving the value to the SecureStore.
 +     * The Android however, displays the authentication prompt when saving the value.
@@ -24,21 +24,21 @@ index 77895d0..2bb1b8f 100644
  /**
   * Returns whether the SecureStore API is enabled on the current device. This does not check the app
 diff --git a/node_modules/expo-secure-store/ios/SecureStoreModule.swift b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
-index e4c3f2b..9848dc1 100644
+index 5280020..3060cbd 100644
 --- a/node_modules/expo-secure-store/ios/SecureStoreModule.swift
 +++ b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
-@@ -31,6 +31,10 @@ public final class SecureStoreModule: Module {
+@@ -33,6 +33,10 @@ public final class SecureStoreModule: Module {
          throw InvalidKeyException()
        }
  
-+      if options.requireAuthentication && options.forceAuthenticationOnSave {
++     if options.requireAuthentication && options.forceAuthenticationOnSave {
 +        try await triggerPolicy(options: options)
 +      }
 +
        let result = try set(value: value, with: key, options: options)
  
-       if !result {
-@@ -73,6 +77,32 @@ public final class SecureStoreModule: Module {
+       return wrapResultWithFeedback(action: .set, result: result, options: options).value
+@@ -188,6 +192,32 @@ public final class SecureStoreModule: Module {
      }
    }
  
@@ -62,36 +62,31 @@ index e4c3f2b..9848dc1 100644
 +        }
 +      }
 +    }
-+    
++
 +    guard success else {
 +      throw SecureStoreRuntimeError("Unable to authenticate")
 +    }
 +  }
 +
-   private func getAuthType() -> AuthType {
-     if !areBiometricsEnabled() {return AuthType.credentials}
-     let biometryType = LAContext().biometryType
-@@ -269,3 +299,4 @@ public final class SecureStoreModule: Module {
-     return key
-   }
- }
-+
+   private func searchKeyChain(with key: String, options: SecureStoreOptions, requireAuthentication: Bool? = nil) throws -> Data? {
+     var query = query(with: key, options: options, requireAuthentication: requireAuthentication)
+ 
 diff --git a/node_modules/expo-secure-store/ios/SecureStoreOptions.swift b/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
-index 13e15be..504c36b 100644
+index b95eca7..321c7e5 100644
 --- a/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
 +++ b/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
-@@ -18,6 +18,9 @@ internal struct SecureStoreOptions: Record {
+@@ -21,6 +21,9 @@ internal struct SecureStoreOptions: Record {
  
    @Field
-   var enableDeviceFallback: Bool = false
+   var returnUsedAuthenticationType: Bool = false
 +
 +  @Field
 +  var forceAuthenticationOnSave: Bool = false
  }
  
  @available(iOS 11.2, *)
-@@ -48,3 +51,10 @@ struct SecureStoreFeedback<T> {
-     get { return [value, authType] }
+@@ -86,3 +89,10 @@ struct SecureStoreSetFeedback<T>: SecureStoreFeedback {
+     get { return authType }
    }
  }
 +
@@ -102,28 +97,28 @@ index 13e15be..504c36b 100644
 +  }
 +}
 diff --git a/node_modules/expo-secure-store/src/SecureStore.ts b/node_modules/expo-secure-store/src/SecureStore.ts
-index c1cc118..1f2ed59 100644
+index e32ebdf..3fbaf02 100644
 --- a/node_modules/expo-secure-store/src/SecureStore.ts
 +++ b/node_modules/expo-secure-store/src/SecureStore.ts
-@@ -161,6 +161,21 @@ export type SecureStoreOptions = {
-    * @platform ios
-    */
-   enableDeviceFallback?: boolean;
+@@ -186,6 +186,21 @@ export type SecureStoreOptions = {
+      * @platform ios
+      */
+     returnUsedAuthenticationType?: boolean;
 +
-+  /**
-+   * On iOS, the system does not ask for auth when saving the value to the SecureStore.
-+   * The Android however, displays the authentication prompt when saving the value.
-+   * To keep the behavior on every platform similar as much as possible,
-+   * setting this flag to true will ensure that authentication is required when saving a value to the store.
-+   *
-+   * @warning: This flag only works for the asynchronous version of the SecureStore save method.
-+   * It should only be considered an improvement to the user experience; it does not prevent the user
-+   * from saving the value without authenticating using other methods (e.g. by directly modifying the keychain).
-+   * For it to take effect, the 'requireAuthentication' flag must be set to true.
-+   * @default false
-+   * @platform ios
-+   */
-+  forceAuthenticationOnSave?: boolean;
++    /**
++     * On iOS, the system does not ask for auth when saving the value to the SecureStore.
++     * The Android however, displays the authentication prompt when saving the value.
++     * To keep the behavior on every platform similar as much as possible,
++     * setting this flag to true will ensure that authentication is required when saving a value to the store.
++     *
++     * @warning: This flag only works for the asynchronous version of the SecureStore save method.
++     * It should only be considered an improvement to the user experience; it does not prevent the user
++     * from saving the value without authenticating using other methods (e.g. by directly modifying the keychain).
++     * For it to take effect, the 'requireAuthentication' flag must be set to true.
++     * @default false
++     * @platform ios
++     */
++    forceAuthenticationOnSave?: boolean;
  };
  
  // @needsAudit
diff --git a/patches/expo-secure-store/expo-secure-store+14.2.4+004+fail-on-update.patch b/patches/expo-secure-store/expo-secure-store+14.2.4+004+fail-on-update.patch
index 8b912f97d1e1..175b02cb71d8 100644
--- a/patches/expo-secure-store/expo-secure-store+14.2.4+004+fail-on-update.patch
+++ b/patches/expo-secure-store/expo-secure-store+14.2.4+004+fail-on-update.patch
@@ -1,9 +1,9 @@
 diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt
-index 866d47d..1be213f 100644
+index d209efc..e362aef 100644
 --- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt
 +++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt
-@@ -196,6 +196,10 @@ open class SecureStoreModule : Module() {
-       return SecureStoreAuthType.NONE
+@@ -212,6 +212,10 @@ open class SecureStoreModule : Module() {
+       return SecureStoreOriginalFeedback(null)
      }
  
 +    if (prefs.contains(keychainAwareKey) && options.failOnUpdate) {
@@ -14,26 +14,27 @@ index 866d47d..1be213f 100644
        if (keyIsInvalidated) {
          // Invalidated keys will block writing even though it's not possible to re-validate them
 diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt
-index d38650c..3ce02db 100644
+index 4e30440..16674b8 100644
 --- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt
 +++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt
-@@ -10,6 +10,7 @@ class SecureStoreOptions(
-   @Field var authenticationPrompt: String = " ",
+@@ -11,7 +11,8 @@ class SecureStoreOptions(
    @Field var keychainService: String = SecureStoreModule.DEFAULT_KEYSTORE_ALIAS,
    @Field var requireAuthentication: Boolean = false,
-+  @Field var failOnUpdate: Boolean = false,
-   @Field var enableDeviceFallback: Boolean = false
+   @Field var enableDeviceFallback: Boolean = false,
+-  @Field var returnUsedAuthenticationType: Boolean = false
++  @Field var returnUsedAuthenticationType: Boolean = false,
++  @Field var failOnUpdate: Boolean = false
  ) : Record, Serializable
  
+ enum class SecureStoreAuthType(index: Int) {
 diff --git a/node_modules/expo-secure-store/build/SecureStore.d.ts b/node_modules/expo-secure-store/build/SecureStore.d.ts
-index d6aa1c8..b920e4d 100644
+index 4543001..0f3ef89 100644
 --- a/node_modules/expo-secure-store/build/SecureStore.d.ts
 +++ b/node_modules/expo-secure-store/build/SecureStore.d.ts
-@@ -151,6 +151,17 @@ export type SecureStoreOptions = {
+@@ -168,6 +168,16 @@ export type SecureStoreOptions = {
       * @platform ios
       */
      forceAuthenticationOnSave?: boolean;
-+
 +    /**
 +     * If the key has already been stored, the save function will throw an error instead of overwriting it.
 +     * The behaviour differs slightly depending on the platform.
@@ -48,24 +49,24 @@ index d6aa1c8..b920e4d 100644
  /**
   * Returns whether the SecureStore API is enabled on the current device. This does not check the app
 diff --git a/node_modules/expo-secure-store/ios/SecureStoreModule.swift b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
-index 9848dc1..3ecabf8 100644
+index 3060cbd..5a81b11 100644
 --- a/node_modules/expo-secure-store/ios/SecureStoreModule.swift
 +++ b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
-@@ -199,6 +199,9 @@ public final class SecureStoreModule: Module {
+@@ -167,6 +167,9 @@ public final class SecureStoreModule: Module {
        SecItemDelete(query(with: key, options: options, requireAuthentication: !options.requireAuthentication) as CFDictionary)
        return true
      case errSecDuplicateItem:
-+      if options.failOnUpdate {
++     if options.failOnUpdate {
 +        throw SecureStoreRuntimeError("Key already exists")
 +      }
        return try update(value: value, with: key, options: options)
      default:
        throw KeyChainException(status)
 diff --git a/node_modules/expo-secure-store/ios/SecureStoreOptions.swift b/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
-index 504c36b..4c490b1 100644
+index 321c7e5..6a787b0 100644
 --- a/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
 +++ b/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
-@@ -21,6 +21,9 @@ internal struct SecureStoreOptions: Record {
+@@ -24,6 +24,9 @@ internal struct SecureStoreOptions: Record {
  
    @Field
    var forceAuthenticationOnSave: Bool = false
@@ -76,24 +77,32 @@ index 504c36b..4c490b1 100644
  
  @available(iOS 11.2, *)
 diff --git a/node_modules/expo-secure-store/src/SecureStore.ts b/node_modules/expo-secure-store/src/SecureStore.ts
-index 1f2ed59..db64fd2 100644
+index 3fbaf02..9c3463c 100644
 --- a/node_modules/expo-secure-store/src/SecureStore.ts
 +++ b/node_modules/expo-secure-store/src/SecureStore.ts
-@@ -176,6 +176,17 @@ export type SecureStoreOptions = {
-    * @platform ios
-    */
-   forceAuthenticationOnSave?: boolean;
+@@ -201,6 +201,17 @@ export type SecureStoreOptions = {
+      * @platform ios
+      */
+     forceAuthenticationOnSave?: boolean;
 +
-+  /**
-+   * If the key has already been stored, the save function will throw an error instead of overwriting it.
-+   * The behaviour differs slightly depending on the platform.
-+   * On Android, an error is thrown before the authentication prompt. On iOS, an error is thrown after authentication.
-+   *
-+   * @default false
-+   * @platform ios
-+   * @platform android
-+   */
-+  failOnUpdate?: boolean;
++    /**
++     * If the key has already been stored, the save function will throw an error instead of overwriting it.
++     * The behaviour differs slightly depending on the platform.
++     * On Android, an error is thrown before the authentication prompt. On iOS, an error is thrown after authentication.
++     *
++     * @default false
++     * @platform ios
++     * @platform android
++     */
++    failOnUpdate?: boolean;
  };
  
  // @needsAudit
+@@ -341,7 +352,6 @@ export function canUseBiometricAuthentication(): boolean {
+ export function canUseDeviceCredentialsAuthentication(): boolean {
+     return ExpoSecureStore.canUseDeviceCredentialsAuthentication();
+ }
+-
+ function ensureValidKey(key: string) {
+   if (!isValidKey(key)) {
+     throw new Error(
diff --git a/patches/expo-secure-store/expo-secure-store+14.2.4+005+force-read-authentication-on-simulators.patch b/patches/expo-secure-store/expo-secure-store+14.2.4+005+force-read-authentication-on-simulators.patch
index 100829ae041c..062542442189 100644
--- a/patches/expo-secure-store/expo-secure-store+14.2.4+005+force-read-authentication-on-simulators.patch
+++ b/patches/expo-secure-store/expo-secure-store+14.2.4+005+force-read-authentication-on-simulators.patch
@@ -1,12 +1,11 @@
 diff --git a/node_modules/expo-secure-store/build/SecureStore.d.ts b/node_modules/expo-secure-store/build/SecureStore.d.ts
-index bf5290c..cc981d3 100644
+index a6856a9..59bdee0 100644
 --- a/node_modules/expo-secure-store/build/SecureStore.d.ts
 +++ b/node_modules/expo-secure-store/build/SecureStore.d.ts
-@@ -162,6 +162,19 @@ export type SecureStoreOptions = {
+@@ -178,6 +178,18 @@ export type SecureStoreOptions = {
       * @platform android
       */
      failOnUpdate?: boolean;
-+
 +    /**
 +     * The LocalAuthentication behaves slightly differently on iOS simulators.
 +     * In numerous cases, the authentication prompts are skipped on simulators (as opposed to real devices).
@@ -23,11 +22,11 @@ index bf5290c..cc981d3 100644
  /**
   * Returns whether the SecureStore API is enabled on the current device. This does not check the app
 diff --git a/node_modules/expo-secure-store/ios/SecureStoreModule.swift b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
-index 4bfe51f..cbc4615 100644
+index 5a81b11..35a339a 100644
 --- a/node_modules/expo-secure-store/ios/SecureStoreModule.swift
 +++ b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
-@@ -19,6 +19,11 @@ public final class SecureStoreModule: Module {
-     ])
+@@ -17,6 +17,11 @@ public final class SecureStoreModule: Module {
+     Constant("WHEN_UNLOCKED_THIS_DEVICE_ONLY") { SecureStoreAccessible.whenUnlockedThisDeviceOnly.rawValue }
  
      AsyncFunction("getValueWithKeyAsync") { (key: String, options: SecureStoreOptions) in
 +      #if targetEnvironment(simulator)
@@ -35,14 +34,14 @@ index 4bfe51f..cbc4615 100644
 +          try await triggerPolicy(options: options)
 +        }
 +      #endif
-       return getSecureStoreFeedback(value: try get(with: key, options: options)).values
-     }
+       let result = try get(with: key, options: options)
  
+       return wrapResultWithFeedback(action: .get, result: result, options: options).value
 diff --git a/node_modules/expo-secure-store/ios/SecureStoreOptions.swift b/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
-index 4c490b1..aee90ed 100644
+index 6a787b0..8f110dd 100644
 --- a/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
 +++ b/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
-@@ -24,6 +24,9 @@ internal struct SecureStoreOptions: Record {
+@@ -27,6 +27,9 @@ internal struct SecureStoreOptions: Record {
  
    @Field
    var failOnUpdate: Bool = false
@@ -53,26 +52,34 @@ index 4c490b1..aee90ed 100644
  
  @available(iOS 11.2, *)
 diff --git a/node_modules/expo-secure-store/src/SecureStore.ts b/node_modules/expo-secure-store/src/SecureStore.ts
-index db64fd2..69541ce 100644
+index 9c3463c..52fc89d 100644
 --- a/node_modules/expo-secure-store/src/SecureStore.ts
 +++ b/node_modules/expo-secure-store/src/SecureStore.ts
-@@ -187,6 +187,19 @@ export type SecureStoreOptions = {
-    * @platform android
-    */
-   failOnUpdate?: boolean;
+@@ -212,6 +212,19 @@ export type SecureStoreOptions = {
+      * @platform android
+      */
+     failOnUpdate?: boolean;
 +
-+  /**
-+   * The LocalAuthentication behaves slightly differently on iOS simulators.
-+   * In numerous cases, the authentication prompts are skipped on simulators (as opposed to real devices).
-+   * Setting this flag to true forces the prompt to appear on simulators when a value with the `requireAuthentication` flag set to true is read.
-+   * This is purely for testing the app on simulators, in cases where the prompt does not appear when the value is read.
-+   * This has no effect on real devices.
-+   *
-+   * @warning: This flag only works for the asynchronous version of the SecureStore read method.
-+   * @default false
-+   * @platform ios
-+   */
-+  forceReadAuthenticationOnSimulators?: boolean;
++    /**
++     * The LocalAuthentication behaves slightly differently on iOS simulators.
++     * In numerous cases, the authentication prompts are skipped on simulators (as opposed to real devices).
++     * Setting this flag to true forces the prompt to appear on simulators when a value with the `requireAuthentication` flag set to true is read.
++     * This is purely for testing the app on simulators, in cases where the prompt does not appear when the value is read.
++     * This has no effect on real devices.
++     *
++     * @warning: This flag only works for the asynchronous version of the SecureStore read method.
++     * @default false
++     * @platform ios
++     */
++    forceReadAuthenticationOnSimulators?: boolean;
  };
  
  // @needsAudit
+@@ -352,6 +365,7 @@ export function canUseBiometricAuthentication(): boolean {
+ export function canUseDeviceCredentialsAuthentication(): boolean {
+     return ExpoSecureStore.canUseDeviceCredentialsAuthentication();
+ }
++
+ function ensureValidKey(key: string) {
+   if (!isValidKey(key)) {
+     throw new Error(

From 3635bd766e642efa39c46117d41e2fbb98efb8a0 Mon Sep 17 00:00:00 2001
From: Jakub Korytko <jakub@korytko.me>
Date: Tue, 2 Dec 2025 13:37:51 +0100
Subject: [PATCH 3/7] Remove whitespaces from patch files

---
 ...re-store+14.2.4+002+return-auth-type.patch | 409 +++---------------
 ...2.4+003+force-authentication-on-save.patch |  50 +--
 ...cure-store+14.2.4+004+fail-on-update.patch |  48 +-
 ...ce-read-authentication-on-simulators.patch |  52 +--
 4 files changed, 133 insertions(+), 426 deletions(-)

diff --git a/patches/expo-secure-store/expo-secure-store+14.2.4+002+return-auth-type.patch b/patches/expo-secure-store/expo-secure-store+14.2.4+002+return-auth-type.patch
index 8689d910d7eb..74b39840a335 100644
--- a/patches/expo-secure-store/expo-secure-store+14.2.4+002+return-auth-type.patch
+++ b/patches/expo-secure-store/expo-secure-store+14.2.4+002+return-auth-type.patch
@@ -374,7 +374,7 @@ index 39459ff..a531aff 100644
 +  ): SecureStoreOriginalFeedback<String>
  }
 diff --git a/node_modules/expo-secure-store/build/SecureStore.d.ts b/node_modules/expo-secure-store/build/SecureStore.d.ts
-index 835a4d4..7bfcc98 100644
+index 835a4d4..d2da8d9 100644
 --- a/node_modules/expo-secure-store/build/SecureStore.d.ts
 +++ b/node_modules/expo-secure-store/build/SecureStore.d.ts
 @@ -1,4 +1,53 @@
@@ -431,15 +431,7 @@ index 835a4d4..7bfcc98 100644
  /**
   * The data in the keychain item cannot be accessed after a restart until the device has been
   * unlocked once by the user. This may be useful if you need to access the item when the phone
-@@ -59,6 +108,7 @@ export type SecureStoreOptions = {
-      * Warning: This option is not supported in Expo Go when biometric authentication is available due to a missing NSFaceIDUsageDescription.
-      * In release builds or when using continuous native generation, make sure to use the `expo-secure-store` config plugin.
-      *
-+     * > **Note:** This library requires a real device for testing since emulators/simulators do not require biometric authentication when retrieving secrets, unlike real iOS devices.
-      */
-     requireAuthentication?: boolean;
-     /**
-@@ -88,6 +138,22 @@ export type SecureStoreOptions = {
+@@ -88,6 +137,22 @@ export type SecureStoreOptions = {
       * @platform ios
       */
      enableDeviceFallback?: boolean;
@@ -462,7 +454,7 @@ index 835a4d4..7bfcc98 100644
  };
  /**
   * Returns whether the SecureStore API is enabled on the current device. This does not check the app
-@@ -119,27 +185,27 @@ export declare function deleteItemAsync(key: string, options?: SecureStoreOption
+@@ -119,7 +184,7 @@ export declare function deleteItemAsync(key: string, options?: SecureStoreOption
   * > After a key has been invalidated, it becomes impossible to read its value.
   * > This only applies to values stored with `requireAuthentication` set to `true`.
   */
@@ -471,10 +463,7 @@ index 835a4d4..7bfcc98 100644
  /**
   * Stores a key–value pair.
   *
-  * @param key The key to associate with the stored value. Keys may contain alphanumeric characters, `.`, `-`, and `_`.
-- * @param value The value to store. Size limit is 2048 bytes.
-+ * @param value The value to store.
-  * @param options An [`SecureStoreOptions`](#securestoreoptions) object.
+@@ -129,7 +194,7 @@ export declare function getItemAsync(key: string, options?: SecureStoreOptions):
   *
   * @return A promise that rejects if value cannot be stored on the device.
   */
@@ -483,10 +472,7 @@ index 835a4d4..7bfcc98 100644
  /**
   * Stores a key–value pair synchronously.
   * > **Note:** This function blocks the JavaScript thread, so the application may not be interactive when the `requireAuthentication` option is set to `true` until the user authenticates.
-  *
-  * @param key The key to associate with the stored value. Keys may contain alphanumeric characters, `.`, `-`, and `_`.
-- * @param value The value to store. Size limit is 2048 bytes.
-+ * @param value The value to store.
+@@ -139,7 +204,7 @@ export declare function setItemAsync(key: string, value: string, options?: Secur
   * @param options An [`SecureStoreOptions`](#securestoreoptions) object.
   *
   */
@@ -495,7 +481,7 @@ index 835a4d4..7bfcc98 100644
  /**
   * Synchronously reads the stored value associated with the provided key.
   * > **Note:** This function blocks the JavaScript thread, so the application may not be interactive when reading a value with `requireAuthentication`
-@@ -150,7 +216,7 @@ export declare function setItem(key: string, value: string, options?: SecureStor
+@@ -150,7 +215,7 @@ export declare function setItem(key: string, value: string, options?: SecureStor
   * @return Previously stored value. It resolves with `null` if there is no entry
   * for the given key or if the key has been invalidated.
   */
@@ -504,14 +490,8 @@ index 835a4d4..7bfcc98 100644
  /**
   * Checks if the value can be saved with `requireAuthentication` option enabled.
   * @return `true` if the device supports biometric authentication and the enrolled method is sufficiently secure. Otherwise, returns `false`. Always returns false on tvOS.
-@@ -165,4 +231,5 @@ export declare function canUseBiometricAuthentication(): boolean;
-  * @platform ios
-  */
- export declare function canUseDeviceCredentialsAuthentication(): boolean;
-+export {};
- //# sourceMappingURL=SecureStore.d.ts.map
 diff --git a/node_modules/expo-secure-store/build/SecureStore.js b/node_modules/expo-secure-store/build/SecureStore.js
-index e11bd94..04569ac 100644
+index e11bd94..940fc61 100644
 --- a/node_modules/expo-secure-store/build/SecureStore.js
 +++ b/node_modules/expo-secure-store/build/SecureStore.js
 @@ -1,6 +1,53 @@
@@ -568,16 +548,7 @@ index e11bd94..04569ac 100644
  /**
   * The data in the keychain item cannot be accessed after a restart until the device has been
   * unlocked once by the user. This may be useful if you need to access the item when the phone
-@@ -92,7 +139,7 @@ export async function getItemAsync(key, options = {}) {
-  * Stores a key–value pair.
-  *
-  * @param key The key to associate with the stored value. Keys may contain alphanumeric characters, `.`, `-`, and `_`.
-- * @param value The value to store. Size limit is 2048 bytes.
-+ * @param value The value to store.
-  * @param options An [`SecureStoreOptions`](#securestoreoptions) object.
-  *
-  * @return A promise that rejects if value cannot be stored on the device.
-@@ -102,14 +149,14 @@ export async function setItemAsync(key, value, options = {}) {
+@@ -102,7 +149,7 @@ export async function setItemAsync(key, value, options = {}) {
      if (!isValidValue(value)) {
          throw new Error(`Invalid value provided to SecureStore. Values must be strings; consider JSON-encoding your values if they are serializable.`);
      }
@@ -586,42 +557,16 @@ index e11bd94..04569ac 100644
  }
  /**
   * Stores a key–value pair synchronously.
-  * > **Note:** This function blocks the JavaScript thread, so the application may not be interactive when the `requireAuthentication` option is set to `true` until the user authenticates.
-  *
-  * @param key The key to associate with the stored value. Keys may contain alphanumeric characters, `.`, `-`, and `_`.
-- * @param value The value to store. Size limit is 2048 bytes.
-+ * @param value The value to store.
-  * @param options An [`SecureStoreOptions`](#securestoreoptions) object.
-  *
-  */
 diff --git a/node_modules/expo-secure-store/ios/SecureStoreModule.swift b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
-index 1268979..5280020 100644
+index 1268979..7764c8e 100644
 --- a/node_modules/expo-secure-store/ios/SecureStoreModule.swift
 +++ b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
-@@ -8,38 +8,44 @@ public final class SecureStoreModule: Module {
-   public func definition() -> ModuleDefinition {
-     Name("ExpoSecureStore")
+@@ -18,28 +18,36 @@ public final class SecureStoreModule: Module {
+       "WHEN_UNLOCKED_THIS_DEVICE_ONLY": SecureStoreAccessible.whenUnlockedThisDeviceOnly.rawValue
+     ])
  
--    Constants([
--      "AFTER_FIRST_UNLOCK": SecureStoreAccessible.afterFirstUnlock.rawValue,
--      "AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY": SecureStoreAccessible.afterFirstUnlockThisDeviceOnly.rawValue,
--      "ALWAYS": SecureStoreAccessible.always.rawValue,
--      "WHEN_PASSCODE_SET_THIS_DEVICE_ONLY": SecureStoreAccessible.whenPasscodeSetThisDeviceOnly.rawValue,
--      "ALWAYS_THIS_DEVICE_ONLY": SecureStoreAccessible.alwaysThisDeviceOnly.rawValue,
--      "WHEN_UNLOCKED": SecureStoreAccessible.whenUnlocked.rawValue,
--      "WHEN_UNLOCKED_THIS_DEVICE_ONLY": SecureStoreAccessible.whenUnlockedThisDeviceOnly.rawValue
--    ])
--
 -    AsyncFunction("getValueWithKeyAsync") { (key: String, options: SecureStoreOptions) -> String? in
 -      return try get(with: key, options: options)
-+    Constant("AFTER_FIRST_UNLOCK") { SecureStoreAccessible.afterFirstUnlock.rawValue }
-+    Constant("AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY") { SecureStoreAccessible.afterFirstUnlockThisDeviceOnly.rawValue }
-+    Constant("ALWAYS") { SecureStoreAccessible.always.rawValue }
-+    Constant("WHEN_PASSCODE_SET_THIS_DEVICE_ONLY") { SecureStoreAccessible.whenPasscodeSetThisDeviceOnly.rawValue }
-+    Constant("ALWAYS_THIS_DEVICE_ONLY") { SecureStoreAccessible.alwaysThisDeviceOnly.rawValue }
-+    Constant("WHEN_UNLOCKED") { SecureStoreAccessible.whenUnlocked.rawValue }
-+    Constant("WHEN_UNLOCKED_THIS_DEVICE_ONLY") { SecureStoreAccessible.whenUnlockedThisDeviceOnly.rawValue }
-+
 +    AsyncFunction("getValueWithKeyAsync") { (key: String, options: SecureStoreOptions) in
 +      let result = try get(with: key, options: options)
 +
@@ -661,7 +606,7 @@ index 1268979..5280020 100644
      }
  
      AsyncFunction("deleteValueWithKeyAsync") { (key: String, options: SecureStoreOptions) in
-@@ -53,18 +59,7 @@ public final class SecureStoreModule: Module {
+@@ -53,18 +61,7 @@ public final class SecureStoreModule: Module {
      }
  
      Function("canUseBiometricAuthentication") {() -> Bool in
@@ -681,7 +626,7 @@ index 1268979..5280020 100644
      }
  
      Function("canUseDeviceCredentialsAuthentication") { () -> Bool in
-@@ -76,6 +71,41 @@ public final class SecureStoreModule: Module {
+@@ -76,6 +73,41 @@ public final class SecureStoreModule: Module {
      return LAContext().canEvaluatePolicy(LAPolicy.deviceOwnerAuthentication, error: nil)
    }
  
@@ -800,7 +745,7 @@ index c9e843b..b95eca7 100644
 +  }
  }
 diff --git a/node_modules/expo-secure-store/src/SecureStore.ts b/node_modules/expo-secure-store/src/SecureStore.ts
-index 4f253a1..e32ebdf 100644
+index 4f253a1..d44c0bb 100644
 --- a/node_modules/expo-secure-store/src/SecureStore.ts
 +++ b/node_modules/expo-secure-store/src/SecureStore.ts
 @@ -1,8 +1,63 @@
@@ -867,312 +812,90 @@ index 4f253a1..e32ebdf 100644
  // @needsAudit
  /**
   * The data in the keychain item cannot be accessed after a restart until the device has been
-@@ -17,7 +72,7 @@ export const AFTER_FIRST_UNLOCK: KeychainAccessibilityConstant = ExpoSecureStore
-  * from a backup.
-  */
- export const AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY: KeychainAccessibilityConstant =
--  ExpoSecureStore.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY;
-+    ExpoSecureStore.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY;
- 
- // @needsAudit
- /**
-@@ -34,7 +89,7 @@ export const ALWAYS: KeychainAccessibilityConstant = ExpoSecureStore.ALWAYS;
-  * store an entry. If the user removes their passcode, the entry will be deleted.
-  */
- export const WHEN_PASSCODE_SET_THIS_DEVICE_ONLY: KeychainAccessibilityConstant =
--  ExpoSecureStore.WHEN_PASSCODE_SET_THIS_DEVICE_ONLY;
-+    ExpoSecureStore.WHEN_PASSCODE_SET_THIS_DEVICE_ONLY;
- 
- // @needsAudit
- /**
-@@ -43,7 +98,7 @@ export const WHEN_PASSCODE_SET_THIS_DEVICE_ONLY: KeychainAccessibilityConstant =
-  * @deprecated Use an accessibility level that provides some user protection, such as `AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY`.
-  */
- export const ALWAYS_THIS_DEVICE_ONLY: KeychainAccessibilityConstant =
--  ExpoSecureStore.ALWAYS_THIS_DEVICE_ONLY;
-+    ExpoSecureStore.ALWAYS_THIS_DEVICE_ONLY;
- 
- // @needsAudit
- /**
-@@ -57,62 +112,80 @@ export const WHEN_UNLOCKED: KeychainAccessibilityConstant = ExpoSecureStore.WHEN
-  * a backup.
-  */
- export const WHEN_UNLOCKED_THIS_DEVICE_ONLY: KeychainAccessibilityConstant =
--  ExpoSecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY;
-+    ExpoSecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY;
- 
- // @needsAudit
- export type SecureStoreOptions = {
--  /**
--   * - Android: Equivalent of the public/private key pair `Alias`.
--   * - iOS: The item's service, equivalent to [`kSecAttrService`](https://developer.apple.com/documentation/security/ksecattrservice/).
--   * > If the item is set with the `keychainService` option, it will be required to later fetch the value.
--   */
--  keychainService?: string;
--  /**
--   * Option responsible for enabling the usage of the user authentication methods available on the device while
--   * accessing data stored in SecureStore.
--   * - Android: Equivalent to [`setUserAuthenticationRequired(true)`](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setUserAuthenticationRequired(boolean))
--   *   (requires API 23).
--   * - iOS: Equivalent to [`biometryCurrentSet`](https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/2937192-biometrycurrentset).
--   * Complete functionality is unlocked only with a freshly generated key - this would not work in tandem with the `keychainService`
--   * value used for the others non-authenticated operations.
--   *
--   * This option works slightly differently across platforms: On Android, user authentication is required for all operations.
--   * On iOS, the user is prompted to authenticate only when reading or updating an existing value (not when creating a new one).
--   *
--   * Warning: This option is not supported in Expo Go when biometric authentication is available due to a missing NSFaceIDUsageDescription.
--   * In release builds or when using continuous native generation, make sure to use the `expo-secure-store` config plugin.
--   *
--   */
--  requireAuthentication?: boolean;
--  /**
--   * Custom message displayed to the user while `requireAuthentication` option is turned on.
--   */
--  authenticationPrompt?: string;
--  /**
--   * Specifies when the stored entry is accessible, using iOS's `kSecAttrAccessible` property.
--   * @see Apple's documentation on [keychain item accessibility](https://developer.apple.com/documentation/security/ksecattraccessible/).
--   * @default SecureStore.WHEN_UNLOCKED
--   * @platform ios
--   */
--  keychainAccessible?: KeychainAccessibilityConstant;
-+    /**
-+     * - Android: Equivalent of the public/private key pair `Alias`.
-+     * - iOS: The item's service, equivalent to [`kSecAttrService`](https://developer.apple.com/documentation/security/ksecattrservice/).
-+     * > If the item is set with the `keychainService` option, it will be required to later fetch the value.
-+     */
-+    keychainService?: string;
-+    /**
-+     * Option responsible for enabling the usage of the user authentication methods available on the device while
-+     * accessing data stored in SecureStore.
-+     * - Android: Equivalent to [`setUserAuthenticationRequired(true)`](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setUserAuthenticationRequired(boolean))
-+     *   (requires API 23).
-+     * - iOS: Equivalent to [`biometryCurrentSet`](https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/2937192-biometrycurrentset).
-+     * Complete functionality is unlocked only with a freshly generated key - this would not work in tandem with the `keychainService`
-+     * value used for the others non-authenticated operations.
-+     *
-+     * This option works slightly differently across platforms: On Android, user authentication is required for all operations.
-+     * On iOS, the user is prompted to authenticate only when reading or updating an existing value (not when creating a new one).
-+     *
-+     * Warning: This option is not supported in Expo Go when biometric authentication is available due to a missing NSFaceIDUsageDescription.
-+     * In release builds or when using continuous native generation, make sure to use the `expo-secure-store` config plugin.
-+     *
-+     * > **Note:** This library requires a real device for testing since emulators/simulators do not require biometric authentication when retrieving secrets, unlike real iOS devices.
-+     */
-+    requireAuthentication?: boolean;
-+    /**
-+     * Custom message displayed to the user while `requireAuthentication` option is turned on.
-+     */
-+    authenticationPrompt?: string;
-+    /**
-+     * Specifies when the stored entry is accessible, using iOS's `kSecAttrAccessible` property.
-+     * @see Apple's documentation on [keychain item accessibility](https://developer.apple.com/documentation/security/ksecattraccessible/).
-+     * @default SecureStore.WHEN_UNLOCKED
-+     * @platform ios
-+     */
-+    keychainAccessible?: KeychainAccessibilityConstant;
-+
-+    /**
-+     * Specifies the access group the stored entry belongs to.
-+     * @see Apple's documentation on [Sharing access to keychain items among a collection of apps](https://developer.apple.com/documentation/security/sharing-access-to-keychain-items-among-a-collection-of-apps).
-+     * @platform ios
-+     */
-+    accessGroup?: string;
- 
--  /**
--   * Specifies the access group the stored entry belongs to.
--   * @see Apple's documentation on [Sharing access to keychain items among a collection of apps](https://developer.apple.com/documentation/security/sharing-access-to-keychain-items-among-a-collection-of-apps).
--   * @platform ios
--   */
--  accessGroup?: string;
-+    /**
-+     * This flag enables users to authenticate using Lock Screen Knowledge Factor (e.g. PIN, pattern or password).
-+     * For sensitive apps, it is recommended not having biometric fall back to such factor.
-+     * @see: https://developer.android.com/security/fraud-prevention/authentication
-+     *
-+     * @default false
-+     * @platform android
-+     * @platform ios
-+     */
-+    enableDeviceFallback?: boolean;
- 
--  /**
--   * This flag enables users to authenticate using Lock Screen Knowledge Factor (e.g. PIN, pattern or password).
--   * For sensitive apps, it is recommended not having biometric fall back to such factor.
--   * @see: https://developer.android.com/security/fraud-prevention/authentication
--   *
--   * @default false
--   * @platform android
--   * @platform ios
--   */
--  enableDeviceFallback?: boolean;
-+    /**
-+     * When this flag is set to true, the get methods of SecureStore will return a two-element array. The first value will be the original value returned when this flag is set to false.
-+     * The second value is the authentication type used to read the value from the AUTH_TYPE object.
-+     * As for the set function, the returned value will simply be AUTH_TYPE.
-+     *
-+     * @warning
-+     * If the iOS device supports biometrics and the user falls back to device credentials, it will not be detected.
-+     * This is not the case on Android, but we cannot specify the exact type of biometrics (e.g. fingerprint or face scan).
-+     * Whether the type is detected correctly depends on the platform and its native implementation.
-+     * This should be treated as more of a hint.
-+     *
-+     * @default false
-+     * @platform android
-+     * @platform ios
-+     */
-+    returnUsedAuthenticationType?: boolean;
+@@ -113,6 +168,23 @@ export type SecureStoreOptions = {
+    * @platform ios
+    */
+   enableDeviceFallback?: boolean;
++
++  /**
++   * When this flag is set to true, the get methods of SecureStore will return a two-element array. The first value will be the original value returned when this flag is set to false.
++   * The second value is the authentication type used to read the value from the AUTH_TYPE object.
++   * As for the set function, the returned value will simply be AUTH_TYPE.
++   *
++   * @warning
++   * If the iOS device supports biometrics and the user falls back to device credentials, it will not be detected.
++   * This is not the case on Android, but we cannot specify the exact type of biometrics (e.g. fingerprint or face scan).
++   * Whether the type is detected correctly depends on the platform and its native implementation.
++   * This should be treated as more of a hint.
++   *
++   * @default false
++   * @platform android
++   * @platform ios
++   */
++  returnUsedAuthenticationType?: boolean;
  };
  
  // @needsAudit
-@@ -124,7 +197,7 @@ export type SecureStoreOptions = {
-  * on the current device. Currently, this resolves `true` on Android and iOS only.
-  */
- export async function isAvailableAsync(): Promise<boolean> {
--  return !!ExpoSecureStore.getValueWithKeyAsync;
-+    return !!ExpoSecureStore.getValueWithKeyAsync;
- }
- 
- // @needsAudit
-@@ -137,12 +210,12 @@ export async function isAvailableAsync(): Promise<boolean> {
-  * @return A promise that rejects if the value can't be deleted.
-  */
- export async function deleteItemAsync(
--  key: string,
--  options: SecureStoreOptions = {}
-+    key: string,
-+    options: SecureStoreOptions = {}
- ): Promise<void> {
--  ensureValidKey(key);
-+    ensureValidKey(key);
- 
--  await ExpoSecureStore.deleteValueWithKeyAsync(key, options);
-+    await ExpoSecureStore.deleteValueWithKeyAsync(key, options);
- }
- 
- // @needsAudit
-@@ -159,12 +232,12 @@ export async function deleteItemAsync(
+@@ -159,10 +231,10 @@ export async function deleteItemAsync(
   * > After a key has been invalidated, it becomes impossible to read its value.
   * > This only applies to values stored with `requireAuthentication` set to `true`.
   */
 -export async function getItemAsync(
--  key: string,
++export async function getItemAsync<R extends SecureStoreOptions>(
+   key: string,
 -  options: SecureStoreOptions = {}
 -): Promise<string | null> {
--  ensureValidKey(key);
--  return await ExpoSecureStore.getValueWithKeyAsync(key, options);
-+export async function getItemAsync<R extends SecureStoreOptions>(
-+    key: string,
-+    options: R | EmptyObject = {}
++  options: R | EmptyObject = {}
 +): Promise<SecureStoreGetFeedback<string, R>> {
-+    ensureValidKey(key);
-+    return await ExpoSecureStore.getValueWithKeyAsync(key, options);
+   ensureValidKey(key);
+   return await ExpoSecureStore.getValueWithKeyAsync(key, options);
  }
- 
- // @needsAudit
-@@ -172,24 +245,24 @@ export async function getItemAsync(
-  * Stores a key–value pair.
-  *
-  * @param key The key to associate with the stored value. Keys may contain alphanumeric characters, `.`, `-`, and `_`.
-- * @param value The value to store. Size limit is 2048 bytes.
-+ * @param value The value to store.
-  * @param options An [`SecureStoreOptions`](#securestoreoptions) object.
+@@ -177,11 +249,11 @@ export async function getItemAsync(
   *
   * @return A promise that rejects if value cannot be stored on the device.
   */
 -export async function setItemAsync(
--  key: string,
--  value: string,
++export async function setItemAsync<R extends SecureStoreOptions>(
+   key: string,
+   value: string,
 -  options: SecureStoreOptions = {}
 -): Promise<void> {
--  ensureValidKey(key);
--  if (!isValidValue(value)) {
--    throw new Error(
--      `Invalid value provided to SecureStore. Values must be strings; consider JSON-encoding your values if they are serializable.`
--    );
--  }
-+export async function setItemAsync<R extends SecureStoreOptions>(
-+    key: string,
-+    value: string,
-+    options: R | EmptyObject = {}
++  options: R | EmptyObject = {}
 +): Promise<SecureStoreSetFeedback<R>> {
-+    ensureValidKey(key);
-+    if (!isValidValue(value)) {
-+        throw new Error(
-+            `Invalid value provided to SecureStore. Values must be strings; consider JSON-encoding your values if they are serializable.`
-+        );
-+    }
+   ensureValidKey(key);
+   if (!isValidValue(value)) {
+     throw new Error(
+@@ -189,7 +261,7 @@ export async function setItemAsync(
+     );
+   }
  
 -  await ExpoSecureStore.setValueWithKeyAsync(value, key, options);
-+    return await ExpoSecureStore.setValueWithKeyAsync(value, key, options);
++  return await ExpoSecureStore.setValueWithKeyAsync(value, key, options);
  }
  
  /**
-@@ -197,19 +270,23 @@ export async function setItemAsync(
-  * > **Note:** This function blocks the JavaScript thread, so the application may not be interactive when the `requireAuthentication` option is set to `true` until the user authenticates.
-  *
-  * @param key The key to associate with the stored value. Keys may contain alphanumeric characters, `.`, `-`, and `_`.
-- * @param value The value to store. Size limit is 2048 bytes.
-+ * @param value The value to store.
+@@ -201,7 +273,11 @@ export async function setItemAsync(
   * @param options An [`SecureStoreOptions`](#securestoreoptions) object.
   *
   */
 -export function setItem(key: string, value: string, options: SecureStoreOptions = {}): void {
--  ensureValidKey(key);
--  if (!isValidValue(value)) {
--    throw new Error(
--      `Invalid value provided to SecureStore. Values must be strings; consider JSON-encoding your values if they are serializable.`
--    );
--  }
 +export function setItem<R extends SecureStoreOptions>(
-+    key: string,
-+    value: string,
-+    options: R | EmptyObject = {}
++  key: string,
++  value: string,
++  options: R | EmptyObject = {}
 +): SecureStoreSetFeedback<R> {
-+    ensureValidKey(key);
-+    if (!isValidValue(value)) {
-+        throw new Error(
-+            `Invalid value provided to SecureStore. Values must be strings; consider JSON-encoding your values if they are serializable.`
-+        );
-+    }
- 
--  return ExpoSecureStore.setValueWithKeySync(value, key, options);
-+    return ExpoSecureStore.setValueWithKeySync(value, key, options);
- }
- 
- /**
-@@ -222,9 +299,12 @@ export function setItem(key: string, value: string, options: SecureStoreOptions
+   ensureValidKey(key);
+   if (!isValidValue(value)) {
+     throw new Error(
+@@ -222,7 +298,10 @@ export function setItem(key: string, value: string, options: SecureStoreOptions
   * @return Previously stored value. It resolves with `null` if there is no entry
   * for the given key or if the key has been invalidated.
   */
 -export function getItem(key: string, options: SecureStoreOptions = {}): string | null {
--  ensureValidKey(key);
--  return ExpoSecureStore.getValueWithKeySync(key, options);
 +export function getItem<R extends SecureStoreOptions>(
-+    key: string,
-+    options: R | EmptyObject = {}
++  key: string,
++  options: R | EmptyObject = {}
 +): SecureStoreGetFeedback<string, R> {
-+    ensureValidKey(key);
-+    return ExpoSecureStore.getValueWithKeySync(key, options);
+   ensureValidKey(key);
+   return ExpoSecureStore.getValueWithKeySync(key, options);
  }
- 
- /**
-@@ -234,7 +314,7 @@ export function getItem(key: string, options: SecureStoreOptions = {}): string |
-  * @platform ios
-  */
- export function canUseBiometricAuthentication(): boolean {
--  return ExpoSecureStore.canUseBiometricAuthentication();
-+    return ExpoSecureStore.canUseBiometricAuthentication();
- }
- 
- /**
-@@ -244,7 +324,7 @@ export function canUseBiometricAuthentication(): boolean {
-  * @platform ios
-  */
- export function canUseDeviceCredentialsAuthentication(): boolean {
--  return ExpoSecureStore.canUseDeviceCredentialsAuthentication();
-+    return ExpoSecureStore.canUseDeviceCredentialsAuthentication();
- }
- 
- function ensureValidKey(key: string) {
diff --git a/patches/expo-secure-store/expo-secure-store+14.2.4+003+force-authentication-on-save.patch b/patches/expo-secure-store/expo-secure-store+14.2.4+003+force-authentication-on-save.patch
index fc085c205b11..59e74054b162 100644
--- a/patches/expo-secure-store/expo-secure-store+14.2.4+003+force-authentication-on-save.patch
+++ b/patches/expo-secure-store/expo-secure-store+14.2.4+003+force-authentication-on-save.patch
@@ -1,8 +1,8 @@
 diff --git a/node_modules/expo-secure-store/build/SecureStore.d.ts b/node_modules/expo-secure-store/build/SecureStore.d.ts
-index 40ec045..be49e3b 100644
+index 7097a8a..975d75b 100644
 --- a/node_modules/expo-secure-store/build/SecureStore.d.ts
 +++ b/node_modules/expo-secure-store/build/SecureStore.d.ts
-@@ -154,6 +154,20 @@ export type SecureStoreOptions = {
+@@ -153,6 +153,20 @@ export type SecureStoreOptions = {
       * @platform ios
       */
      returnUsedAuthenticationType?: boolean;
@@ -24,21 +24,21 @@ index 40ec045..be49e3b 100644
  /**
   * Returns whether the SecureStore API is enabled on the current device. This does not check the app
 diff --git a/node_modules/expo-secure-store/ios/SecureStoreModule.swift b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
-index 5280020..3060cbd 100644
+index 7764c8e..02f837c 100644
 --- a/node_modules/expo-secure-store/ios/SecureStoreModule.swift
 +++ b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
-@@ -33,6 +33,10 @@ public final class SecureStoreModule: Module {
+@@ -35,6 +35,10 @@ public final class SecureStoreModule: Module {
          throw InvalidKeyException()
        }
  
-+     if options.requireAuthentication && options.forceAuthenticationOnSave {
++      if options.requireAuthentication && options.forceAuthenticationOnSave {
 +        try await triggerPolicy(options: options)
 +      }
 +
        let result = try set(value: value, with: key, options: options)
  
        return wrapResultWithFeedback(action: .set, result: result, options: options).value
-@@ -188,6 +192,32 @@ public final class SecureStoreModule: Module {
+@@ -190,6 +194,32 @@ public final class SecureStoreModule: Module {
      }
    }
  
@@ -97,28 +97,28 @@ index b95eca7..321c7e5 100644
 +  }
 +}
 diff --git a/node_modules/expo-secure-store/src/SecureStore.ts b/node_modules/expo-secure-store/src/SecureStore.ts
-index e32ebdf..3fbaf02 100644
+index d44c0bb..c4b95ae 100644
 --- a/node_modules/expo-secure-store/src/SecureStore.ts
 +++ b/node_modules/expo-secure-store/src/SecureStore.ts
-@@ -186,6 +186,21 @@ export type SecureStoreOptions = {
-      * @platform ios
-      */
-     returnUsedAuthenticationType?: boolean;
+@@ -185,6 +185,21 @@ export type SecureStoreOptions = {
+    * @platform ios
+    */
+   returnUsedAuthenticationType?: boolean;
 +
-+    /**
-+     * On iOS, the system does not ask for auth when saving the value to the SecureStore.
-+     * The Android however, displays the authentication prompt when saving the value.
-+     * To keep the behavior on every platform similar as much as possible,
-+     * setting this flag to true will ensure that authentication is required when saving a value to the store.
-+     *
-+     * @warning: This flag only works for the asynchronous version of the SecureStore save method.
-+     * It should only be considered an improvement to the user experience; it does not prevent the user
-+     * from saving the value without authenticating using other methods (e.g. by directly modifying the keychain).
-+     * For it to take effect, the 'requireAuthentication' flag must be set to true.
-+     * @default false
-+     * @platform ios
-+     */
-+    forceAuthenticationOnSave?: boolean;
++  /**
++   * On iOS, the system does not ask for auth when saving the value to the SecureStore.
++   * The Android however, displays the authentication prompt when saving the value.
++   * To keep the behavior on every platform similar as much as possible,
++   * setting this flag to true will ensure that authentication is required when saving a value to the store.
++   *
++   * @warning: This flag only works for the asynchronous version of the SecureStore save method.
++   * It should only be considered an improvement to the user experience; it does not prevent the user
++   * from saving the value without authenticating using other methods (e.g. by directly modifying the keychain).
++   * For it to take effect, the 'requireAuthentication' flag must be set to true.
++   * @default false
++   * @platform ios
++   */
++  forceAuthenticationOnSave?: boolean;
  };
  
  // @needsAudit
diff --git a/patches/expo-secure-store/expo-secure-store+14.2.4+004+fail-on-update.patch b/patches/expo-secure-store/expo-secure-store+14.2.4+004+fail-on-update.patch
index 175b02cb71d8..c3acba54c616 100644
--- a/patches/expo-secure-store/expo-secure-store+14.2.4+004+fail-on-update.patch
+++ b/patches/expo-secure-store/expo-secure-store+14.2.4+004+fail-on-update.patch
@@ -28,10 +28,10 @@ index 4e30440..16674b8 100644
  
  enum class SecureStoreAuthType(index: Int) {
 diff --git a/node_modules/expo-secure-store/build/SecureStore.d.ts b/node_modules/expo-secure-store/build/SecureStore.d.ts
-index 4543001..0f3ef89 100644
+index f1e3610..8954c59 100644
 --- a/node_modules/expo-secure-store/build/SecureStore.d.ts
 +++ b/node_modules/expo-secure-store/build/SecureStore.d.ts
-@@ -168,6 +168,16 @@ export type SecureStoreOptions = {
+@@ -167,6 +167,16 @@ export type SecureStoreOptions = {
       * @platform ios
       */
      forceAuthenticationOnSave?: boolean;
@@ -49,14 +49,14 @@ index 4543001..0f3ef89 100644
  /**
   * Returns whether the SecureStore API is enabled on the current device. This does not check the app
 diff --git a/node_modules/expo-secure-store/ios/SecureStoreModule.swift b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
-index 3060cbd..5a81b11 100644
+index 02f837c..47282db 100644
 --- a/node_modules/expo-secure-store/ios/SecureStoreModule.swift
 +++ b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
-@@ -167,6 +167,9 @@ public final class SecureStoreModule: Module {
+@@ -169,6 +169,9 @@ public final class SecureStoreModule: Module {
        SecItemDelete(query(with: key, options: options, requireAuthentication: !options.requireAuthentication) as CFDictionary)
        return true
      case errSecDuplicateItem:
-+     if options.failOnUpdate {
++      if options.failOnUpdate {
 +        throw SecureStoreRuntimeError("Key already exists")
 +      }
        return try update(value: value, with: key, options: options)
@@ -77,32 +77,24 @@ index 321c7e5..6a787b0 100644
  
  @available(iOS 11.2, *)
 diff --git a/node_modules/expo-secure-store/src/SecureStore.ts b/node_modules/expo-secure-store/src/SecureStore.ts
-index 3fbaf02..9c3463c 100644
+index c4b95ae..0bedcaa 100644
 --- a/node_modules/expo-secure-store/src/SecureStore.ts
 +++ b/node_modules/expo-secure-store/src/SecureStore.ts
-@@ -201,6 +201,17 @@ export type SecureStoreOptions = {
-      * @platform ios
-      */
-     forceAuthenticationOnSave?: boolean;
+@@ -200,6 +200,17 @@ export type SecureStoreOptions = {
+    * @platform ios
+    */
+   forceAuthenticationOnSave?: boolean;
 +
-+    /**
-+     * If the key has already been stored, the save function will throw an error instead of overwriting it.
-+     * The behaviour differs slightly depending on the platform.
-+     * On Android, an error is thrown before the authentication prompt. On iOS, an error is thrown after authentication.
-+     *
-+     * @default false
-+     * @platform ios
-+     * @platform android
-+     */
-+    failOnUpdate?: boolean;
++  /**
++   * If the key has already been stored, the save function will throw an error instead of overwriting it.
++   * The behaviour differs slightly depending on the platform.
++   * On Android, an error is thrown before the authentication prompt. On iOS, an error is thrown after authentication.
++   *
++   * @default false
++   * @platform ios
++   * @platform android
++   */
++  failOnUpdate?: boolean;
  };
  
  // @needsAudit
-@@ -341,7 +352,6 @@ export function canUseBiometricAuthentication(): boolean {
- export function canUseDeviceCredentialsAuthentication(): boolean {
-     return ExpoSecureStore.canUseDeviceCredentialsAuthentication();
- }
--
- function ensureValidKey(key: string) {
-   if (!isValidKey(key)) {
-     throw new Error(
diff --git a/patches/expo-secure-store/expo-secure-store+14.2.4+005+force-read-authentication-on-simulators.patch b/patches/expo-secure-store/expo-secure-store+14.2.4+005+force-read-authentication-on-simulators.patch
index 062542442189..f65e0a31540a 100644
--- a/patches/expo-secure-store/expo-secure-store+14.2.4+005+force-read-authentication-on-simulators.patch
+++ b/patches/expo-secure-store/expo-secure-store+14.2.4+005+force-read-authentication-on-simulators.patch
@@ -1,8 +1,8 @@
 diff --git a/node_modules/expo-secure-store/build/SecureStore.d.ts b/node_modules/expo-secure-store/build/SecureStore.d.ts
-index a6856a9..59bdee0 100644
+index 836958c..29531c5 100644
 --- a/node_modules/expo-secure-store/build/SecureStore.d.ts
 +++ b/node_modules/expo-secure-store/build/SecureStore.d.ts
-@@ -178,6 +178,18 @@ export type SecureStoreOptions = {
+@@ -177,6 +177,18 @@ export type SecureStoreOptions = {
       * @platform android
       */
      failOnUpdate?: boolean;
@@ -22,11 +22,11 @@ index a6856a9..59bdee0 100644
  /**
   * Returns whether the SecureStore API is enabled on the current device. This does not check the app
 diff --git a/node_modules/expo-secure-store/ios/SecureStoreModule.swift b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
-index 5a81b11..35a339a 100644
+index 47282db..dcbe242 100644
 --- a/node_modules/expo-secure-store/ios/SecureStoreModule.swift
 +++ b/node_modules/expo-secure-store/ios/SecureStoreModule.swift
-@@ -17,6 +17,11 @@ public final class SecureStoreModule: Module {
-     Constant("WHEN_UNLOCKED_THIS_DEVICE_ONLY") { SecureStoreAccessible.whenUnlockedThisDeviceOnly.rawValue }
+@@ -19,6 +19,11 @@ public final class SecureStoreModule: Module {
+     ])
  
      AsyncFunction("getValueWithKeyAsync") { (key: String, options: SecureStoreOptions) in
 +      #if targetEnvironment(simulator)
@@ -52,34 +52,26 @@ index 6a787b0..8f110dd 100644
  
  @available(iOS 11.2, *)
 diff --git a/node_modules/expo-secure-store/src/SecureStore.ts b/node_modules/expo-secure-store/src/SecureStore.ts
-index 9c3463c..52fc89d 100644
+index 0bedcaa..bae5580 100644
 --- a/node_modules/expo-secure-store/src/SecureStore.ts
 +++ b/node_modules/expo-secure-store/src/SecureStore.ts
-@@ -212,6 +212,19 @@ export type SecureStoreOptions = {
-      * @platform android
-      */
-     failOnUpdate?: boolean;
+@@ -211,6 +211,19 @@ export type SecureStoreOptions = {
+    * @platform android
+    */
+   failOnUpdate?: boolean;
 +
-+    /**
-+     * The LocalAuthentication behaves slightly differently on iOS simulators.
-+     * In numerous cases, the authentication prompts are skipped on simulators (as opposed to real devices).
-+     * Setting this flag to true forces the prompt to appear on simulators when a value with the `requireAuthentication` flag set to true is read.
-+     * This is purely for testing the app on simulators, in cases where the prompt does not appear when the value is read.
-+     * This has no effect on real devices.
-+     *
-+     * @warning: This flag only works for the asynchronous version of the SecureStore read method.
-+     * @default false
-+     * @platform ios
-+     */
-+    forceReadAuthenticationOnSimulators?: boolean;
++  /**
++   * The LocalAuthentication behaves slightly differently on iOS simulators.
++   * In numerous cases, the authentication prompts are skipped on simulators (as opposed to real devices).
++   * Setting this flag to true forces the prompt to appear on simulators when a value with the `requireAuthentication` flag set to true is read.
++   * This is purely for testing the app on simulators, in cases where the prompt does not appear when the value is read.
++   * This has no effect on real devices.
++   *
++   * @warning: This flag only works for the asynchronous version of the SecureStore read method.
++   * @default false
++   * @platform ios
++   */
++  forceReadAuthenticationOnSimulators?: boolean;
  };
  
  // @needsAudit
-@@ -352,6 +365,7 @@ export function canUseBiometricAuthentication(): boolean {
- export function canUseDeviceCredentialsAuthentication(): boolean {
-     return ExpoSecureStore.canUseDeviceCredentialsAuthentication();
- }
-+
- function ensureValidKey(key: string) {
-   if (!isValidKey(key)) {
-     throw new Error(

From 1109576284c75cc0f40e7c1729c45c4c9d56806e Mon Sep 17 00:00:00 2001
From: Jakub Korytko <jakub@korytko.me>
Date: Tue, 2 Dec 2025 13:40:28 +0100
Subject: [PATCH 4/7] Update details.md

---
 patches/expo-secure-store/details.md | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/patches/expo-secure-store/details.md b/patches/expo-secure-store/details.md
index aaf1bb1ab02f..6bc1a8c084b1 100644
--- a/patches/expo-secure-store/details.md
+++ b/patches/expo-secure-store/details.md
@@ -19,7 +19,11 @@
 - Reason:
 
     ```
-    This patch makes the read and write methods return the authentication type used to access the store.
+    This patch adds the `returnUsedAuthenticationType` flag.
+    When this flag is set to true, the get methods of SecureStore will return a two-element array. 
+    The first value will be the original value returned when this flag is set to false.
+    The second value is the authentication type used to read the value from the AUTH_TYPE object.
+    As for the set function, the returned value will simply be AUTH_TYPE.
     It uses a pre-defined constant that mimics an enum and can also be imported directly from the app.
     ```
 

From cc20346fb1fc970cf9dcd10d3393aecb1ee4968f Mon Sep 17 00:00:00 2001
From: Jakub Korytko <jakub@korytko.me>
Date: Tue, 2 Dec 2025 19:13:32 +0100
Subject: [PATCH 5/7] Fix Android expo-secure-store patch

---
 ...re-store+14.2.4+002+return-auth-type.patch | 82 +++++++++----------
 1 file changed, 41 insertions(+), 41 deletions(-)

diff --git a/patches/expo-secure-store/expo-secure-store+14.2.4+002+return-auth-type.patch b/patches/expo-secure-store/expo-secure-store+14.2.4+002+return-auth-type.patch
index 74b39840a335..c8cdfcf5ac02 100644
--- a/patches/expo-secure-store/expo-secure-store+14.2.4+002+return-auth-type.patch
+++ b/patches/expo-secure-store/expo-secure-store+14.2.4+002+return-auth-type.patch
@@ -5,7 +5,7 @@ index 4a1a009..980ef0a 100644
 @@ -20,12 +20,12 @@ class AuthenticationHelper(
  ) {
    private var isAuthenticating = false
- 
+
 -  suspend fun authenticateCipher(cipher: Cipher, requiresAuthentication: Boolean, title: String, enableDeviceFallback: Boolean): Cipher {
 -    if (requiresAuthentication) {
 -      return openAuthenticationPrompt(cipher, title, enableDeviceFallback).cryptoObject?.cipher
@@ -18,25 +18,25 @@ index 4a1a009..980ef0a 100644
 -    return cipher
 +    return promptResult
    }
- 
+
    private suspend fun openAuthenticationPrompt(
 diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt
 index 3f21552..d209efc 100644
 --- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt
 +++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt
 @@ -36,23 +36,23 @@ open class SecureStoreModule : Module() {
- 
+
      AsyncFunction("setValueWithKeyAsync") Coroutine { value: String?, key: String?, options: SecureStoreOptions ->
        key ?: throw NullKeyException()
 -      return@Coroutine setItemImpl(key, value, options, false)
-+      return@Coroutine narrowSecureStoreFeedback(SecureStoreFeedbackAction.SET, setItemImpl(key, value, options, false), options)
++      return@Coroutine narrowSecureStoreFeedback(SecureStoreFeedbackAction.SET, setItemImpl(key, value, options, false), options).value
      }
- 
+
      AsyncFunction("getValueWithKeyAsync") Coroutine { key: String, options: SecureStoreOptions ->
 -      return@Coroutine getItemImpl(key, options)
-+      return@Coroutine narrowSecureStoreFeedback(SecureStoreFeedbackAction.GET,getItemImpl(key, options), options)
++      return@Coroutine narrowSecureStoreFeedback(SecureStoreFeedbackAction.GET,getItemImpl(key, options), options).value
      }
- 
+
      Function("setValueWithKeySync") { value: String?, key: String?, options: SecureStoreOptions ->
        key ?: throw NullKeyException()
        return@Function runBlocking {
@@ -44,18 +44,18 @@ index 3f21552..d209efc 100644
 +        return@runBlocking narrowSecureStoreFeedback(SecureStoreFeedbackAction.SET, setItemImpl(key, value, options, keyIsInvalidated = false), options).value
        }
      }
- 
+
      Function("getValueWithKeySync") { key: String, options: SecureStoreOptions ->
        return@Function runBlocking {
 -        return@runBlocking getItemImpl(key, options)
 +        return@runBlocking narrowSecureStoreFeedback(SecureStoreFeedbackAction.GET, getItemImpl(key, options), options).value
        }
      }
- 
+
 @@ -94,7 +94,23 @@ open class SecureStoreModule : Module() {
      }
    }
- 
+
 -  private suspend fun getItemImpl(key: String, options: SecureStoreOptions): String? {
 +  private suspend fun <T> narrowSecureStoreFeedback(action: String, feedback: SecureStoreOriginalFeedback<T>, options: SecureStoreOptions): SecureStoreNarrowedFeedback<T> {
 +    if (!options.returnUsedAuthenticationType) {
@@ -84,19 +84,19 @@ index 3f21552..d209efc 100644
 -    return null
 +    return SecureStoreOriginalFeedback(null)
    }
- 
+
 -  private suspend fun readJSONEncodedItem(key: String, prefs: SharedPreferences, options: SecureStoreOptions): String? {
 +  private suspend fun readJSONEncodedItem(key: String, prefs: SharedPreferences, options: SecureStoreOptions): SecureStoreOriginalFeedback<out String?> {
      val keychainAwareKey = createKeychainAwareKey(key, options.keychainService)
- 
+
      val legacyEncryptedItemString = prefs.getString(key, null)
 @@ -126,7 +142,7 @@ open class SecureStoreModule : Module() {
        ""
      }
- 
+
 -    encryptedItemString ?: return null
 +    encryptedItemString ?: return SecureStoreOriginalFeedback(null)
- 
+
      val encryptedItem: JSONObject = try {
        JSONObject(encryptedItemString)
 @@ -149,13 +165,13 @@ open class SecureStoreModule : Module() {
@@ -136,12 +136,12 @@ index 3f21552..d209efc 100644
 @@ -184,7 +200,7 @@ open class SecureStoreModule : Module() {
      }
    }
- 
+
 -  private suspend fun setItemImpl(key: String, value: String?, options: SecureStoreOptions, keyIsInvalidated: Boolean) {
 +  private suspend fun setItemImpl(key: String, value: String?, options: SecureStoreOptions, keyIsInvalidated: Boolean): SecureStoreOriginalFeedback<out JSONObject?> {
      val keychainAwareKey = createKeychainAwareKey(key, options.keychainService)
      val prefs: SharedPreferences = getSharedPreferences()
- 
+
 @@ -193,7 +209,7 @@ open class SecureStoreModule : Module() {
        if (!success) {
          throw WriteException("Could not write a null value to SecureStore", key, options.keychainService)
@@ -149,7 +149,7 @@ index 3f21552..d209efc 100644
 -      return
 +      return SecureStoreOriginalFeedback(null)
      }
- 
+
      try {
 @@ -210,7 +226,8 @@ open class SecureStoreModule : Module() {
         back a value.
@@ -160,7 +160,7 @@ index 3f21552..d209efc 100644
 +      val encryptedItem = encryptResult.value
        encryptedItem.put(SCHEME_PROPERTY, AESEncryptor.NAME)
        saveEncryptedItem(encryptedItem, prefs, keychainAwareKey, options.requireAuthentication, options.keychainService)
- 
+
 @@ -218,6 +235,9 @@ open class SecureStoreModule : Module() {
        if (prefs.contains(key)) {
          prefs.edit().remove(key).apply()
@@ -177,7 +177,7 @@ index 0455fd6..4e30440 100644
 +++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt
 @@ -1,5 +1,6 @@
  package expo.modules.securestore
- 
+
 +import androidx.biometric.BiometricPrompt
  import expo.modules.kotlin.records.Field
  import expo.modules.kotlin.records.Record
@@ -268,7 +268,7 @@ index 1cd187f..d927ff1 100644
      val secretKey = keyStoreEntry.secretKey
      val cipher = Cipher.getInstance(AES_CIPHER)
      cipher.init(Cipher.ENCRYPT_MODE, secretKey)
- 
+
      val gcmSpec = cipher.parameters.getParameterSpec(GCMParameterSpec::class.java)
 -    val authenticatedCipher = authenticationHelper.authenticateCipher(cipher, requireAuthentication, authenticationPrompt, enableDeviceFallback)
 +    var promptResult: BiometricPrompt.AuthenticationResult? = null
@@ -280,11 +280,11 @@ index 1cd187f..d927ff1 100644
 +    } else {
 +      authenticatedCipher = cipher
 +    }
- 
+
 -    return createEncryptedItemWithCipher(plaintextValue, authenticatedCipher, gcmSpec)
 +    return SecureStoreOriginalFeedback(createEncryptedItemWithCipher(plaintextValue, authenticatedCipher, gcmSpec), promptResult)
    }
- 
+
    internal fun createEncryptedItemWithCipher(
 @@ -121,7 +131,7 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
      keyStoreEntry: KeyStore.SecretKeyEntry,
@@ -313,7 +313,7 @@ index 1cd187f..d927ff1 100644
 +
 +    return SecureStoreOriginalFeedback(String(unlockedCipher.doFinal(ciphertextBytes), StandardCharsets.UTF_8), promptResult)
    }
- 
+
    companion object {
 diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/HybridAESEncryptor.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/HybridAESEncryptor.kt
 index 5f8bbfd..7527001 100644
@@ -350,7 +350,7 @@ index 39459ff..a531aff 100644
 --- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/KeyBasedEncryptor.kt
 +++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/KeyBasedEncryptor.kt
 @@ -2,6 +2,7 @@ package expo.modules.securestore.encryptors
- 
+
  import expo.modules.securestore.AuthenticationHelper
  import expo.modules.securestore.SecureStoreOptions
 +import expo.modules.securestore.SecureStoreOriginalFeedback
@@ -363,7 +363,7 @@ index 39459ff..a531aff 100644
      enableDeviceFallback: Boolean,
 -  ): JSONObject
 +  ): SecureStoreOriginalFeedback<JSONObject>
- 
+
    @Throws(GeneralSecurityException::class, JSONException::class)
    suspend fun decryptItem(
 @@ -36,5 +37,5 @@ interface KeyBasedEncryptor<E : KeyStore.Entry> {
@@ -564,7 +564,7 @@ index 1268979..7764c8e 100644
 @@ -18,28 +18,36 @@ public final class SecureStoreModule: Module {
        "WHEN_UNLOCKED_THIS_DEVICE_ONLY": SecureStoreAccessible.whenUnlockedThisDeviceOnly.rawValue
      ])
- 
+
 -    AsyncFunction("getValueWithKeyAsync") { (key: String, options: SecureStoreOptions) -> String? in
 -      return try get(with: key, options: options)
 +    AsyncFunction("getValueWithKeyAsync") { (key: String, options: SecureStoreOptions) in
@@ -572,7 +572,7 @@ index 1268979..7764c8e 100644
 +
 +      return wrapResultWithFeedback(action: .get, result: result, options: options).value
      }
- 
+
 -    Function("getValueWithKeySync") { (key: String, options: SecureStoreOptions) -> String? in
 -      return try get(with: key, options: options)
 +    Function("getValueWithKeySync") { (key: String, options: SecureStoreOptions) in
@@ -580,35 +580,35 @@ index 1268979..7764c8e 100644
 +
 +      return wrapResultWithFeedback(action: .get, result: result, options: options).value
      }
- 
+
 -    AsyncFunction("setValueWithKeyAsync") { (value: String, key: String, options: SecureStoreOptions) -> Bool in
 +    AsyncFunction("setValueWithKeyAsync") { (value: String, key: String, options: SecureStoreOptions) in
        guard let key = validate(for: key) else {
          throw InvalidKeyException()
        }
- 
+
 -      return try set(value: value, with: key, options: options)
 +      let result = try set(value: value, with: key, options: options)
 +
 +      return wrapResultWithFeedback(action: .set, result: result, options: options).value
      }
- 
+
 -    Function("setValueWithKeySync") {(value: String, key: String, options: SecureStoreOptions) -> Bool in
 +    Function("setValueWithKeySync") {(value: String, key: String, options: SecureStoreOptions) in
        guard let key = validate(for: key) else {
          throw InvalidKeyException()
        }
- 
+
 -      return try set(value: value, with: key, options: options)
 +      let result = try set(value: value, with: key, options: options)
 +
 +      return wrapResultWithFeedback(action: .set, result: result, options: options).value
      }
- 
+
      AsyncFunction("deleteValueWithKeyAsync") { (key: String, options: SecureStoreOptions) in
 @@ -53,18 +61,7 @@ public final class SecureStoreModule: Module {
      }
- 
+
      Function("canUseBiometricAuthentication") {() -> Bool in
 -      #if os(tvOS)
 -      return false
@@ -624,12 +624,12 @@ index 1268979..7764c8e 100644
 -      #endif
 +      return areBiometricsEnabled()
      }
- 
+
      Function("canUseDeviceCredentialsAuthentication") { () -> Bool in
 @@ -76,6 +73,41 @@ public final class SecureStoreModule: Module {
      return LAContext().canEvaluatePolicy(LAPolicy.deviceOwnerAuthentication, error: nil)
    }
- 
+
 +  private func getAuthType() -> AuthType {
 +    if !areBiometricsEnabled() {return AuthType.credentials}
 +    let biometryType = LAContext().biometryType
@@ -673,7 +673,7 @@ index c9e843b..b95eca7 100644
 --- a/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
 +++ b/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
 @@ -18,4 +18,71 @@ internal struct SecureStoreOptions: Record {
- 
+
    @Field
    var enableDeviceFallback: Bool = false
 +
@@ -751,7 +751,7 @@ index 4f253a1..d44c0bb 100644
 @@ -1,8 +1,63 @@
  import ExpoSecureStore from './ExpoSecureStore';
  import { byteCountOverLimit, VALUE_BYTES_LIMIT } from './byteCounter';
- 
+
 +type EmptyObject = Record<string, never>;
 +type SecureStoreSetFeedback<R extends SecureStoreOptions> =
 +    R['returnUsedAuthenticationType'] extends true ? AuthType : void;
@@ -760,7 +760,7 @@ index 4f253a1..d44c0bb 100644
 +    R extends SecureStoreOptions,
 +> = R['returnUsedAuthenticationType'] extends true ? [T | null, AuthType] : T | null;
  export type KeychainAccessibilityConstant = number;
- 
+
 +/**
 + * Authentication type returned by the SecureStore after reading item or saving it to the store.
 + */
@@ -834,7 +834,7 @@ index 4f253a1..d44c0bb 100644
 +   */
 +  returnUsedAuthenticationType?: boolean;
  };
- 
+
  // @needsAudit
 @@ -159,10 +231,10 @@ export async function deleteItemAsync(
   * > After a key has been invalidated, it becomes impossible to read its value.
@@ -868,11 +868,11 @@ index 4f253a1..d44c0bb 100644
 @@ -189,7 +261,7 @@ export async function setItemAsync(
      );
    }
- 
+
 -  await ExpoSecureStore.setValueWithKeyAsync(value, key, options);
 +  return await ExpoSecureStore.setValueWithKeyAsync(value, key, options);
  }
- 
+
  /**
 @@ -201,7 +273,11 @@ export async function setItemAsync(
   * @param options An [`SecureStoreOptions`](#securestoreoptions) object.

From f6f4721f07c1520873ee5b9fba90c005370279d4 Mon Sep 17 00:00:00 2001
From: Jakub Korytko <jakub@korytko.me>
Date: Thu, 4 Dec 2025 12:41:47 +0100
Subject: [PATCH 6/7] Update enable device fallback patch

---
 ...re+14.2.4+001+enable-device-fallback.patch | 125 ++++++++++++------
 1 file changed, 86 insertions(+), 39 deletions(-)

diff --git a/patches/expo-secure-store/expo-secure-store+14.2.4+001+enable-device-fallback.patch b/patches/expo-secure-store/expo-secure-store+14.2.4+001+enable-device-fallback.patch
index 9a0b432e66d9..d5d5edad1a25 100644
--- a/patches/expo-secure-store/expo-secure-store+14.2.4+001+enable-device-fallback.patch
+++ b/patches/expo-secure-store/expo-secure-store+14.2.4+001+enable-device-fallback.patch
@@ -3,7 +3,7 @@ index a281b88..4a1a009 100644
 --- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/AuthenticationHelper.kt
 +++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/AuthenticationHelper.kt
 @@ -2,6 +2,7 @@ package expo.modules.securestore
- 
+
  import android.annotation.SuppressLint
  import android.app.Activity
 +import android.app.KeyguardManager
@@ -13,7 +13,7 @@ index a281b88..4a1a009 100644
 @@ -19,9 +20,9 @@ class AuthenticationHelper(
  ) {
    private var isAuthenticating = false
- 
+
 -  suspend fun authenticateCipher(cipher: Cipher, requiresAuthentication: Boolean, title: String): Cipher {
 +  suspend fun authenticateCipher(cipher: Cipher, requiresAuthentication: Boolean, title: String, enableDeviceFallback: Boolean): Cipher {
      if (requiresAuthentication) {
@@ -23,7 +23,7 @@ index a281b88..4a1a009 100644
      }
      return cipher
 @@ -29,7 +30,8 @@ class AuthenticationHelper(
- 
+
    private suspend fun openAuthenticationPrompt(
      cipher: Cipher,
 -    title: String
@@ -34,7 +34,7 @@ index a281b88..4a1a009 100644
        throw AuthenticationException("Biometric authentication requires Android API 23")
 @@ -41,11 +43,16 @@ class AuthenticationHelper(
      isAuthenticating = true
- 
+
      try {
 -      assertBiometricsSupport()
 +      if (enableDeviceFallback) {
@@ -45,16 +45,16 @@ index a281b88..4a1a009 100644
 +
        val fragmentActivity = getCurrentActivity() as? FragmentActivity
          ?: throw AuthenticationException("Cannot display biometric prompt when the app is not in the foreground")
- 
+
 -      val authenticationPrompt = AuthenticationPrompt(fragmentActivity, context, title)
 +      val authenticationPrompt = AuthenticationPrompt(fragmentActivity, context, title, enableDeviceFallback)
- 
+
        return withContext(Dispatchers.Main.immediate) {
          return@withContext authenticationPrompt.authenticate(cipher)
 @@ -56,6 +63,14 @@ class AuthenticationHelper(
      }
    }
- 
+
 +  fun assertDeviceSecurity() {
 +    val manager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
 +    val isSecure = manager.isDeviceSecure
@@ -67,40 +67,54 @@ index a281b88..4a1a009 100644
      val biometricManager = BiometricManager.from(context)
      @SuppressLint("SwitchIntDef") // BiometricManager.BIOMETRIC_SUCCESS shouldn't do anything
 diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/AuthenticationPrompt.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/AuthenticationPrompt.kt
-index e5729cc..2543b0c 100644
+index e5729cc..ce448d6 100644
 --- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/AuthenticationPrompt.kt
 +++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/AuthenticationPrompt.kt
 @@ -1,5 +1,7 @@
  package expo.modules.securestore
- 
+
 +import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
 +import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
  import android.content.Context
  import androidx.biometric.BiometricPrompt
  import androidx.biometric.BiometricPrompt.PromptInfo
-@@ -11,11 +13,12 @@ import kotlin.coroutines.resume
+@@ -11,12 +13,23 @@ import kotlin.coroutines.resume
  import kotlin.coroutines.resumeWithException
  import kotlin.coroutines.suspendCoroutine
- 
+
 -class AuthenticationPrompt(private val currentActivity: FragmentActivity, context: Context, title: String) {
 +class AuthenticationPrompt(private val currentActivity: FragmentActivity, context: Context, title: String, enableDeviceFallback: Boolean) {
 +  private var authType: Int = if (enableDeviceFallback) BIOMETRIC_STRONG or DEVICE_CREDENTIAL else BIOMETRIC_STRONG
    private var executor: Executor = ContextCompat.getMainExecutor(context)
-   private var promptInfo = PromptInfo.Builder()
-     .setTitle(title)
+-  private var promptInfo = PromptInfo.Builder()
+-    .setTitle(title)
 -    .setNegativeButtonText(context.getString(android.R.string.cancel))
-+    .setAllowedAuthenticators(authType)
-     .build()
- 
+-    .build()
++  private var promptInfo = buildPromptInfo(context, title, enableDeviceFallback)
++
++  private fun buildPromptInfo(context: Context, title: String, enableDeviceFallback: Boolean): PromptInfo {
++    var prompt = PromptInfo.Builder()
++      .setTitle(title)
++      .setAllowedAuthenticators(authType)
++
++    if (!enableDeviceFallback) {
++      prompt = prompt.
++        setNegativeButtonText(context.getString(android.R.string.cancel))
++    }
++
++    return prompt.build()
++  }
+
    suspend fun authenticate(cipher: Cipher): BiometricPrompt.AuthenticationResult? =
+     suspendCoroutine { continuation ->
 diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt
-index 0fef884..3f21552 100644
+index 0fef884..bf9689b 100644
 --- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt
 +++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt
 @@ -75,6 +75,15 @@ open class SecureStoreModule : Module() {
        }
      }
- 
+
 +    Function("canUseDeviceCredentialsAuthentication") {
 +      return@Function try {
 +        authenticationHelper.assertDeviceSecurity()
@@ -121,7 +135,7 @@ index 0fef884..3f21552 100644
 +      val encryptedItem = mAESEncryptor.createEncryptedItem(value, secretKeyEntry, options.requireAuthentication, options.authenticationPrompt, authenticationHelper, options.enableDeviceFallback)
        encryptedItem.put(SCHEME_PROPERTY, AESEncryptor.NAME)
        saveEncryptedItem(encryptedItem, prefs, keychainAwareKey, options.requireAuthentication, options.keychainService)
- 
+
 @@ -343,7 +352,11 @@ open class SecureStoreModule : Module() {
      return getKeyEntry(keyStoreEntryClass, encryptor, options, requireAuthentication) ?: run {
        // Android won't allow us to generate the keys if the device doesn't support biometrics or no biometrics are enrolled
@@ -135,6 +149,14 @@ index 0fef884..3f21552 100644
        }
        encryptor.initializeKeyStoreEntry(keyStore, options)
      }
+@@ -383,6 +396,7 @@ open class SecureStoreModule : Module() {
+     private const val KEYSTORE_ALIAS_PROPERTY = "keystoreAlias"
+     const val USES_KEYSTORE_SUFFIX_PROPERTY = "usesKeystoreSuffix"
+     const val DEFAULT_KEYSTORE_ALIAS = "key_v1"
++    const val DEFAULT_FALLBACK_KEYSTORE_ALIAS = "fallback_key_v1"
+     const val AUTHENTICATED_KEYSTORE_SUFFIX = "keystoreAuthenticated"
+     const val UNAUTHENTICATED_KEYSTORE_SUFFIX = "keystoreUnauthenticated"
+   }
 diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt
 index 79a600f..0455fd6 100644
 --- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreOptions.kt
@@ -148,12 +170,12 @@ index 79a600f..0455fd6 100644
 +  @Field var enableDeviceFallback: Boolean = false
  ) : Record, Serializable
 diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/AESEncryptor.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/AESEncryptor.kt
-index 3a12dc9..1cd187f 100644
+index 3a12dc9..1cec6c0 100644
 --- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/AESEncryptor.kt
 +++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/AESEncryptor.kt
 @@ -1,9 +1,10 @@
  package expo.modules.securestore.encryptors
- 
+
 -import android.annotation.TargetApi
 +import android.os.Build
  import android.security.keystore.KeyGenParameterSpec
@@ -163,17 +185,29 @@ index 3a12dc9..1cd187f 100644
  import expo.modules.securestore.AuthenticationHelper
  import expo.modules.securestore.DecryptException
  import expo.modules.securestore.SecureStoreModule
-@@ -50,17 +51,22 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
+@@ -33,6 +34,11 @@ import javax.crypto.spec.GCMParameterSpec
+ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
+   override fun getKeyStoreAlias(options: SecureStoreOptions): String {
+     val baseAlias = options.keychainService
++
++    if (baseAlias == SecureStoreModule.DEFAULT_KEYSTORE_ALIAS && options.enableDeviceFallback) {
++      return "$AES_CIPHER:${SecureStoreModule.DEFAULT_FALLBACK_KEYSTORE_ALIAS}"
++    }
++
+     return "$AES_CIPHER:$baseAlias"
+   }
+
+@@ -50,17 +56,22 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
      return "${getKeyStoreAlias(options)}:$suffix"
    }
- 
+
 -  @TargetApi(23)
 +  @RequiresApi(Build.VERSION_CODES.R)
    @Throws(GeneralSecurityException::class)
    override fun initializeKeyStoreEntry(keyStore: KeyStore, options: SecureStoreOptions): KeyStore.SecretKeyEntry {
      val extendedKeystoreAlias = getExtendedKeyStoreAlias(options, options.requireAuthentication)
      val keyPurposes = KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
- 
+
 +    val authType =
 +      if (options.enableDeviceFallback) KeyProperties.AUTH_BIOMETRIC_STRONG or KeyProperties.AUTH_DEVICE_CREDENTIAL
 +      else KeyProperties.AUTH_BIOMETRIC_STRONG
@@ -185,9 +219,9 @@ index 3a12dc9..1cd187f 100644
        .setUserAuthenticationRequired(options.requireAuthentication)
 +      .setUserAuthenticationParameters(0, authType)
        .build()
- 
+
      val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, keyStore.provider)
-@@ -78,14 +84,15 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
+@@ -78,14 +89,15 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
      keyStoreEntry: KeyStore.SecretKeyEntry,
      requireAuthentication: Boolean,
      authenticationPrompt: String,
@@ -198,14 +232,14 @@ index 3a12dc9..1cd187f 100644
      val secretKey = keyStoreEntry.secretKey
      val cipher = Cipher.getInstance(AES_CIPHER)
      cipher.init(Cipher.ENCRYPT_MODE, secretKey)
- 
+
      val gcmSpec = cipher.parameters.getParameterSpec(GCMParameterSpec::class.java)
 -    val authenticatedCipher = authenticationHelper.authenticateCipher(cipher, requireAuthentication, authenticationPrompt)
 +    val authenticatedCipher = authenticationHelper.authenticateCipher(cipher, requireAuthentication, authenticationPrompt, enableDeviceFallback)
- 
+
      return createEncryptedItemWithCipher(plaintextValue, authenticatedCipher, gcmSpec)
    }
-@@ -128,7 +135,7 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
+@@ -128,7 +140,7 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
        throw DecryptException("Authentication tag length must be at least $MIN_GCM_AUTHENTICATION_TAG_LENGTH bits long", key, options.keychainService)
      }
      cipher.init(Cipher.DECRYPT_MODE, keyStoreEntry.secretKey, gcmSpec)
@@ -213,12 +247,24 @@ index 3a12dc9..1cd187f 100644
 +    val unlockedCipher = authenticationHelper.authenticateCipher(cipher, requiresAuthentication, options.authenticationPrompt, options.enableDeviceFallback)
      return String(unlockedCipher.doFinal(ciphertextBytes), StandardCharsets.UTF_8)
    }
- 
+
 diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/HybridAESEncryptor.kt b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/HybridAESEncryptor.kt
-index fb42599..5f8bbfd 100644
+index fb42599..e996b39 100644
 --- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/HybridAESEncryptor.kt
 +++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/encryptors/HybridAESEncryptor.kt
-@@ -69,7 +69,8 @@ class HybridAESEncryptor(private var mContext: Context, private val mAESEncrypto
+@@ -51,6 +51,11 @@ class HybridAESEncryptor(private var mContext: Context, private val mAESEncrypto
+
+   override fun getKeyStoreAlias(options: SecureStoreOptions): String {
+     val baseAlias = options.keychainService
++
++    if (baseAlias == SecureStoreModule.DEFAULT_KEYSTORE_ALIAS && options.enableDeviceFallback) {
++      return "$RSA_CIPHER:${SecureStoreModule.DEFAULT_FALLBACK_KEYSTORE_ALIAS}"
++    }
++
+     return "$RSA_CIPHER:$baseAlias"
+   }
+
+@@ -69,7 +74,8 @@ class HybridAESEncryptor(private var mContext: Context, private val mAESEncrypto
      keyStoreEntry: KeyStore.PrivateKeyEntry,
      requireAuthentication: Boolean,
      authenticationPrompt: String,
@@ -240,10 +286,10 @@ index e493467..39459ff 100644
 +    authenticationHelper: AuthenticationHelper,
 +    enableDeviceFallback: Boolean,
    ): JSONObject
- 
+
    @Throws(GeneralSecurityException::class, JSONException::class)
 diff --git a/node_modules/expo-secure-store/build/SecureStore.d.ts b/node_modules/expo-secure-store/build/SecureStore.d.ts
-index d5cd157..be9acfe 100644
+index d5cd157..835a4d4 100644
 --- a/node_modules/expo-secure-store/build/SecureStore.d.ts
 +++ b/node_modules/expo-secure-store/build/SecureStore.d.ts
 @@ -78,6 +78,16 @@ export type SecureStoreOptions = {
@@ -275,6 +321,7 @@ index d5cd157..be9acfe 100644
 + */
 +export declare function canUseDeviceCredentialsAuthentication(): boolean;
  //# sourceMappingURL=SecureStore.d.ts.map
+\ No newline at end of file
 diff --git a/node_modules/expo-secure-store/build/SecureStore.js b/node_modules/expo-secure-store/build/SecureStore.js
 index 4d87b38..e11bd94 100644
 --- a/node_modules/expo-secure-store/build/SecureStore.js
@@ -312,7 +359,7 @@ index 439b08d..1268979 100644
 +  private func areDeviceCredentialsEnabled() -> Bool {
 +    return LAContext().canEvaluatePolicy(LAPolicy.deviceOwnerAuthentication, error: nil)
    }
- 
+
    private func get(with key: String, options: SecureStoreOptions) throws -> String? {
 @@ -99,12 +107,17 @@ public final class SecureStoreModule: Module {
      if !options.requireAuthentication {
@@ -325,7 +372,7 @@ index 439b08d..1268979 100644
 +          throw MissingPlistKeyException()
 +        }
        }
- 
+
        var error: Unmanaged<CFError>? = nil
 -      guard let accessOptions = SecAccessControlCreateWithFlags(kCFAllocatorDefault, accessibility, .biometryCurrentSet, &error) else {
 +
@@ -340,7 +387,7 @@ index 7e3fa4d..c9e843b 100644
 --- a/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
 +++ b/node_modules/expo-secure-store/ios/SecureStoreOptions.swift
 @@ -15,4 +15,7 @@ internal struct SecureStoreOptions: Record {
- 
+
    @Field
    var accessGroup: String?
 +
@@ -367,12 +414,12 @@ index ee43e04..4f253a1 100644
 +   */
 +  enableDeviceFallback?: boolean;
  };
- 
+
  // @needsAudit
 @@ -226,6 +237,16 @@ export function canUseBiometricAuthentication(): boolean {
    return ExpoSecureStore.canUseBiometricAuthentication();
  }
- 
+
 +/**
 + * Checks whether any device credentials are configured on the device.
 + * @return `true` if the device has device credentials configured. Otherwise, returns `false`.

From b07d42ea7640d543a41fe37ffb0f11a41b5bacdc Mon Sep 17 00:00:00 2001
From: Jakub Korytko <jakub@korytko.me>
Date: Thu, 4 Dec 2025 13:49:04 +0100
Subject: [PATCH 7/7] Update expo-secure-store patches details

---
 patches/expo-secure-store/details.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/patches/expo-secure-store/details.md b/patches/expo-secure-store/details.md
index 6bc1a8c084b1..d6d176d012a1 100644
--- a/patches/expo-secure-store/details.md
+++ b/patches/expo-secure-store/details.md
@@ -10,7 +10,7 @@
     Additionally, support for screen locks can be checked using the new 'canUseDeviceCredentialsAuthentication' method. 
     ```
 
-- Upstream PR/issue: TBA
+- Upstream PR/issue: https://github.com/expo/expo/pull/41409
 - E/App issue: https://github.com/Expensify/App/issues/75225
 - PR introducing patch: https://github.com/Expensify/App/pull/76288
 
