web-production-saas-starter/next_b2b_starter/STYTCH_CONFIGURATION.md
2025-12-16 18:54:41 +04:00

243 lines
7.3 KiB
Markdown

# 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:
1. **Prevent emails being sent to non-existent users**
2. **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:
1. **Frontend**: Custom email form in `app/auth/page.tsx`
2. **Backend API**: `/api/auth/magic-link` validates membership before sending
3. **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
1. Log into your [Stytch Dashboard](https://stytch.com/dashboard)
2. Navigate to **Frontend SDK** settings
3. Find **"Create Organizations"** toggle under **Enabled methods**
4. **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:
1. Navigate to **Organizations** in the dashboard
2. Select your organization
3. Go to **Settings****Authentication**
4. Configure the following:
```json
{
"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 link
- `email_invites: "RESTRICTED"` - Requires explicit invitation to join
- `email_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:
```bash
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
1. Enter an email that doesn't exist in any organization
2. Click "Send magic link"
3. **Expected**: Message says "If an account exists with that email, a magic link has been sent."
4. **Verify**: No email is actually sent
5. **Check backend logs**: Should see "No members found" for the email
### Test Case 2: Existing Member
1. Enter an email of an existing organization member
2. Click "Send magic link"
3. **Expected**: Same message as above
4. **Verify**: Email IS sent with magic link
5. **Check inbox**: Magic link email received
### Test Case 3: Magic Link Authentication
1. Click the magic link from Test Case 2
2. **Expected**: User is authenticated and redirected to dashboard
3. **Verify**: Session is created with correct organization
### Test Case 4: Unknown User Clicks Link (if they somehow got one)
1. If someone gets a magic link URL (e.g., from a legitimate user)
2. **Expected**: Authentication fails with error
3. **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:**
```json
{
"email": "user@company.com"
}
```
**Response (Success):**
```json
{
"success": true,
"message": "If an account exists with that email, a magic link has been sent."
}
```
**Response (Error):**
```json
{
"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
1. Go to **Organizations** → Select org → **Members**
2. Click **Invite Member**
3. Enter email and assign roles
4. User receives invitation email
### Method 2: Programmatic Invite
```typescript
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
```typescript
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:**
1. Email is verified in Stytch
2. Member status is "active" (not "pending" or "invited")
3. Backend logs for member search results
4. Stytch API credentials are correct
### Issue: Unknown users still getting emails
**Check:**
1. Using `/api/auth/magic-link` endpoint (not direct Stytch SDK call)
2. Backend search is working correctly
3. 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`:
```bash
# 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
1. **Enable MFA**: Require multi-factor authentication for sensitive organizations
2. **Monitor failed attempts**: Track authentication failures in your logs
3. **Rate limiting**: Add rate limiting to `/api/auth/magic-link` endpoint
4. **Email verification**: Ensure all members have verified emails
5. **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:
1. **Export existing members**: Get list of all current members
2. **Notify users**: Inform them that signup is now invite-only
3. **Update documentation**: Update user docs about the new auth flow
4. **Monitor support requests**: Users may try to sign up and fail
## Questions?
For Stytch-specific configuration questions:
- [Stytch B2B Documentation](https://stytch.com/docs/b2b)
- [Stytch Support](https://stytch.com/contact)
For implementation questions related to this codebase:
- Review `app/api/auth/magic-link/route.ts` for backend logic
- Review `app/auth/page.tsx` for frontend implementation