SuperTokens

repository·master·Indexed 12 days ago

https://github.com/supertokens/supertokens-core

An open-source authentication and session management provider designed as an alternative to proprietary services. It offers an end-to-end solution for secure login, including passwordless, social, and email/password methods, and is designed for on-premises deployment to give developers full control over user data. The architecture consists of a Frontend SDK, a Backend SDK, and SuperTokens Core, an HTTP service containing the core authentication logic.

Tokens
12.3K
Snippets
34
Records
54
Agent score
96%

What's inside SuperTokens

  1. What is SuperTokens?

    master

    SuperTokens is an open-source authentication provider designed as an alternative to proprietary services like Auth0 or AWS Cognito. It provides secure login and session management with an emphasis on user data control, extensibility, and minimal vendor lock-in.

    Key characteristics:

    • Open Source: Free to use with no limits on user counts.
    • On-premises Deployment: You control 100% of your user data by using your own database.
    • End-to-end Solution: Handles sign-ups, login, and session management without requiring manual OAuth protocol implementation.
    • Decoupled Features: You can use SuperTokens for just login, just session management, or both. It also supports session management integrations with other providers like Auth0.
  2. SuperTokens features overview

    master

    SuperTokens supports a wide range of authentication and authorization patterns, including:

    • Login Methods: Passwordless, Social Login, Email/Password, and Phone/Password.
    • Session Management: Robust handling of user sessions.
    • Security: Multi-Factor Authentication (MFA).
    • Enterprise/Scale: Multi-Tenancy / Organization Support (Enterprise SSO) and User Roles.
    • Architecture Patterns: Microservice Authentication.
  3. How SuperTokens architecture works

    master

    The SuperTokens architecture is composed of three primary building blocks that work together to provide authentication services:

    1. Frontend SDK: Responsible for managing session tokens on the client side and rendering login UI widgets.
    2. Backend SDK: Provides the API surface for your application's backend (e.g., Node.js, Go, Python). It handles sign-up, sign-in, sign-out, and session refreshing. Your Frontend SDK communicates with these APIs.
    3. SuperTokens Core: An HTTP service that contains the core authentication logic and performs database operations. This service is consumed by the Backend SDK.

    This separation allows for high performance; for example, common operations like session verification can happen within the Backend SDK without needing to contact the Java Core service every time.

  4. Understand SuperTokens User Table Migration Modes

    master

    SuperTokens is migrating from an old table structure (all_auth_recipe_users, *_user_to_tenant) to a new reservation table structure (recipe_user_tenants, recipe_user_account_infos, primary_user_tenants). You can control this transition using the migration_mode configuration.

    ModeOld Table WritesNew Table WritesOld Table ReadsNew Table Reads
    LEGACY (default)YesNoYesNo
    DUAL_WRITE_READ_OLDYesYesYesNo
    DUAL_WRITE_READ_NEWYesYesNoYes
    MIGRATEDNoYesNoYes
  5. Perform an offline schema migration via SQL

    master

    If you can tolerate a maintenance window, you can perform a 'cold migration' using an offline SQL script. This is recommended for single-region/single-instance setups or self-hosted dev/staging environments. This process involves stopping traffic, deploying new binaries in LEGACY mode to initialize the schema, running a backfill script, and then manually setting the migration mode to MIGRATED.

    Pre-flight

    1. Confirm versions.
    2. Schedule a maintenance window.
    3. Backup your database.

    Migration Steps

    1. Stop traffic

    Drain or block all SuperTokens API traffic. No writes can be in flight during the migration.

    2. Deploy new binaries in LEGACY mode

    Boot one core+plugin instance against the database. This creates the new tables and columns via GeneralQueries.createTablesIfNotExists.

    Verify the schema exists using:

    \dt recipe_user_account_infos
    \dt recipe_user_tenants
    \dt primary_user_tenants
    \d+ app_id_to_user_id      -- expect time_joined, primary_or_recipe_user_time_joined columns

    Shut the instance down before the backfill.

    3. Run the offline backfill

    Use the migration-backfill.sql script from the supertokens-postgresql-plugin repository.

    psql "<connection-uri>" -v app_id="'my-app'" -f migration-scripts/migration-backfill.sql

    Note: Use -v app_id="''" to scope to all apps, or -v app_id="'my-app'" to scope to a single app.

    4. Verify data integrity

    Run the verification queries included at the bottom of migration-backfill.sql. They should all return 0. For absolute confidence, run a canonical dump comparison:

    psql "<connection-uri>" -f migration-scripts/dump_old_canonical.sql > old.csv
    psql "<connection-uri>" -f migration-scripts/dump_new_canonical.sql > new.csv
    diff old.csv new.csv

    5. Set tenant to MIGRATED mode

    You can do this in one of two ways:

    Option A: Direct DB Edit Update the tenant_configs row directly. Adjust the syntax based on whether your column is text or jsonb:

    UPDATE tenant_configs
    SET core_config = jsonb_set(
        core_config::jsonb,
        '{migration_mode}',
        '"MIGRATED"'::jsonb
    )::text
    WHERE connection_uri_domain = '' AND app_id = 'public' AND tenant_id = 'public';

    Option B: API Flip

    1. Boot the new core in LEGACY mode.
    2. Issue a PUT /recipe/multitenancy/connectionuridomain/v2 with migration_mode: "MIGRATED".
    3. Shut down the instance.

    6. Bring traffic back up

    Start all instances. Verify with synthetic users (create, link, update email) and check that GET /migration/mode returns "mode": "MIGRATED" for every CUD.

  6. Choose a migration strategy (Online vs Offline)

    master

    Select your migration path based on your production requirements and dataset size:

    ScenarioRecommended path
    Production with HA, can't tolerate downtimeOnline, one CUD at a time, with the cron driving backfill
    Single-region single-instance, comfortable with a maintenance windowOffline, one psql invocation
    Very large dataset (>50M users)Online, but kick off the offline SQL during Step 3 to skip the 5-minute cron tick latency
    Self-hosted dev/stagingOffline; it's the simplest
    Want to validate parity before flipping prodOnline to DUAL_WRITE_READ_NEW, soak, then use the canonical dump diff to spot-check before MIGRATED
  7. Online Runbook: Migrating from Legacy to Migrated Schema

    master

    This guide outlines the zero-downtime production path for migrating SuperTokens from a legacy schema to the new schema (introduced in v12.0.0). The process uses a multi-step transition through different migration_mode states to ensure data consistency and allow for reversible rollbacks until the final cutover.

    Migration Modes

    • LEGACY: Old and new instances coexist. New tables exist but remain empty. Reads and writes only use old tables.
    • DUAL_WRITE_READ_OLD: New writes are performed to both old and new tables atomically. Reads still come from old tables.
    • DUAL_WRITE_READ_NEW: New writes are performed to both old and new tables. Reads now come from the new reservation tables. This is the first high-risk step.
    • MIGRATED: Writes to old tables stop. The old tables become stale. This step is one-way via the standard API.

    Pre-flight Requirements

    • Version Check: Ensure plugin-interface >= 8.6.0, postgresql-plugin >= 9.5.0, and core >= 12.0.0 are bundled.
    • Backup: Take a logical backup of the live database (e.g., pg_dump --schema-only and a full data dump).
    • Capacity: Verify the cluster has CPU headroom for dual-writes, as operations like updateEmail will perform double the work during DUAL_WRITE phases.
  8. Step 3: Validate Data Consistency after Backfill

    master

    Before switching reads to the new tables, run these SQL queries to ensure the backfill was successful and data is consistent across the old and new structures.

    -- 1. No users missing time_joined
    SELECT COUNT(*) FROM app_id_to_user_id
    WHERE time_joined = 0 AND app_id = 'public';
    -- Expected: 0
    
    -- 2. All users have account info entries
    SELECT COUNT(*) FROM app_id_to_user_id a
    WHERE a.app_id = 'public' AND NOT EXISTS (
        SELECT 1 FROM recipe_user_account_infos rai
        WHERE rai.app_id = a.app_id AND rai.recipe_user_id = a.user_id
    );
    -- Expected: 0
    
    -- 3. Tenant coverage matches
    SELECT
        (SELECT COUNT(*) FROM all_auth_recipe_users WHERE app_id = 'public') AS old_count,
        (SELECT COUNT(*) FROM recipe_user_tenants WHERE app_id = 'public') AS new_count;
    -- new_count should be >= old_count
    
    -- 4. All linked users have primary reservations
    SELECT DISTINCT a.primary_or_recipe_user_id
    FROM app_id_to_user_id a
    WHERE a.is_linked_or_is_a_primary_user = TRUE AND a.app_id = 'public'
    AND NOT EXISTS (
        SELECT 1 FROM primary_user_tenants pt
        WHERE pt.app_id = a.app_id AND pt.primary_user_id = a.primary_or_recipe_user_id
    );
    -- Expected: empty
  9. Step 3: Backfill Existing Users

    master

    Once in DUAL_WRITE_READ_OLD, the system automatically starts a cron job (5-minute tick, batch size 1000) to backfill existing users into the new tables.

    Monitoring Progress

    Use the following endpoint to check the status of the backfill: GET /migration/backfill/progress

    Wait until pendingUsers == 0 for every CUD before proceeding to the next step.

    Verification

    • Completeness Scan: GET /migration/backfill/progress?verify=true returns inconsistentUsersCount. This should be 0.
    • Parity Dumps: For large datasets, use the dump_old_canonical.sql and dump_new_canonical.sql scripts from the supertokens-postgresql-plugin repository to diff the two views.

    Troubleshooting

    If the backfill fails with Unknown recipeId during backfill: '...', it means a recipe has introduced rows that the backfill logic does not recognize. This requires a patch release to handle the new recipeId.

    # Check progress
    GET /migration/backfill/progress
    
    # Verify completeness
    GET /migration/backfill/progress?verify=true
  10. Step 2: Transition to DUAL_WRITE_READ_OLD

    master

    To begin dual-writing data to both old and new tables while keeping reads on the old schema, update the coreConfig for your Connection URI Domain (CUD).

    Note: This mode allows for easy rollback by setting the mode back to LEGACY via the same endpoint. Rows written to new tables during this phase are harmless if ignored.

    Verification:

    • Create a test user: verify the row exists in both all_auth_recipe_users and recipe_user_tenants.
    • Link users: verify primary_user_tenants has the reservation and old tables remain consistent.
    PUT /recipe/multitenancy/connectionuridomain/v2
    {
      "connectionUriDomain": "<cud>",
      "coreConfig": { "migration_mode": "DUAL_WRITE_READ_OLD" }
    }
  11. Step 4: Transition to DUAL_WRITE_READ_NEW

    master

    This is the first risk-bearing step. Reads will now be served from the new reservation tables. You must ensure pendingUsers == 0 from the backfill step before proceeding.

    Deployment Order:

    1. Confirm all instances of the CUD report DUAL_WRITE_READ_OLD via GET /migration/mode.
    2. Issue the configuration update.
    3. Watch GET /migration/mode until every instance flips to DUAL_WRITE_READ_NEW.

    Rollback: If issues arise, drop the mode back to DUAL_WRITE_READ_OLD. Since old tables were kept in sync, reading from them remains safe. This rollback window stays open until Step 6.

    PUT /recipe/multitenancy/connectionuridomain/v2
    {
      "connectionUriDomain": "<cud>",
      "coreConfig": { "migration_mode": "DUAL_WRITE_READ_NEW" }
    }
  12. Step 6: Final Cutover to MIGRATED

    master

    The final step moves the CUD to the MIGRATED state. At this point, writes to the old tables stop, and the old tables become stale.

    Validation: The API validator runs requireBackfillComplete and will refuse the request if any user has time_joined = 0.

    Warning: One-Way Operation This step is one-way through the standard CRUD API. To genuinely roll back after this step, you must:

    1. Manually drop the tenant config row in PostgreSQL.
    2. Replay every write performed since the flip into the old tables out-of-band.

    It is highly recommended to perform a 'Soak' period (Step 5) of at least 1-2 weeks in DUAL_WRITE_READ_NEW before executing this step.

    PUT /recipe/multitenancy/connectionuridomain/v2
    {
      "connectionUriDomain": "<cud",
      "coreConfig": { "migration_mode": "MIGRATED" }
    }