> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://www.openfort.io/api/mcp` to find what you need.
>
> **Have feedback?** Use `submit_feedback` on the same MCP server.

# User management

One Openfort user can hold several login methods on the same account and the same embedded wallet. Let users link another method at any point, not only at sign-in, and unlink methods they no longer use.

Every linking call needs an active session. The examples assume the user is already signed in. In JavaScript, `openfort` is your initialized `Openfort` instance.

## When linking happens automatically

Email is the identity key. Within a project environment, a sign-in that carries an email Openfort has already seen resolves to the existing user and their embedded wallet. A user who signs up with `ada@example.com` and later signs in with Google on that address lands on the same account without any linking call.

* **Both sides need a verified email.** The provider must report the address as verified, and the existing user's email must be verified. Otherwise the sign-in fails with `account_not_linked`. Facebook never reports emails as verified, so it always needs an explicit link. See [Link Facebook to an existing account](#link-facebook-to-an-existing-account).
* **Emails are scoped per project environment.** The same address in another project, or in production versus staging, is a different user with a different wallet.
* **Only the email is matched.** Openfort doesn't deduplicate on name, phone number, or provider account ID.

These flows create a separate user and wallet unless you link them yourself:

* The provider returns no email. X (Twitter) omits it unless your app has elevated API access.
* The provider returns a different email, such as an Apple Private Relay address.
* The user signs in with a wallet (SIWE), which has no email to match on.

:::warning
Linking only works going forward. It doesn't merge two accounts that already exist or move assets between their wallets. Prompt users to link while they're signed in, before a second wallet exists.
:::

## Link a social account

Start an OAuth flow that attaches the provider to the signed-in user instead of creating a new one:

:::code-group
```tsx [React]
import { OAuthProvider, useOAuth } from '@openfort/react'

const { linkOauth } = useOAuth()

const linkGoogle = () => linkOauth({ provider: OAuthProvider.GOOGLE })
```

```tsx [React Native]
import { OAuthProvider, useOAuth } from '@openfort/react-native'

const { linkOauth } = useOAuth()

const linkGoogle = () => linkOauth({ provider: OAuthProvider.GOOGLE })
```

```ts [JavaScript]
import { OAuthProvider } from '@openfort/openfort-js'

const url = await openfort.auth.initLinkOAuth({
  provider: OAuthProvider.GOOGLE,
  redirectTo: 'https://your-app.com/auth/callback',
})
window.location.href = url
```
:::

In JavaScript, `initLinkOAuth` also accepts `options.scopes` and `options.skipBrowserRedirect`. Guest users ignore `options`.

### Link Facebook to an existing account

Openfort never merges a Facebook sign-in into an existing user automatically, because Facebook doesn't verify the email it returns. Trusting it would let anyone with a Facebook profile on a matching address take over the account, so a Facebook sign-in on an existing email fails with `account_not_linked`.

To add Facebook and keep the user's wallet:

1. Sign the user in with a method they already have, such as email OTP.
2. Link `OAuthProvider.FACEBOOK` from that session with the call above.
3. After the user approves in Facebook, Openfort attaches the Facebook account to the signed-in user. The user ID and embedded wallet stay the same, and later Facebook sign-ins resolve to this user.

The Facebook email doesn't need to match, and Openfort doesn't change the user's email or its verification status. If the redirect carries `error=account_already_linked_to_different_user`, that Facebook account belongs to another user in the same project environment. Unlink it there first.

## Link an email

Add an email to the account, for example to upgrade a guest user. Openfort sends a verification email to the address:

:::code-group
```tsx [React]
import { useEmailAuth } from '@openfort/react'

const { linkEmail } = useEmailAuth()

await linkEmail({
  email: 'ada@example.com',
  emailVerificationRedirectTo: 'https://your-app.com/verify-email',
})
```

```tsx [React Native]
import { useOpenfortClient } from '@openfort/react-native'

const client = useOpenfortClient()

await client.auth.addEmail({
  email: 'ada@example.com',
  callbackURL: 'https://your-app.com/verify-email',
})
```

```ts [JavaScript]
await openfort.auth.addEmail({
  email: 'ada@example.com',
  callbackURL: 'https://your-app.com/verify-email',
})
```
:::

## Link a phone number

Send an SMS code to the number, then link it with the code the user enters:

:::code-group
```tsx [React]
import { usePhoneOtpAuth } from '@openfort/react'

const { requestPhoneOtp, linkPhoneOtp } = usePhoneOtpAuth()

await requestPhoneOtp({ phoneNumber: '+15551234567' })
await linkPhoneOtp({ phoneNumber: '+15551234567', otp: '123456' })
```

```tsx [React Native]
import { useOpenfortClient } from '@openfort/react-native'

const client = useOpenfortClient()

await client.auth.requestPhoneOtp({ phoneNumber: '+15551234567' })
await client.auth.linkPhoneOtp({ phoneNumber: '+15551234567', otp: '123456' })
```

```ts [JavaScript]
await openfort.auth.requestPhoneOtp({ phoneNumber: '+15551234567' })
await openfort.auth.linkPhoneOtp({ phoneNumber: '+15551234567', otp: '123456' })
```
:::

## Link a wallet

Link an external wallet with Sign-In with Ethereum (SIWE). React connects the wallet and signs the message for you through wagmi. In React Native and JavaScript you get a nonce, sign the SIWE message with the user's wallet, then submit the signature:

:::code-group
```tsx [React]
import { useWalletAuth } from '@openfort/react/wagmi'

const { availableWallets, linkWallet } = useWalletAuth()

// Pass the connector ID of the wallet the user picks
await linkWallet(availableWallets[0].id)
```

```tsx [React Native]
import { useWalletAuth } from '@openfort/react-native'

const { generateSiweMessage, linkSiwe } = useWalletAuth()

const { message } = await generateSiweMessage({
  wallet: address,
  from: { domain: 'your-app.com', uri: 'https://your-app.com' },
  link: true,
})
const signature = await signMessage(message) // sign with the user's wallet
await linkSiwe({ signature, walletAddress: address, messageOverride: message })
```

```ts [JavaScript]
const { nonce } = await openfort.auth.initLinkSiwe({ address })

// Build a SIWE message with this nonce and sign it with the user's wallet
await openfort.auth.linkWithSiwe({
  signature,
  message,
  address,
  chainId: 1,
  walletClientType: 'metamask',
  connectorType: 'injected',
})
```
:::

## Unlink accounts

Remove a social account or wallet the user linked earlier. React and React Native call the underlying client:

:::code-group
```tsx [React]
import { OAuthProvider, useOpenfort } from '@openfort/react'

const client = useOpenfort((s) => s.client)

await client.auth.unlinkOAuth({ provider: OAuthProvider.GOOGLE })
await client.auth.unlinkWallet({ address, chainId: 1 })
```

```tsx [React Native]
import { OAuthProvider, useOpenfortClient } from '@openfort/react-native'

const client = useOpenfortClient()

await client.auth.unlinkOAuth({ provider: OAuthProvider.GOOGLE })
await client.auth.unlinkWallet({ address, chainId: 1 })
```

```ts [JavaScript]
import { OAuthProvider } from '@openfort/openfort-js'

await openfort.auth.unlinkOAuth({ provider: OAuthProvider.GOOGLE })
await openfort.auth.unlinkWallet({ address, chainId: 1 })
```
:::

In React, the `OpenfortButton` modal also lets users link and remove providers from their account screen without extra code.

The user's linked methods are listed in `user.linkedAccounts`, returned by `useUser` in React and React Native and by `openfort.user.get()` in JavaScript.
