Supabase returns 'identity_not_found: Identity does not exist' when attempting to link, unlink, or reference an identity that has been deleted or doesn't exist in the auth.identities table. This typically occurs during OAuth linking operations, user merging, or when identities are manually removed from the database. The fix involves verifying the identity exists, recreating it if needed, and ensuring proper linking procedures.
The Supabase Auth system maintains a separate 'auth.identities' table that stores authentication identities (OAuth providers, email/password, phone, etc.) linked to user accounts. When you attempt to perform operations like linking a new OAuth provider to an existing user, unlinking an identity, or referencing an identity for authentication, Supabase checks this table first. The error "identity_not_found: Identity does not exist" means: - The identity you're trying to reference (by provider, ID, or user association) doesn't exist in the 'auth.identities' table - The identity may have been previously unlinked or deleted - You might be trying to link an identity that's already associated with another user - The identity record could be corrupted or missing due to manual database operations This is different from "user_not_found" because the user exists, but the specific identity (authentication method) you're trying to use doesn't.
Use the service-role key to inspect the auth.identities table and confirm the identity record:
import { createClient } from '@supabase/supabase-js'
const supabaseAdmin = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
)
// Check for a specific identity by provider and provider ID
const { data: identities, error } = await supabaseAdmin
.from('auth.identities')
.select('*')
.eq('provider', 'github')
.eq('provider_id', '123456789') // The provider's user ID
.single()
if (error) {
console.log('Identity not found or query error:', error.message)
} else {
console.log('Identity exists:', identities)
}You can also query directly via SQL:
SELECT id, user_id, provider, provider_id, identity_data, created_at, updated_at
FROM auth.identities
WHERE provider = 'github' AND provider_id = '123456789';This confirms whether the identity row exists and which user it's associated with.
If the identity is genuinely missing but should exist, you may need to recreate it. For OAuth identities, this typically requires the user to re-authenticate:
// For OAuth providers, trigger a new OAuth flow
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: 'https://yourapp.com/auth/callback'
}
})
if (error) {
console.error('OAuth error:', error.message)
} else {
// User will be redirected to GitHub for authentication
// Upon return, the identity will be recreated
}For email/password identities that are missing, you might need to:
// Use the Management API to create or update the user
const { data, error } = await supabaseAdmin.auth.admin.updateUserById(
userId,
{
email: '[email protected]',
password: 'new-password-if-needed'
}
)Note: Recreating identities should be done carefully to avoid creating duplicate accounts.
Verify that the identity isn't already associated with a different user (which would cause identity_already_exists in some cases):
// Check all identities for a specific provider ID
const { data: allIdentities, error } = await supabaseAdmin
.from('auth.identities')
.select('user_id, provider, provider_id')
.eq('provider_id', '123456789') // The provider's user ID
if (allIdentities && allIdentities.length > 0) {
console.log('This identity is associated with user(s):', allIdentities)
// If it's associated with a different user than expected
const expectedUserId = 'expected-user-uuid'
const actualUserIds = allIdentities.map(id => id.user_id)
if (!actualUserIds.includes(expectedUserId)) {
console.log('Identity belongs to different user(s): ' + actualUserIds.join(', '))
console.log('Expected it to belong to: ' + expectedUserId)
}
}If the identity belongs to a different user, you may need to:
1. Unlink it from the wrong user (if you have permission)
2. Link it to the correct user
3. Or ask the user to use a different OAuth account
When this error occurs in your application, provide clear guidance to users:
import { isAuthApiError } from '@supabase/supabase-js'
try {
const { data, error } = await supabase.auth.linkIdentity({
provider: 'github'
})
if (error) {
if (isAuthApiError(error) && error.code === 'identity_not_found') {
// User-friendly message
setErrorMessage(
'Unable to link your GitHub account. The account connection appears to be missing. ' +
'Please try signing in with GitHub again to re-establish the connection.'
)
// Log for debugging
console.error('Identity not found details:', {
code: error.code,
status: error.status,
message: error.message
})
}
}
} catch (error) {
console.error('Unexpected error:', error)
}For unlinking operations:
if (error?.code === 'identity_not_found') {
// The identity is already gone, so operation is effectively successful
console.log('Identity already removed, cleaning up UI state')
removeProviderFromUI('github')
}Implement monitoring and safeguards to prevent identity records from going missing:
// Regular audit of user-identity associations
async function auditUserIdentities(userId) {
const { data: user } = await supabaseAdmin.auth.admin.getUserById(userId)
const { data: identities } = await supabaseAdmin
.from('auth.identities')
.select('*')
.eq('user_id', userId)
console.log('User ' + userId + ' has ' + (identities?.length || 0) + ' identities')
// Check for common issues
if (user && identities && identities.length === 0) {
console.warn('User ' + userId + ' has no identities - this may cause auth issues')
// Implement recovery logic or alert
}
}
// Add database constraints or triggers if you have direct access
// (Note: Requires database access beyond standard Supabase API)
// Implement backup of critical auth data
async function backupAuthData() {
// Export auth.users and auth.identities regularly
// Store backups securely for disaster recovery
}Consider implementing:
1. Regular audits of identity-user associations
2. Alerts for users with zero identities (can't authenticate)
3. Backup strategies for auth data
4. Documentation of manual identity recovery procedures
### How Supabase manages identities
Supabase stores identities separately from users in the 'auth.identities' table. Each identity row contains:
- 'id': Internal UUID for the identity record
- 'user_id': Reference to the 'auth.users' row
- 'provider': Authentication provider (email, phone, github, google, etc.)
- 'provider_id': The provider's unique identifier for this user
- 'identity_data': JSON blob with provider-specific data (profile info, tokens, etc.)
### Identity lifecycle
1. Creation: When a user signs up or links a new provider
2. Linking: Adding additional identities to an existing user
3. Unlinking: Removing an identity from a user (user must have at least one identity left)
4. Deletion: When a user is deleted, all their identities are cascade-deleted
### Common pitfalls
- Manual SQL operations: Direct database modifications can break the relationship between users and identities
- OAuth provider changes: If a user changes their OAuth account (e.g., GitHub username), the provider_id changes, creating what appears to be a "missing" identity
- Import/export issues: Migrating users between projects without their identities
- Race conditions: Concurrent linking/unlinking operations can leave inconsistent state
### Recovery strategies
For critical production issues:
1. Use database backups to restore missing identities
2. Implement phased rollout of identity-related features
3. Maintain audit logs of all identity operations
4. Consider implementing a reconciliation job that periodically fixes orphaned or missing identities
email_conflict_identity_not_deletable: Cannot delete identity because of email conflict
How to fix "Cannot delete identity because of email conflict" in Supabase
mfa_challenge_expired: MFA challenge has expired
How to fix "mfa_challenge_expired: MFA challenge has expired" in Supabase
conflict: Database conflict, usually related to concurrent requests
How to fix "database conflict usually related to concurrent requests" in Supabase
phone_exists: Phone number already exists
How to fix "phone_exists" in Supabase
StorageApiError: resource_already_exists
StorageApiError: Resource already exists