Account Linking
Link multiple sign-in methods to the same EdgeBase user. This covers both anonymous account upgrades and attaching additional OAuth providers to an already signed-in account. In every case, the userId stays the same, so existing app data does not move.
Supported Flows
Anonymous upgrade
Start with an anonymous session, then attach a permanent credential:
- Email/password
- OAuth
- Phone OTP
The account keeps the same userId, flips isAnonymous from true to false, and gets a fresh JWT.
Existing account linking
If a user is already signed in, they can attach additional OAuth identities to the same account. This is the usual "Connect Google" or "Connect GitHub" settings-page flow.
Today, explicit signed-in linking is available for OAuth identities. Email/password and phone linking remain anonymous-upgrade flows.
What Changes After Linking
Anonymous upgrade
| Property | Before | After |
|---|---|---|
userId | abc-123 | abc-123 (unchanged) |
isAnonymous | true | false |
| JWT | Anonymous claims | Fresh JWT with permanent-account claims |
| Data | All records preserved | All records preserved |
| Auto-cleanup | Subject to anonymousRetentionDays | No longer auto-cleaned |
Existing signed-in account
| Property | Before | After |
|---|---|---|
userId | abc-123 | abc-123 (unchanged) |
| Linked OAuth providers | ['google'] | ['google', 'github'] |
| JWT | Existing session | Fresh JWT reflecting the latest account state |
| Data | All records preserved | All records preserved |
Anonymous Upgrade
Link Email/Password
Endpoint: POST /api/auth/link/email
const result = await client.auth.linkWithEmail({
email: 'user@example.com',
password: 'securePassword123',
});
// result.user.isAnonymous === false
// result.user.id is unchanged
Link OAuth
Endpoint: POST /api/auth/oauth/link/:provider
await client.auth.linkWithOAuth('google');
// Anonymous account becomes permanent after callback
Link Phone
Endpoints
POST /api/auth/link/phonePOST /api/auth/verify-link-phone
await client.auth.linkWithPhone({ phone: '+1234567890' });
const result = await client.auth.verifyPhoneLink({
phone: '+1234567890',
code: '123456',
});
// result.user.isAnonymous === false
Attach Another OAuth Provider To An Existing Account
Use linkWithOAuth() while already signed in:
await client.auth.linkWithOAuth('github', {
redirectUrl: `${window.location.origin}/settings/connections/callback`,
state: 'settings-connections',
});
EdgeBase redirects to the provider, then back to your app. On success, the provider is attached to the same user account and the callback includes fresh access_token, refresh_token, and your optional state.
If the OAuth identity already belongs to another user, the request fails with 409 Conflict.
Manage Linked Identities
Use these APIs to show connected providers in account settings and let users disconnect them.
const { identities, methods } = await client.auth.listIdentities();
console.log(identities);
// [
// { id: 'oauthacc_x', type: 'oauth', provider: 'google', providerUserId: '123' },
// { id: 'oauthacc_y', type: 'oauth', provider: 'github', providerUserId: '456' },
// ]
console.log(methods);
// { hasPassword, hasMagicLink, hasEmailOtp, hasPhone, passkeyCount, oauthCount, total }
await client.auth.unlinkIdentity(identities[1].id);
REST endpoints
GET /api/auth/identitiesDELETE /api/auth/identities/:identityId
Last Sign-In Method Protection
EdgeBase refuses to unlink the final remaining sign-in method for a user. That protection counts:
- Password
- Email-based passwordless sign-in
- Phone OTP
- Passkeys
- Linked OAuth providers
Auto-Linking vs Explicit Linking
- Auto-linking happens during normal OAuth sign-in when the provider supplies a verified email that already belongs to an existing EdgeBase user.
- Explicit linking happens when a signed-in user calls
linkWithOAuth(...)from an account settings screen.
Explicit linking never merges into a different account. If the OAuth identity is already attached elsewhere, the request fails.
linkWithOAuth() accepts redirectUrl or redirectTo plus an optional state string. The callback appends state back to your app redirect so you can resume the exact UI flow.
Access Rules After Linking
Before upgrading, anonymous users are still subject to rules that check auth.isAnonymous:
access: {
insert(auth) { return auth !== null && !auth.isAnonymous },
read(auth) { return auth !== null },
}
After an anonymous upgrade, auth.isAnonymous becomes false, so the user can pass rules that require a permanent account.
For existing permanent accounts, linking an additional OAuth identity does not change auth.isAnonymous; it only expands the available sign-in methods for the same user.
Error Handling
| Error | Cause |
|---|---|
409 Conflict | The email, phone number, or OAuth identity is already registered to another account |
400 Bad Request | Invalid provider, bad redirect URL, malformed request, or attempt to unlink the last remaining sign-in method |
401 Unauthorized | No valid JWT provided |