Why passwordless authentication matters for modern apps
High-growth SaaS products often lose users at the login screen. A new user signs up, sees a password field, and drops off because they don’t want to manage yet another credential. At scale, even a small drop-off in onboarding translates into thousands of lost accounts.
Security adds more weight to the problem. Password databases are prime targets for breaches, reset flows are noisy and expensive to maintain, and compliance teams keep raising the bar. For developers, passwords create friction for users and risk for companies.
Passwordless authentication solves both issues. Instead of asking users to remember credentials, authentication happens through time-sensitive tokens, magic links, or one-time codes. Scalekit provides these flows out of the box, handling OTPs, email delivery, verification, and session management. Supabase complements this by storing user data securely, enforcing access rules, and powering realtime updates.
This guide walks through how to combine the two platforms into a complete passwordless system. Each section covers a critical part of the stack, from database schema and RLS to triggers, realtime events, and backup strategies.
Establishing the profiles table as the source of truth
Even though Scalekit manages passwordless authentication, the application still needs a persistent user record inside Supabase. Scalekit verifies the user’s identity, but Supabase is where application logic, row-level security, and realtime features operate. Without a proper schema, downstream policies and triggers cannot reliably attach to user sessions.
The profiles table serves as this bridge:
Identity anchor: Each user is assigned a UUID that serves as the stable primary key across the app.
Email mapping: Since Scalekit sessions are scoped to an email, the table must enforce unique email constraints.
Metadata storage: Profile data, preferences, and future extensibility live here, not in Scalekit.
Auditability: Created/updated timestamps are crucial for debugging and compliance.
Realtime hooks: Updates to this table drive subscriptions and broadcasts that keep the frontend in sync.
By letting Scalekit handle authentication and Supabase handle persistence, the architecture combines the strengths of both systems: secure passwordless auth flows with a strongly enforced, queryable data model.
The next section details the schema design for the profiles table, including required fields and recommended constraints.
Scalekit authentication integration with Supabase profiles
Scalekit provides the passwordless login layer, while Supabase serves as the persistent user store. Scalekit issues OTPs or magic links, verifies them, and the backend establishes a secure session. Supabase is used only to store and query user profiles after authentication. This separation ensures clean boundaries: Scalekit for identity, Supabase for data.
How the flow works
Email submission
The React client calls your backend endpoint /api/auth/passwordless/send.
Backend uses scalekit.passwordless.sendPasswordlessEmail() to trigger OTP or magic link delivery.
User verification
The client submits the OTP (verifyPasswordlessEmail) or hits the magic link callback.
Backend validates with Scalekit, returning a verified email.
Session creation
Backend generates a JWT or HTTP-only cookie scoped to the email.
No Supabase Auth tokens are involved.
Profile sync
Backend checks public.profiles in Supabase.
If no record exists, it inserts a new one with the email.
Updates timestamps or metadata as needed.
Realtime broadcast
After sync, the backend emits a profile_sync event via the Supabase realtime channel.
This ensures clients immediately reflect the new login state.
Example: sending a passwordless email (backend)
// passwordless.ts import { Scalekit } from "@scalekit-sdk/node";
const scalekit = new Scalekit( process.env.SCALEKIT_URL!, process.env.SCALEKIT_CLIENT_ID!, process.env.SCALEKIT_CLIENT_SECRET! );
This integration keeps Scalekit responsible for authentication and Supabase focused on secure data access. Developers gain the flexibility of OTP or magic links without locking into Supabase Auth, while still leveraging Supabase’s strong RLS and realtime APIs.
Passwordless authentication flow
During our holiday sale, users want to quickly sign in without remembering passwords, while we need secure verification and seamless session handling across devices. Scalekit’s passwordless authentication handles OTPs or magic links, and Supabase stores persistent profiles securely.
Backend flow
User submits email → Scalekit sends OTP/magic link → Backend verifies → Supabase syncs profile → Client receives session and realtime updates.
1. Sending OTP / Magic link
// server/routes/passwordless.ts
import { Scalekit } from "@scalekit-sdk/node";
import { setSessionCookie } from '../session';
import { supabaseAdmin } from '../supabaseAdminClient';
const scalekit = new Scalekit(
process.env.SCALEKIT_URL!,
process.env.SCALEKIT_CLIENT_ID!,
process.env.SCALEKIT_CLIENT_SECRET!
);
export async function sendLoginEmail(email: string) {
return scalekit.passwordless.sendPasswordlessEmail(email, {
template: "SIGNIN",
expiresIn: 300,
magiclinkAuthUri: `${process.env.APP_BASE_URL}/passwordless/verify`
});
}
Fast, passwordless login during peak traffic events
Centralized session control via server-managed JWT cookies
Seamless profile sync with Realtime updates
Secure: Scalekit handles OTP/magic link verification, preventing password leaks or brute-force attacks
Optional enhancements
Edge functions: Handle verification callbacks, lightweight server computations, notifications
Database triggers: Auto-update last_login, audit logs, or notify external systems
Enforcing row-level security for email-scoped access
Imagine our e-commerce platform from the intro, during a holiday sale, thousands of users are logging in at once. We want to ensure that each user only sees their own profile and data, even under high load. Since authentication is handled by Scalekit, Supabase needs RLS policies to enforce this isolation.
Key design points
Email-based enforcement: Each row in public.profiles is tied to a unique email. Only the user whose email matches the JWT claim can access that row.
Client restrictions: Clients cannot insert or update arbitrary rows; they interact only with their own profile.
Scoped policies: Reads and updates check that the profiles.email matches the email claim in the server-issued session JWT. This ensures that even if a malicious client tries to fetch others’ data, Supabase denies access.
Example RLS policies for public.profiles
-- Enable RLS
alter table public.profiles enable row level security;
-- Read own profile
create policy "Users can view their own profile"
on public.profiles
for select
using (email = current_setting('request.jwt.claim.email'));
-- Update own profile
create policy "Users can update their own profile"
on public.profiles
for update
using (email = current_setting('request.jwt.claim.email'))
with check (email = current_setting('request.jwt.claim.email'));
Service role bypass
The backend uses Supabase’s service_role key when syncing profiles after Scalekit verification. This bypasses RLS so that inserts and updates always succeed, even if no row exists yet. Clients never see this key, they are limited to their own records by RLS.
By linking this back to our e-commerce scenario, RLS ensures that during high-traffic periods:
Users can only access their own profiles.
No cross-user data leaks occur, even if someone tries to manipulate requests.
The system scales securely, letting your backend handle high concurrency without compromising user isolation.
Realtime updates for user profiles
During our holiday sale scenario, imagine a user updating their profile or preferences from one device while browsing on another. To provide a seamless experience, we want changes to propagate instantly without forcing a full page refresh. This is where Supabase Realtime comes in.
How it works
Postgres changes feed: Any insert or update to public.profiles triggers a change event.
Client subscription: Each client subscribes only to the row corresponding to their email, ensuring they receive updates for their profile only.
Broadcast fallback: After a profile sync on the server (post-Scalekit verification), a broadcast event ensures that any missed updates are delivered, even if the client temporarily loses connection.
Profile changes in Supabase are pushed to clients via row-level subscriptions and broadcast channels, ensuring instant updates across devices.
// After Scalekit verification
supabaseAdmin
.from('profiles')
.update({ last_verified: new Date() })
.eq('email', email);
supabaseAdmin
.channel('broadcast')
.send({ type: 'profile_sync', email });
Why this matters
Users instantly see updates made from other devices.
During high concurrency (like our holiday sale), we avoid stale data or race conditions.
Combined with RLS, it guarantees that only the intended user receives their own profile changes.
By linking Realtime updates with Scalekit authentication and RLS, we create a secure, seamless, and user-centric experience, even under heavy load.
Passwordless authentication with Scalekit
Recall our holiday sale scenario: users want to quickly sign in without remembering passwords, while we need secure verification and seamless session handling across devices. Scalekit passwordless authentication solves this perfectly.
Flow overview
User enters email → request is sent to our backend.
Backend calls Scalekit → sends a verification code or magic link.
User verifies → backend confirms with Scalekit, creates a session cookie, and syncs the profile in Supabase.
Realtime update → client receives any profile updates via the Realtime subscription.
Fast, passwordless login for users during high-traffic events.
Centralized session control, our server manages JWT cookies, not Supabase Auth.
Seamless profile sync across devices with Realtime events.
Security and flexibility, Scalekit handles OTP/magic link verification, preventing password leaks or brute force attacks.
By integrating Scalekit into our backend flow, we eliminate the need for Supabase Auth entirely while still leveraging Supabase for secure, real-time data storage and access. Users get a smooth, instant experience, and developers retain full control over sessions and data sync.
Database schema and Row-Level Security (RLS)
Imagine our holiday sale scenario: thousands of users are logging in simultaneously, redeeming offers, and updating their profiles. To handle this smoothly, we need a database design that ensures each user sees only their own data, while supporting real-time updates without overloading the server.
Profiles table
create table public.profiles (
id uuid primary key default gen_random_uuid(),
email text unique not null,
name text,
last_login timestamptz default now(),
created_at timestamptz default now(),
updated_at timestamptz default now()
);
Why this table matters:
Each user has a unique record, keyed by their email, linking Scalekit authentication to Supabase profiles.
Stores essential profile data like name and last_login, useful for tracking activity during high-traffic periods.
Enables the backend to safely upsert verified emails from Scalekit.
Supports Realtime notifications so the frontend reflects profile changes instantly.
Row-Level Security (RLS)
To ensure strict user isolation even under heavy load:
-- Enable RLS
alter table public.profiles enable row level security;
-- Select own profile
create policy select_own_profile
on public.profiles
for select using (email = current_setting('jwt.claims.email'));
-- Update own profile
create policy update_own_profile
on public.profiles
for update using (email = current_setting('jwt.claims.email'));
-- Insert own profile
create policy insert_own_profile
on public.profiles
for insert with check (email = current_setting('jwt.claims.email'));
Key points:
JWTs issued by the backend after Scalekit verification provide the email claim for policy enforcement.
Clients can only access or modify their own profiles.
The server uses Supabase’s service-role key to bypass RLS for safe profile inserts/updates.
Real-Time updates
During our sale, a user might log in from multiple devices or update their preferences. To keep the UI in sync:
Clients subscribe to their own profile rows using email-scoped filters.
Any change in public.profiles triggers a Realtime event.
Broadcast fallbacks ensure updates reach clients even if they temporarily disconnect.
Why this matters in our scenario:
Users instantly see updated profile info and session states.
RLS ensures no cross-user data leaks, even during traffic spikes.
Combines secure isolation with seamless frontend updates, making the holiday sale experience smooth for both users and developers.
Optional enhancements: Database triggers
While our current setup relies on server-side sync for profile updates, you could also add Postgres triggers to automate actions on insert/update events. For example:
Automatically update last_login on user verification.
Send audit logs to a separate table for compliance.
Notify external systems of profile changes.
Triggers can complement Realtime subscriptions and ensure server-side logic remains consistent even if multiple services interact with the database.
Realtime subscriptions & broadcast
During our simulated holiday sale, hundreds or thousands of users may log in at the same time. To ensure the UI reflects the latest profile state without overloading the server, we leverage Supabase Realtime combined with a broadcast fallback.
After Scalekit verification, the server upserts the profile and triggers a broadcast event.
This ensures the client receives updates even if RLS or subscription latency prevents direct realtime changes.
Why it matters
Instant UI feedback: Users immediately see their updated verification status, preferences, or session info.
Scalable: Even with hundreds of concurrent logins, the broadcast channel ensures reliable delivery without heavy polling.
Secure: RLS and email-scoped subscriptions prevent exposure of other users’ data.
Connecting to our story
Imagine the peak of a holiday sale: multiple users are logging in simultaneously, redeeming offers, and updating preferences. Without Realtime updates, some clients might see stale session states, causing frustration or errors. By combining email-scoped subscriptions with server broadcasts, every user sees their latest profile instantly, while the backend safely manages thousands of concurrent events.
Server implementation: Passwordless flow
With Scalekit handling all authentication, the server’s main responsibilities are:
Initiating the passwordless flow
Verifying users via OTP or magic link
Syncing profiles to Supabase
Creating session cookies for secure client access
This ensures that even during our holiday sale spike, every login is fast, reliable, and secure.
1. Sending the verification email
When a user enters their email, the backend calls Scalekit to send either a verification code or a magic link:
Scalable: Server never handles passwords; Scalekit manages auth load.
Reliable: Profile sync and broadcasts guarantee the client sees the latest state instantly.
Secure: Email-scoped sessions + RLS ensure users only access their own data.
Connecting to our story
During the holiday sale, a user logs in with their email. Scalekit sends the verification link in seconds. The server validates it, updates the Supabase profile, and the client instantly sees their verified state, even in a surge of hundreds of logins. No stale UI, no race conditions, no downtime.
Optional: Edge Functions for Enhanced Workflows
Supabase Edge Functions could be used to offload certain backend tasks, such as:
Handling verification callbacks from Scalekit
Performing lightweight server-side computations before profile sync
Triggering notifications or custom webhooks Incorporating Edge Functions can reduce latency and scale specific operations independently from your main server.
Client implementation: Passwordless UI & hooks
On the client side, our goal is to make the login process fast, intuitive, and reactive, even when traffic spikes during a sale.
1. Collecting user email
The user enters their email in a simple React form. Upon submission, the request is sent to our backend:
Ensures the UI always reflects the latest user state, even if multiple devices are logged in.
Prevents stale profiles or race conditions during heavy traffic.
Linking back to the holiday sale story
During the sale, users logging in across devices see instant updates to their profiles and session state. Scalekit handles authentication at scale, and the frontend reacts immediately to profile changes, no lag, no dropped sessions.
Realtime updates & broadcast
Scenario: During our holiday sale, users log in from multiple devices, update preferences, or redeem offers. We need a system that instantly reflects changes without stale UI, even under high traffic.
// After Scalekit verification and profile sync
await supabaseAdmin
.from('broadcast')
.send({ type: 'profile_sync', email });
Why this matters:
Instant UI updates: Users immediately see profile changes across devices.
Concurrency-safe: Even hundreds of simultaneous logins are handled seamlessly.
Secure: RLS and email-scoped subscriptions prevent exposure of other users’ data.
Optional: Backup and disaster recovery
For production apps, consider implementing regular backups of the public.profiles table and other critical data. Supabase provides built-in backups, but additional strategies could include:
Periodic exports to cloud storage
Versioned backups of audit logs
Automated restore tests to ensure reliability
These strategies help maintain data integrity during high-traffic events or unexpected incidents, complementing the secure auth and realtime sync flow described above.
Conclusion
In this guide, we implemented a passwordless authentication flow using Scalekit for secure email OTPs and magic links, while relying on Supabase purely as a database with realtime updates. This approach eliminates the need for traditional auth providers, letting your server manage JWT sessions scoped to user emails. With Realtime subscriptions and a service-role sync, the frontend stays instantly updated, ensuring a seamless experience for both the user and the developer managing the backend.
Looking back at our initial scenario, the developer trying to handle auth, session state, and profile updates efficiently, this setup directly addresses those pain points. Scalekit offloads the complexity of verification, email sending, and token management, while Supabase handles persistent data storage and RLS-based security. The combination ensures a clean separation of concerns and reduces potential bugs related to session handling or realtime updates.
The architecture we’ve built is both secure and scalable. Using JWTs scoped to email keeps session management simple, service-role access allows safe server-side updates bypassing RLS, and realtime subscriptions keep clients in sync without heavy polling. Developers can extend this foundation with custom RLS policies, additional tables, or frontend state management patterns without disrupting the auth flow.
As for next steps, you can explore advanced Scalekit features like multi-factor authentication, suspicious activity monitoring, or custom email templates. On the database side, experimenting with Supabase RLS rules, triggers, and additional realtime channels can further enhance your app. Finally, reviewing the Scalekit and Supabase documentation will give you deeper insights and open avenues to build a fully production-ready passwordless system tailored to your specific app needs.
FAQ
How does Scalekit handle OTP expiration and rate limits?
Scalekit enforces a default 5-attempt limit per 10-minute window for OTP verification to prevent brute-force attacks. Expired codes are rejected automatically, and new requests trigger a fresh auth_request_id. Rate limiting is configurable on the Scalekit dashboard for production-grade security.
Can Scalekit magic links be used across devices?
Yes, but only if same browser origin enforcement is disabled. If enabled, the magic link must be opened in the original browser session to prevent token hijacking, ensuring a secure passwordless authentication flow.
Why use service-role Supabase client for profile sync?
The service-role client bypasses RLS, allowing the server to safely insert/update public.profiles without exposing sensitive operations to the client. This ensures secure server-side sync while preserving fine-grained access control for frontend queries.
How does Postgres Realtime integrate with JWT-based sessions?
Realtime subscriptions listen to public.profiles changes, but JWT-scoped sessions prevent unauthorized access. By combining RLS policies and JWT validation, developers can achieve instant frontend updates without exposing sensitive data or service-role credentials.
Can this passwordless system scale for multi-tenant apps?
Yes, by extending the public.profiles schema with tenant_id and adjusting RLS policies, each tenant’s data can remain isolated. Coupled with Scalekit’s stateless auth and Supabase Realtime, the system supports multi-tenant passwordless workflows with minimal server overhead.