Add Turalogin email authentication to my app using V2 API. Here's how it works:
1. My backend calls Turalogin API to start auth (sends email to user)
2. User receives email with BOTH a clickable one-time login link AND a 6-digit code
3. User chooses: Click link (instant) OR enter code manually (if link doesn't work)
4. My backend verifies the token/code with Turalogin API (server-side only)
5. On success, I create my own session/cookie - Turalogin never touches my frontend
API Details (V2 - Recommended):
- Base URL: https://api.turalogin.com/api/v2
- IMPORTANT: Every API request MUST include the Authorization header:
Authorization: Bearer <TURALOGIN_API_KEY>
Security Notes:
- All /auth/verify endpoints MUST be called server-side only (never from browser)
- Your TURALOGIN_API_KEY must never be exposed to the client
V2 Endpoints:
POST /auth/start
Body: {
email: string,
method?: 'ota' | 'otp', // Optional, defaults to 'ota'
redirectUrl?: string, // Required for ota method
appName?: string // Optional: Override app name in emails (max 128 chars, for multi-brand apps)
}
Returns: { token, method, message, expiresAt }
- method='ota' (One Time Authentication): Email contains clickable link + 6-digit code
- Link format: {redirectUrl}?token={token}
- User can click link OR enter the 6-digit code
- method='otp': Email contains ONLY 6-digit code (no link)
- For pure OTP flow without links
POST /auth/verify/link
Body: {
token: string // From URL query parameter (?token=...)
}
Returns: { success, jwt, user: { email } }
- Use this when user clicks the one-time login link
- Extract token from URL query parameter
- Returns JWT in the "jwt" field (V2 uses email as JWT subject)
- V2 does not return expiresIn - you manage your own session duration
POST /auth/verify/code
Body: {
token: string, // From /auth/start response
code: string // 6-digit code from email
}
Returns: { success, jwt, user: { email } }
- Use this when user enters the 6-digit code manually
- Requires BOTH token (from /auth/start) AND code (from user input)
- Returns JWT in the "jwt" field (V2 uses email as JWT subject)
- V2 does not return expiresIn - you manage your own session duration
Error Responses:
- 400: Missing or invalid parameters (includes helpful hints)
- 401: Invalid, expired, already-used token/session, or wrong verification code
- 500: Server error (retry with exponential backoff)
Session Constraints:
- One-time login links and OTP codes expire after 15 minutes
- Each link/code can only be used once (single-use)
- After verification, create your own session - Turalogin does not manage sessions
Domain/User Restriction (Your Responsibility):
Turalogin verifies ANY email address. If your app needs access control:
- Domain restriction: Validate email domain BEFORE calling /auth/start
- Email allowlist: Check against your user database BEFORE calling /auth/start
- Example: const domain = email.split("@")[1]?.toLowerCase(); if (!ALLOWED_DOMAINS.includes(domain)) return 403;
- This validation MUST be server-side (in your API route), never client-side only
- Turalogin handles email verification - domain/user restriction is your responsibility
Authentication Methods:
- **ota** (default - One Time Authentication): Email contains clickable link + 6-digit code. User chooses which to use.
- **otp**: Email contains ONLY 6-digit code (no link). Pure OTP flow.
Per-Request Branding (appName):
- Pass optional appName in /auth/start to override the dashboard-configured app name in emails
- Useful for multi-brand apps sharing a single API key (e.g. multiple domains, white-label)
- Email subject/body will say "Sign in to {appName}" instead of the dashboard name
- Omitting appName falls back to dashboard name (fully backward compatible)
V2 vs V1:
V2 uses clearer naming and separate endpoints:
- token (not sessionId)
- jwt (not token in response)
- redirectUrl (not validationUrl)
- /verify/link and /verify/code (not single /verify endpoint)
Environment Variables:
- TURALOGIN_API_KEY: Your API key from the dashboard
- TURALOGIN_REDIRECT_URL: The URL where authentication links redirect to (required for ota method)
- Development: http://localhost:3000/auth/verify
- Production: https://myapp.com/auth/verify
Example fetch call to start authentication (V2):
const response = await fetch('https://api.turalogin.com/api/v2/auth/start', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.TURALOGIN_API_KEY}`
},
body: JSON.stringify({
email,
method: 'ota', // 'ota' (link + code) or 'otp' (code only)
redirectUrl: process.env.TURALOGIN_REDIRECT_URL, // required for ota
appName: 'My Brand' // optional: override app name in emails (for multi-brand apps)
})
});
const { token } = await response.json();
// IMPORTANT: Store this token - you'll need it for code verification
Please create:
1. Auth endpoint to start authentication:
- Calls Turalogin /api/v2/auth/start with email, method, and redirectUrl
- Returns the token to your frontend/confirmation page
- Example: redirect to /check-email?token=${token}&email=${email}
2. Auth verification endpoints that handle TWO authentication paths:
Path A - One Time Login Link, akaMagic Link (automatic):
- Extract token from URL query param: ?token=xyz
- Call /api/v2/auth/verify/link with { token }
- Receives: { jwt, user }
Path B - 6-Digit Code (manual):
- User enters the 6-digit code from email
- Get the token from your confirmation page (URL param, state, or hidden field)
- Call /api/v2/auth/verify/code with { token: tokenFromStep1, code: userEnteredCode }
- Receives: { jwt, user }
- CRITICAL: You must pass BOTH token (from step 1) AND code (from user input)
3. Login page/form with email input
4. Confirmation page after email submission:
- Display: "Check your email for a login link from Turalogin.com"
- Display: "Or enter the 6-digit code from the email below:"
- Include a code input field (6 digits, numeric)
- Store the token from step 1 (pass via URL param, React state, or form hidden field)
- When user enters code, call your verification endpoint with { token, code }
5. Use proper error handling for all error cases (400, 401, 500) and loading states
6. Set up environment variables for both TURALOGIN_API_KEY and TURALOGIN_REDIRECT_URL
IMPORTANT - Domain Restriction (Your Responsibility):
Turalogin will send a magic link to ANY valid email address. If you need to restrict access to specific email domains (e.g., internal tools, team dashboards, B2B apps), you MUST validate the domain in your backend BEFORE calling /auth/start.
Example domain restriction pattern:
const domain = email.split("@")[1]?.toLowerCase();
const ALLOWED_DOMAINS = ["yourcompany.com", "partner.com"];
if (!ALLOWED_DOMAINS.includes(domain)) {
return Response.json({ error: "Unauthorized domain" }, { status: 403 });
}
This validation must be server-side (in your API route), never client-side only. Turalogin handles email verification - domain/user restriction is your responsibility.
V2 Key Concepts:
- token: Returned by /auth/start, used for verification (both paths)
- jwt: Returned by /auth/verify/link or /auth/verify/code (this is your authentication JWT)
- code: The 6-digit number in the email (optional alternative to clicking link)
- appName: Optional per-request override of the email display name (for multi-brand/white-label apps)
V2 Endpoints:
- Start: POST /api/v2/auth/start → returns { token }
- Verify link: POST /api/v2/auth/verify/link → accepts { token } → returns { jwt, user }
- Verify code: POST /api/v2/auth/verify/code → accepts { token, code } → returns { jwt, user }
V2 is clearer than V1 because it uses separate endpoints for link and code verification.