7.3 KiB
Stytch Configuration Guide
This document explains how to configure Stytch B2B to prevent unknown users from receiving magic link emails and creating accounts.
Overview
We've implemented a custom solution to address two critical security requirements:
- Prevent emails being sent to non-existent users
- Block unknown email addresses from creating accounts
How It Works
Custom Backend Validation
Instead of using Stytch's UI component directly (which always sends emails), we've created a custom flow:
- Frontend: Custom email form in
app/auth/page.tsx - Backend API:
/api/auth/magic-linkvalidates membership before sending - Stytch API: Only called if user is an existing member
Security Features
✅ Email validation: Backend checks if email exists in any organization before sending magic link ✅ No user enumeration: Returns same message for existing and non-existing users ✅ JIT provisioning blocked: Organization settings prevent auto-creation of new members ✅ Discovery flow restricted: Users can only join organizations they're invited to
Required Stytch Dashboard Configuration
Step 1: Disable Self-Service Organization Creation
- Log into your Stytch Dashboard
- Navigate to Frontend SDK settings
- Find "Create Organizations" toggle under Enabled methods
- Disable this toggle
Result: Users cannot create new organizations via the discovery flow
Step 2: Configure Organization Settings (Per Organization)
For each organization in your Stytch project:
- Navigate to Organizations in the dashboard
- Select your organization
- Go to Settings → Authentication
- Configure the following:
{
"email_jit_provisioning": "NOT_ALLOWED",
"email_invites": "RESTRICTED",
"email_allowed_domains": ["your-company.com"] // Optional: restrict by domain
}
What each setting does:
email_jit_provisioning: "NOT_ALLOWED"- Prevents new members from being auto-created via magic linkemail_invites: "RESTRICTED"- Requires explicit invitation to joinemail_allowed_domains- (Optional) Only allows specific email domains
Step 2a: (Optional) Configure Allowed Organization IDs
The backend validates emails by searching members across a specific list of organizations. To avoid an extra API call during login, you can provide a comma-separated allowlist:
STYTCH_ALLOWED_ORGANIZATION_IDS=org-test-123,org-test-456
If this variable is not set, we automatically fetch all organizations in the workspace and cache the IDs in memory.
Step 3: Verify API Permissions
Ensure your Stytch API credentials have permission to:
- Search members (
organizations.members.search) - Send magic links (
magicLinks.email.discovery.send)
Testing the Implementation
Test Case 1: Unknown Email
- Enter an email that doesn't exist in any organization
- Click "Send magic link"
- Expected: Message says "If an account exists with that email, a magic link has been sent."
- Verify: No email is actually sent
- Check backend logs: Should see "No members found" for the email
Test Case 2: Existing Member
- Enter an email of an existing organization member
- Click "Send magic link"
- Expected: Same message as above
- Verify: Email IS sent with magic link
- Check inbox: Magic link email received
Test Case 3: Magic Link Authentication
- Click the magic link from Test Case 2
- Expected: User is authenticated and redirected to dashboard
- Verify: Session is created with correct organization
Test Case 4: Unknown User Clicks Link (if they somehow got one)
- If someone gets a magic link URL (e.g., from a legitimate user)
- Expected: Authentication fails with error
- Verify: No session is created, user cannot access dashboard
API Endpoint Documentation
POST /api/auth/magic-link
Validates email and sends magic link to existing members only.
Request:
{
"email": "user@company.com"
}
Response (Success):
{
"success": true,
"message": "If an account exists with that email, a magic link has been sent."
}
Response (Error):
{
"error": "Unable to process request. Please try again later."
}
Note: Response is the same whether user exists or not (prevents enumeration)
How to Add New Members
Since self-service signup is disabled, use one of these methods:
Method 1: Invite via Stytch Dashboard
- Go to Organizations → Select org → Members
- Click Invite Member
- Enter email and assign roles
- User receives invitation email
Method 2: Programmatic Invite
import { getStytchB2BClient } from "@/lib/auth/stytch/server";
const client = getStytchB2BClient();
await client.magicLinks.email.invite.send({
organization_id: "org-test-...",
email_address: "newuser@company.com",
invited_by_member_id: "member-test-...",
});
Method 3: Create Member via API
await client.organizations.members.create({
organization_id: "org-test-...",
email_address: "newuser@company.com",
name: "New User",
roles: ["member"],
});
Troubleshooting
Issue: Existing users not receiving emails
Check:
- Email is verified in Stytch
- Member status is "active" (not "pending" or "invited")
- Backend logs for member search results
- Stytch API credentials are correct
Issue: Unknown users still getting emails
Check:
- Using
/api/auth/magic-linkendpoint (not direct Stytch SDK call) - Backend search is working correctly
- No caching issues in API route
Issue: Users can't create organizations
This is expected! Self-service organization creation is disabled.
Solution: Create organizations manually via:
- Stytch Dashboard
- Stytch API programmatically
Environment Variables
Required in .env.local:
# Stytch B2B Authentication
STYTCH_PROJECT_ID=project-test-...
STYTCH_SECRET=secret-test-...
NEXT_PUBLIC_STYTCH_PUBLIC_TOKEN=public-token-test-...
# Session configuration
NEXT_PUBLIC_STYTCH_SESSION_DURATION_MINUTES=43200 # 30 days
# App URLs
NEXT_PUBLIC_APP_BASE_URL=http://localhost:3000
NEXT_PUBLIC_STYTCH_REDIRECT_PATH=/authenticate
Additional Security Recommendations
- Enable MFA: Require multi-factor authentication for sensitive organizations
- Monitor failed attempts: Track authentication failures in your logs
- Rate limiting: Add rate limiting to
/api/auth/magic-linkendpoint - Email verification: Ensure all members have verified emails
- Session duration: Keep session duration appropriate for your security requirements
Migration from Discovery Flow
If you were previously using the Discovery flow with self-service signup:
- Export existing members: Get list of all current members
- Notify users: Inform them that signup is now invite-only
- Update documentation: Update user docs about the new auth flow
- Monitor support requests: Users may try to sign up and fail
Questions?
For Stytch-specific configuration questions:
For implementation questions related to this codebase:
- Review
app/api/auth/magic-link/route.tsfor backend logic - Review
app/auth/page.tsxfor frontend implementation