# Add seamless sign-in to your React Native onramp integration Skip the OTP dialog for returning users by storing and reusing a Link auth token. Seamless sign-in lets returning users skip the One-time Password (OTP) dialog by reusing a token from a previous session. After a user authorizes your app for the first time, store their `authIntentId`. On later visits, your backend exchanges that ID for a short-lived `linkAuthTokenClientSecret`, which the SDK uses to authenticate the user without showing UI. > Seamless sign-in requires Stripe to enable the feature for your account. Work with your Stripe account executive or solutions architect to enable it. ## Before you begin You must [integrate the Embedded Components onramp](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md) before you can add seamless sign-in. ## Request the seamless sign-in scope [Server-side] When you create a `LinkAuthIntent`, include `auth.persist_login:read` in your OAuth scopes. This scope authorizes your app to create Link authentication tokens for seamless sign-in on future visits. ```shell curl -X POST https://login.link.com/v1/link_auth_intent \ -H "Authorization: Bearer $STRIPE_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"email": "user@example.com", "oauth_client_id": "$OAUTH_CLIENT_ID", "oauth_scopes": "kyc.status:read,crypto:ramp,auth.persist_login:read"}' ``` ## Store the token after authorization [Client-side] After `authorize` returns a `Consented` result, store the `authIntentId` securely (for example, in encrypted storage). You’ll use it in the next session to obtain a `linkAuthTokenClientSecret` from your backend. ```typescript import { useOnramp } from '@stripe/stripe-react-native'; const { authorize } = useOnramp(); const result = await authorize(authIntentId); if (result?.status === 'Consented' && result.customerId) { // Store authIntentId securely to enable seamless sign-in on the next visit. await secureStorage.set('linkAuthIntentId', authIntentId); } ``` ## Exchange the token on your backend [Server-side] On subsequent visits, retrieve the stored `authIntentId` and pass it to your backend. Your backend uses the OAuth access token obtained during the original authorization to call the Link authentication token API. The response contains a `token` (the `linkAuthTokenClientSecret`) and an `expires_in` indicating how long it’s valid. ```shell curl 'https://api.stripe.com/v1/link/auth_token' \ -X POST \ -H "Authorization: Bearer $STRIPE_SECRET_KEY" \ -H "Stripe-OAuth-Token: $ACCESS_TOKEN" ``` ```json { "object": "link.link_auth_token", "expires_in": 5400, "token": "latcs_****" } ``` Pass the `token` value back to your client as the `linkAuthTokenClientSecret`. ## Authenticate the returning user [Client-side] Call `authenticateUserWithToken` from `useOnramp` with the `linkAuthTokenClientSecret`. If authentication succeeds, the user signs in without any UI. ```typescript import { useOnramp } from '@stripe/stripe-react-native'; function SeamlessSignIn() { const { authenticateUserWithToken } = useOnramp(); const handleSeamlessSignIn = async (linkAuthTokenClientSecret: string) => { const result = await authenticateUserWithToken(linkAuthTokenClientSecret); if (result?.error) { // Authentication failed. Clear the stored token and fall back to the standard flow. } else { // User authenticated. Proceed to the onramp session. } }; } ``` ## Handle errors [Client-side] `authenticateUserWithToken` can return an error for several reasons, such as when the user revokes OAuth consent. When `authenticateUserWithToken` returns an error, clear the stored token and fall back to the standard authentication flow with `hasLinkAccount` and `authorize`. ```typescript const result = await authenticateUserWithToken(linkAuthTokenClientSecret); if (result?.error) { // Clear the stored token — it may be expired, already used, or revoked. await secureStorage.delete('linkAuthIntentId'); // Fall back to standard authentication. const linkResult = await hasLinkAccount(email); // ... proceed with hasLinkAccount → authorize flow } ``` ## Log out [Client-side] When the user logs out, call `logOut()` to clear all SDK state and delete any locally stored tokens used for seamless sign-in. ```typescript const { logOut } = useOnramp(); const handleLogOut = async () => { const result = await logOut(); if (result?.error) { // Handle error. } else { // Clear stored seamless sign-in tokens. await secureStorage.delete('linkAuthIntentId'); } }; ```