Skip to main content

Overview

SuperTokens Core provides comprehensive user management with support for multiple authentication methods per user, account linking, and user ID mapping for external identity systems.

User Types

Recipe User vs Primary User

SuperTokens distinguishes between two types of users:

Recipe User

A user authenticated through a single method (email/password, social login, etc.)

Primary User

A user with multiple authentication methods linked together
public class AuthRecipeUserInfo {
    public String id;                    // Primary user ID
    public boolean isPrimaryUser;
    public LoginMethod[] loginMethods;   // All linked auth methods
    public String[] tenantIds;
    public long timeJoined;
}

public class LoginMethod {
    public String recipeUserId;          // Unique per login method
    public RECIPE_ID recipeId;           // emailpassword, thirdparty, passwordless
    public String email;
    public String phoneNumber;
    public ThirdParty thirdParty;
    public String[] tenantIds;
    public boolean verified;
}

Creating Users

Email/Password Users

SignUpResponse response = EmailPassword.signUp(
    tenantIdentifier,
    storage,
    main,
    email,
    password
);

if (response.user != null) {
    // User created successfully
    AuthRecipeUserInfo user = response.user;
}

Third-Party (Social) Users

SignInUpResponse response = ThirdParty.signInUp(
    tenantIdentifier,
    storage,
    main,
    thirdPartyId,        // "google", "github", etc.
    thirdPartyUserId,    // User ID from provider
    email,
    isEmailVerified
);

Passwordless Users

CreateCodeResponse codeResponse = Passwordless.createCode(
    tenantIdentifier,
    storage,
    main,
    email,              // or phoneNumber
    null,               // phoneNumber (if email is null)
    deviceId,
    userInputCode
);

ConsumeCodeResponse response = Passwordless.consumeCode(
    tenantIdentifier,
    storage,
    main,
    deviceId,
    deviceIdHash,
    userInputCode,
    linkCode
);

Account Linking

Making a User Primary

Before linking accounts, you must designate one as primary:
CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(
    main,
    appIdentifier,
    storage,
    recipeUserId
);

if (!result.wasAlreadyAPrimaryUser) {
    // User is now a primary user
    AuthRecipeUserInfo primaryUser = result.user;
}

Linking Accounts

Link a recipe user to a primary user:
LinkAccountsResult result = AuthRecipe.linkAccounts(
    main,
    appIdentifier,
    storage,
    recipeUserId,        // User to link
    primaryUserId        // Primary user to link to
);

if (result.wasLinked) {
    // Account successfully linked
    AuthRecipeUserInfo linkedUser = result.user;
}

Account Linking Checks

From AuthRecipe.java, SuperTokens validates:
1

Primary User Validation

Ensure the primary user ID corresponds to an actual primary user
2

Recipe User Validation

Verify recipe user isn’t already linked to another primary user
3

Conflict Detection

Check for account info conflicts (email, phone, third-party ID)
4

Tenant Consistency

Verify both users share at least one tenant

Unlinking Accounts

boolean wasUnlinked = AuthRecipe.unlinkAccounts(
    main,
    appIdentifier,
    storage,
    recipeUserId
);

if (wasUnlinked) {
    // Recipe user is now independent
}
If you unlink the recipe user that is also the primary user ID, and there are other linked accounts, the primary user will be deleted and all sessions revoked.

User Retrieval

Get User by ID

AuthRecipeUserInfo user = AuthRecipe.getUserById(
    appIdentifier,
    storage,
    userId  // Can be primary or recipe user ID
);

if (user != null) {
    System.out.println("User email: " + user.loginMethods[0].email);
    System.out.println("Is primary: " + user.isPrimaryUser);
    System.out.println("Login methods: " + user.loginMethods.length);
}

Get Multiple Users

List<AuthRecipeUserInfo> users = AuthRecipe.getUsersById(
    appIdentifier,
    storage,
    Arrays.asList(userId1, userId2, userId3)
);

Get User by Email

AuthRecipeUserInfo[] users = EmailPassword.getUsersByEmail(
    tenantIdentifier,
    storage,
    email
);

Get User by Phone Number

AuthRecipeUserInfo user = Passwordless.getUserByPhoneNumber(
    tenantIdentifier,
    storage,
    phoneNumber
);

Get User by Third-Party Info

AuthRecipeUserInfo user = ThirdParty.getUser(
    tenantIdentifier,
    storage,
    thirdPartyId,
    thirdPartyUserId
);

Pagination

List Users with Pagination

UsersResponse response = AuthRecipe.getUsers(
    tenantIdentifier,
    storage,
    limit,              // Max 500
    timeJoinedOrder,    // "ASC" or "DESC"
    paginationToken,    // null for first page
    includeRecipeIds,   // Filter by recipe
    includeEmails       // Filter by emails
);

for (AuthRecipeUserInfo user : response.users) {
    // Process user
}

String nextToken = response.nextPaginationToken;
The maximum pagination limit is 500 users per request.

Updating User Information

Update Email

// Email/Password
EmailPassword.updateUsersEmailOrPassword(
    appIdentifier,
    storage,
    recipeUserId,
    newEmail,
    null  // password
);

// Passwordless
Passwordless.updateUser(
    appIdentifier,
    storage,
    recipeUserId,
    newEmail,
    null  // phoneNumber
);

Update Password

EmailPassword.updateUsersEmailOrPassword(
    appIdentifier,
    storage,
    recipeUserId,
    null,        // email
    newPassword
);

Update Phone Number

Passwordless.updateUser(
    appIdentifier,
    storage,
    recipeUserId,
    null,             // email
    newPhoneNumber
);

User Deletion

Delete User

Deletes user and all associated data:
void deleteUser(
    AppIdentifier appIdentifier,
    Storage storage,
    String userId  // Primary or recipe user ID
) throws StorageQueryException {
    AuthRecipeUserInfo user = AuthRecipe.getUserById(
        appIdentifier, storage, userId
    );
    
    if (user != null) {
        // Delete all login methods
        for (LoginMethod lm : user.loginMethods) {
            // Delete recipe-specific data
            deleteRecipeUser(appIdentifier, storage, lm.recipeUserId);
        }
        
        // Delete non-auth data (sessions, metadata, roles)
        deleteNonAuthRecipeData(appIdentifier, storage, userId);
    }
}
User deletion is permanent and cascades to:
  • All sessions
  • User metadata
  • User roles
  • Email verification tokens
  • Password reset tokens
  • TOTP devices

User ID Mapping

External User IDs

Map SuperTokens user IDs to your existing system:
UserIdMapping.createUserIdMapping(
    appIdentifier,
    storage,
    superTokensUserId,
    externalUserId,
    externalUserIdInfo  // Optional metadata
);

// Retrieve mapping
io.supertokens.pluginInterface.useridmapping.UserIdMapping mapping =
    UserIdMapping.getUserIdMapping(
        appIdentifier,
        storage,
        userId,
        UserIdType.ANY
    );

if (mapping != null) {
    String externalId = mapping.externalUserId;
    String superTokensId = mapping.superTokensUserId;
}

Using External IDs

Once mapped, you can use external IDs in most operations:
// Get user by external ID
AuthRecipeUserInfo user = AuthRecipe.getUserById(
    appIdentifier,
    storage,
    externalUserId  // Automatically resolved
);

// Create session with external ID
SessionInformationHolder session = Session.createNewSession(
    tenantIdentifier,
    storage,
    main,
    externalUserId,  // Resolved to SuperTokens ID internally
    userDataInJWT,
    userDataInDatabase
);

Email Verification

Generate Verification Token

String token = EmailVerification.generateEmailVerificationToken(
    tenantIdentifier,
    storage,
    recipeUserId,
    email
);

// Send token to user via email

Verify Email

EmailVerification.VerifyEmailResponse response = 
    EmailVerification.verifyEmail(
        tenantIdentifier,
        storage,
        token
    );

if (response.isVerified) {
    // Email verified successfully
    String userId = response.userId;
    String email = response.email;
}

Check Verification Status

boolean isVerified = EmailVerification.isEmailVerified(
    appIdentifier,
    storage,
    recipeUserId,
    email
);

Unverify Email

EmailVerification.unverifyEmail(
    appIdentifier,
    storage,
    recipeUserId,
    email
);

Multi-Tenant User Management

Associate User with Tenant

boolean wasAdded = Multitenancy.addUserIdToTenant(
    main,
    tenantIdentifier,
    storage,
    userId
);

Disassociate User from Tenant

boolean wasRemoved = Multitenancy.removeUserIdFromTenant(
    main,
    tenantIdentifier,
    storage,
    userId,
    externalUserId
);

List User’s Tenants

AuthRecipeUserInfo user = AuthRecipe.getUserById(
    appIdentifier,
    storage,
    userId
);

String[] tenantIds = user.tenantIds;
for (String tenantId : tenantIds) {
    System.out.println("User in tenant: " + tenantId);
}

Search by Email

AuthRecipeUserInfo[] users = EmailPassword.getUsersByEmail(
    tenantIdentifier,
    storage,
    "[email protected]"
);

Search by Dashboard Tags

List<String> userIds = storage.searchUsersWithDashboardTags(
    appIdentifier,
    new String[]{"premium", "admin"},
    limit,
    paginationToken
);

Active Users Tracking

Track when users were last active:
// Update last active timestamp
ActiveUsers.updateLastActive(
    appIdentifier,
    main,
    userId
);

// Count active users in time period
int activeCount = ActiveUsers.countUsersActiveSince(
    main,
    appIdentifier,
    System.currentTimeMillis() - (30 * 24 * 60 * 60 * 1000)  // Last 30 days
);
From ActiveUsers.java:15-52:
public static void updateLastActive(
    AppIdentifier appIdentifier,
    Main main,
    String userId
) {
    Storage storage = StorageLayer.getStorage(
        appIdentifier.getAsPublicTenantIdentifier(), main
    );
    StorageUtils.getActiveUsersStorage(storage)
        .updateLastActive(appIdentifier, userId);
}

public static int countUsersActiveSince(
    Main main,
    AppIdentifier appIdentifier,
    long time
) {
    Storage storage = StorageLayer.getStorage(
        appIdentifier.getAsPublicTenantIdentifier(), main
    );
    return StorageUtils.getActiveUsersStorage(storage)
        .countUsersActiveSince(appIdentifier, time);
}
Active user tracking is updated automatically during session operations.

User Count

long totalUsers = AuthRecipe.getUsersCount(
    tenantIdentifier,
    storage,
    includeRecipeIds  // Filter by specific recipes
);

Best Practices

Use Primary Users

Link accounts to provide seamless multi-method authentication

Handle Edge Cases

Always check for null returns and handle exceptions

Verify Emails

Implement email verification for security

Map External IDs

Use ID mapping to integrate with existing systems

Common Patterns

Creating and Linking Users

// Create email/password user
SignUpResponse emailUser = EmailPassword.signUp(
    tenantIdentifier, storage, main, email, password
);

// Make it primary
AuthRecipe.createPrimaryUser(
    main, appIdentifier, storage, emailUser.user.id
);

// Sign in with Google
SignInUpResponse googleUser = ThirdParty.signInUp(
    tenantIdentifier, storage, main,
    "google", googleUserId, email, true
);

// Link Google account
AuthRecipe.linkAccounts(
    main, appIdentifier, storage,
    googleUser.user.loginMethods[0].recipeUserId,
    emailUser.user.id
);

Migrating Users with Mapping

// Create user in SuperTokens
SignUpResponse response = EmailPassword.signUp(
    tenantIdentifier, storage, main, email, password
);

// Map to your existing user ID
UserIdMapping.createUserIdMapping(
    appIdentifier,
    storage,
    response.user.id,
    "your-system-user-123",
    null
);

// Now use either ID
AuthRecipeUserInfo user = AuthRecipe.getUserById(
    appIdentifier,
    storage,
    "your-system-user-123"  // Works!
);

Build docs developers (and LLMs) love