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

Skip the OTP dialog for returning users by reusing a previous Link authorization.

Seamless sign-in lets returning users skip the one-time password (OTP) dialog by reusing a previous authorization. After a user authorizes your app for the first time, store the `authIntentId`. On later visits, your back end uses the `authIntentId` to obtain an OAuth access token for the user and create a short-lived `linkAuthTokenClientSecret`. The SDK uses this client secret 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 authIntentId [Client-side]

After `authorize` returns a `Consented` result, store the `authIntentId` so your back end can obtain an OAuth access token for the user on later visits. The `authIntentId` identifies the previous authorization—it isn’t an OAuth access token. You can store it in encrypted client storage or associate it with the user on your back end.

On a subsequent visit, send the `authIntentId` to your back end, or use the user’s authenticated app session to look it up. Before using the `authIntentId`, verify that it belongs to the current user.

```typescript
import { useOnramp } from '@stripe/stripe-react-native';

const { authorize } = useOnramp();

const result = await authorize(authIntentId);

if (result?.status === 'Consented' && result.customerId) {
  // Store authIntentId to enable seamless sign-in on the next visit.
  await secureStorage.set('linkAuthIntentId', authIntentId);
}
```

## Create an authentication token on your back end [Server-side]

Use the `authIntentId` to [retrieve the OAuth tokens associated with the previous authorization](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#retrieve-access-tokens), or load tokens stored after the original authorization. If the access token expired, [use the refresh token to obtain a new one](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#refresh-an-access-token). To limit credential exposure, we recommend keeping OAuth access tokens and refresh tokens on your back end instead of returning them to the client.

Call the Link authentication token API with the OAuth access token in the `Stripe-OAuth-Token` header and your Stripe secret key in the `Authorization` header. This request doesn’t accept or require the `authIntentId`; the `authIntentId` only lets your back end obtain the OAuth access token associated with the previous authorization. The response contains a `token` (the `linkAuthTokenClientSecret`) and an `expires_in` value 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 authIntentId 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 `authIntentId` and fall back to the standard authentication flow with `hasLinkAccount` and `authorize`.

```typescript
const result = await authenticateUserWithToken(linkAuthTokenClientSecret);

if (result?.error) {
  // Clear the stored authIntentId—the authorization may be expired 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 the locally stored `authIntentId` used for seamless sign-in.

```typescript
const { logOut } = useOnramp();

const handleLogOut = async () => {
  const result = await logOut();

  if (result?.error) {
    // Handle error.
  } else {
    // Clear the stored authIntentId.
    await secureStorage.delete('linkAuthIntentId');
  }
};
```
