Skip to main content
Rain supports two authentication methods: one time codes sent by email or SMS, and passkeys. Both methods can be associated with the same wallet, so adding another authentication method does not create a new wallet. For most integrations, we recommend starting with email authentication. It requires minimal setup, works across devices, and provides a straightforward recovery path. You can also offer passkeys for users who want faster authentication and stronger phishing resistance. If passkeys are the primary authentication method, require users to add an email address as a backup before funding their wallet.

Choosing an authentication method

For most applications, enable email authentication and optionally offer passkeys. If you use passkeys as the primary authentication method, require users to enroll an email address as a backup before they fund their wallet.

Accounts and identity

Rules that shape your login screens:
  • Email and phone number identify the account. The first sign up with an email address or phone number creates an account. Future logins with that same contact return the user to the same wallet. If the same person signs up separately with email and SMS, Rain treats them as separate accounts unless one contact is added to the existing account.
  • Accounts are not automatically merged. If a user creates two accounts, they will have two separate wallets. Returning users should use Log in or Add a passkey rather than Create account.
  • Passkey sign up creates an account without a contact method. signUpWithPasskey creates an account whose initial authentication method is the passkey. Prompt the user to add an email address as a backup immediately after sign up before funding the wallet. See Backup and recovery.
  • The same account works across supported platforms. A user who signs up on iOS can log in on Android with an authentication method already associated with their account and access the same wallet.

Logging in with a one-time code

1

Send the code

Call sendLoginCode with the contact. Rain sends a 6-digit code. Calling again for the same contact replaces the pending code, which is your Resend button.
Phone numbers must be in international (E.164) format. Validation failures throw RAIN_102 before any request is made.
2

Confirm the code

Call confirmLoginCode with what the user typed. Success logs the user in; on a first login it also creates the wallet. A wrong code throws RAIN_203 and leaves the pending challenge intact, so the user retries without a new code. Codes expire after 5 minutes and lock after 3 wrong attempts; after either, send a new one.

Logging in with a passkey

Passkeys are available on iOS and Android. A passkey created on one platform is bound to your domain and your app, so the same account can hold passkeys from both.
1

Set up your domain

Choose a domain you’ll keep. Passkeys are scoped to it: users’ passkeys won’t work if you change it later, and the same account can hold passkeys from more than one domain. Rain runs no shared domain; you host the association files yourself.
  • iOS: Host https://<domain>/.well-known/apple-app-site-association listing your app under webcredentials, and add webcredentials:<domain> to your app’s Associated Domains.
  • Android: Host https://<domain>/.well-known/assetlinks.json with two statements: one for the site itself granting delegate_permission/common.get_login_creds, and an android_app statement granting both delegate_permission/common.handle_all_urls and delegate_permission/common.get_login_creds that lists your package name and the SHA-256 fingerprint of every certificate that signs your app (debug, upload, and Play App Signing). The device refuses a file that carries the app statement alone.
Then pass the domain to the SDK:
The domain is applied once per app launch. Without it, every passkey call throws RAIN_102. On Android, a value that isn’t a registrable domain of at least two labels (a scheme, port, path, or localhost) throws RAIN_102 from the constructor.
2

Offer the right action

Three calls cover the flows. On iOS each takes an anchor, the window the system sheet presents from; on Android each takes the foreground Activity and suspends until the sheet closes.
Show Sign in with passkey and Create account as distinct choices, and put Add a passkey inside the app for users who logged in with a code. Call logout() before starting a passkey sign-up while a session is live: the SDK refuses with RAIN_102 otherwise. On iOS the same applies to a passkey login over a live passkey session; on Android a passkey login replaces the current session. A dismissed sheet throws RAIN_401 and leaves the current session untouched.
3

Ask for a backup contact

A passkey-only account is one lost device away from being unreachable if the passkey isn’t synced. After sign-up, collect an email address and verify it with sendContactVerificationCode and confirmContactVerification. The verified contact becomes a login method for the account. Details in Backup and recovery.

Sessions

A successful login stores a session in the device’s secure storage. The SDK manages it from there:
  • Restore. On launch, awaitSessionRestore() waits (up to 5 seconds) for the stored session to load. Then hasActiveSession() tells you whether to show the login screen.
  • Observe. authState reports loading, authenticated, or unauthenticated; bind your login gate to it. A finer sessionState adds active(expiresAt) and expired if you want to show a countdown or pre-empt expiry.
  • Refresh. Sessions refresh automatically ahead of expiry (RainWalletSessionPolicy: 60-second buffer, two transient retries with backoff). Reads are retried on transient failures; sends never are. Call refreshSession() to force one, for example when the app returns to the foreground.
  • Expiry. When a session can’t be refreshed, or the user logged in on another device, the SDK fires onSessionExpired once and calls throw RAIN_201 until the user logs in again. Ending a session with logout() doesn’t fire the hook.
  • One device at a time. A login ends the user’s sessions on other devices. The signed-out device finds out on its next call.
onSessionExpired is called from a background context. Switch to the main thread before touching UI, and don’t capture short-lived objects in it: the provider holds the hook for its whole life.

What’s next

Wallet creation

What a first login creates, and what to do with the addresses.

Backup and recovery

Keep every account reachable.