Play Restore Keys · Guides
Google Play restore keys in React Native and Expo apps
From April 2027, Google Play requires phone and tablet apps that sign users in to sign those users back in on a new device without asking. Google's requirement says: "Successful user sign in restoration is determined through the successful restore key retrieval." The calls are Android Kotlin APIs, so a React Native and Expo app needs a small native bridge. When we searched npm on 10 October 2026 we found no React Native or Expo package for restore keys.
This guide has the native code, the bridge and what your server must do. Every API name was checked against Google's implementation guide and the androidx reference pages on 11 October 2026. It is technical help, not a Google Play compliance review.
What you need
- Android 9 or later, Google Play services core 24220000 or later and androidx.credentials 1.5.0 or later (Google's minimums; Google recommends the latest stable library).
- compileSdk 35 or later in your Android build: androidx.credentials 1.5.0 requires it.
- A relying-party server. A restore key is a passkey-style (WebAuthn) key: your server sends the options, the phone creates or signs with the key, and your server checks the result.
- Restore keys are Android only. On iOS the bridge below does nothing.
1. The native calls (Kotlin, the same for every framework)
Add the dependencies to the Android module that holds the code (Kotlin DSL shown; in a Groovy build.gradle use implementation "..."; 1.5.0 or any later version):
dependencies {
implementation("androidx.credentials:credentials:1.5.0")
implementation("androidx.credentials:credentials-play-services-auth:1.5.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")
}
Then one class wraps the three calls. Creating the key with cloud backup on is Google's recommendation. Without a Google Account backup or a screen lock that throws E2eeUnavailableException, so the class tries again with cloud backup off.
package com.example.restorekeys
import android.content.Context
import androidx.credentials.ClearCredentialStateRequest
import androidx.credentials.CreateRestoreCredentialRequest
import androidx.credentials.CreateRestoreCredentialResponse
import androidx.credentials.CredentialManager
import androidx.credentials.GetCredentialRequest
import androidx.credentials.GetRestoreCredentialOption
import androidx.credentials.RestoreCredential
import androidx.credentials.exceptions.NoCredentialException
import androidx.credentials.exceptions.restorecredential.E2eeUnavailableException
// The three restore key calls. Every JSON string comes from your server or goes back to it.
class RestoreKeyClient(private val context: Context) {
private val credentialManager = CredentialManager.create(context)
// After sign-in. creationOptionsJson: your server's PublicKeyCredentialCreationOptionsJSON.
// Returns the registration response JSON for your server to verify and store.
suspend fun create(creationOptionsJson: String): String {
val response = try {
credentialManager.createCredential(context, CreateRestoreCredentialRequest(creationOptionsJson, true))
} catch (e: E2eeUnavailableException) {
// no backup or no screen lock: keep the key on the device, a direct transfer still carries it
credentialManager.createCredential(context, CreateRestoreCredentialRequest(creationOptionsJson, false))
}
return (response as CreateRestoreCredentialResponse).responseJson
}
// On first launch. requestOptionsJson: your server's PublicKeyCredentialRequestOptionsJSON.
// Returns the authentication response JSON for your server to verify, or null if there is no restore key.
suspend fun get(requestOptionsJson: String): String? = try {
val request = GetCredentialRequest(listOf(GetRestoreCredentialOption(requestOptionsJson)))
(credentialManager.getCredential(context, request).credential as RestoreCredential).authenticationResponseJson
} catch (e: NoCredentialException) {
null
}
// On sign-out. Deletes the restore key from the device and from the backup.
suspend fun clear() {
credentialManager.clearCredentialState(
ClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL)
)
}
}
2. Bridge it to JavaScript
React Native
A classic native module, in the same Android package as RestoreKeyClient under your app's android/ folder. React Native's New Architecture still loads classic modules through its interop layer. For a Turbo Module, keep RestoreKeyClient and declare the same three methods in your codegen spec.
package com.example.restorekeys
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
class RestoreKeysModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private val keys = RestoreKeyClient(reactContext)
override fun getName() = "RestoreKeys"
@ReactMethod
fun create(creationOptionsJson: String, promise: Promise) {
scope.launch {
try { promise.resolve(keys.create(creationOptionsJson)) } catch (e: Exception) { promise.reject("restore_key", e) }
}
}
@ReactMethod
fun get(requestOptionsJson: String, promise: Promise) {
scope.launch {
try { promise.resolve(keys.get(requestOptionsJson)) } catch (e: Exception) { promise.reject("restore_key", e) }
}
}
@ReactMethod
fun clear(promise: Promise) {
scope.launch {
try { keys.clear(); promise.resolve(null) } catch (e: Exception) { promise.reject("restore_key", e) }
}
}
}
package com.example.restorekeys
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
class RestoreKeysPackage : ReactPackage {
override fun createNativeModules(context: ReactApplicationContext): List<NativeModule> = listOf(RestoreKeysModule(context))
override fun createViewManagers(context: ReactApplicationContext): List<ViewManager<*, *>> = emptyList()
}
// MainApplication.kt, inside getPackages():
// PackageList(this).packages.apply { add(RestoreKeysPackage()) }
The JavaScript side:
import { NativeModules, Platform } from "react-native";
// Restore keys exist on Android only; on iOS every call is a no-op.
const native = Platform.OS === "android" ? NativeModules.RestoreKeys : null;
export const RestoreKeys = {
create: (json) => (native ? native.create(json) : Promise.resolve(null)),
get: (json) => (native ? native.get(json) : Promise.resolve(null)),
clear: () => (native ? native.clear() : Promise.resolve()),
};
Expo
Expo Go cannot load your own native code, so use a development build. Create a local module with npx create-expo-module@latest --local, put RestoreKeyClient in its android/ folder and add the dependencies from step 1 to that module's build.gradle. The module, using the Expo Modules API's suspending functions:
package expo.modules.restorekeys
import com.example.restorekeys.RestoreKeyClient
import expo.modules.kotlin.functions.Coroutine
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
class RestoreKeysModule : Module() {
private val keys by lazy { RestoreKeyClient(requireNotNull(appContext.reactContext)) }
override fun definition() = ModuleDefinition {
Name("RestoreKeys")
AsyncFunction("create") Coroutine { creationOptionsJson: String -> keys.create(creationOptionsJson) }
AsyncFunction("get") Coroutine { requestOptionsJson: String -> keys.get(requestOptionsJson) }
AsyncFunction("clear") Coroutine { -> keys.clear() }
}
}
In JavaScript, requireNativeModule("RestoreKeys") from expo-modules-core returns an object with the same three calls. Call it only when Platform.OS === "android".
The calls, in order:
// After your normal sign-in succeeds (your server's routes; the names are examples)
const options = await api.post("/restore-key/register/options");
const registration = await RestoreKeys.create(JSON.stringify(options));
if (registration) await api.post("/restore-key/register", JSON.parse(registration)); // null on iOS
// On first launch, when no one is signed in
const request = await api.post("/restore-key/login/options");
const assertion = await RestoreKeys.get(JSON.stringify(request));
if (assertion) await api.post("/restore-key/login", JSON.parse(assertion));
// On sign-out
await RestoreKeys.clear();
Create a key after every sign-in, and once at launch for a signed-in user who has none yet, such as someone who signed in before your update. If your app has a BackupAgent, also retrieve the key in onRestoreFinished, as Google's guide recommends.
3. The server
Your server needs four routes: creation options and verification when the key is made, and request options and verification when it is used. The options use the WebAuthn JSON formats PublicKeyCredentialCreationOptionsJSON and PublicKeyCredentialRequestOptionsJSON. The user.id must be a valid WebAuthn user ID or creating the key fails. Any WebAuthn server library can check the results, with two settings for restore keys: do not require user verification, because a restore runs without the user, and store them apart from passkeys so they never show up in a user's passkey list. When the restore response checks out, start a session for that user as your sign-in normally does. A Firebase Authentication app can mint a custom token with the Admin SDK and sign in with it.
Play Restore Keys checks your package name, signing fingerprints and domain for free and lists the server routes to build. It has 10 free checks a day and needs no account. We are also considering a hosted restore-key server for Firebase Auth apps. Nothing is for sale; you can join the waitlist on the Play Restore Keys page.
4. Test it
Android Studio can back up an app and restore it onto an emulator. Follow Google's testing page with both a Device to Device and a Cloud backup. The test passes when the reinstalled app signs in by itself. Our step-by-step test guide covers what the emulator does not show.
Other frameworks: Capacitor and Ionic · Cordova · Flutter