# Add seamless sign-in to your React Native onramp integration

Skip the OTP dialog for returning users by storing and reusing a Link authentication 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 back end exchanges the ID for a short-lived `linkAuthTokenClientSecret` that the SDK uses to authenticate the user without showing any 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 back end.

```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 back end [Server-side]

On subsequent visits, retrieve the stored `authIntentId` and pass it to your back end. Your back end 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');
  }
};
```
