Skip to main content

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

PropertyBeforeAfter
userIdabc-123abc-123 (unchanged)
isAnonymoustruefalse
JWTAnonymous claimsFresh JWT with permanent-account claims
DataAll records preservedAll records preserved
Auto-cleanupSubject to anonymousRetentionDaysNo longer auto-cleaned

Existing signed-in account

PropertyBeforeAfter
userIdabc-123abc-123 (unchanged)
Linked OAuth providers['google']['google', 'github']
JWTExisting sessionFresh JWT reflecting the latest account state
DataAll records preservedAll records preserved

Anonymous Upgrade

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

Endpoint: POST /api/auth/oauth/link/:provider

await client.auth.linkWithOAuth('google');
// Anonymous account becomes permanent after callback

Endpoints

  • POST /api/auth/link/phone
  • POST /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/identities
  • DELETE /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.

Redirects And State

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

ErrorCause
409 ConflictThe email, phone number, or OAuth identity is already registered to another account
400 Bad RequestInvalid provider, bad redirect URL, malformed request, or attempt to unlink the last remaining sign-in method
401 UnauthorizedNo valid JWT provided