Private preview
Add seamless sign-in to your React Native on-ramp integration Private preview
Skip the OTP dialogue for returning users by reusing a previous Link authorisation.
Seamless sign-in lets returning users skip the one-time password (OTP) dialogue by reusing a previous authorisation. After a user authorises 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.
Note
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 on-ramp 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 authorises your app to create Link authentication tokens for seamless sign-in on future visits.
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 authorisation – 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.
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 authorisation, or load tokens stored after the original authorisation. If the access token expired, use the refresh token to obtain a new one. 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 authorisation. The response contains a token (the linkAuthTokenClientSecret) and an expires_in value indicating how long it’s valid.
{
"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.
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.
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.
const { logOut } = useOnramp();
const handleLogOut = async () => {
const result = await logOut();
if (result?.error) {
// Handle error.
} else {
// Clear the stored authIntentId.
await secureStorage.delete('linkAuthIntentId');
}
};