AuthenticationAuth methods
Email OTP
Six-digit one-time codes sent via email for passwordless sign-in.
Email OTP sends a short numeric code to the user's inbox. It works well for mobile flows where clicking a link is awkward and for second-factor verification.
Setup
Add the plugin
import { createKavach } from '@kavachos/core';
import { emailOtp } from '@kavachos/core/plugins/email-otp';
const kavach = await createKavach({
database: { provider: 'postgres', url: process.env.DATABASE_URL! },
secret: process.env.KAVACH_SECRET!,
baseUrl: 'https://auth.example.com',
plugins: [
emailOtp({
sendEmail: async ({ to, code }) => {
await resend.emails.send({
from: 'auth@example.com',
to,
subject: `Your code: ${code}`,
html: `<p>Your sign-in code is <strong>${code}</strong>. It expires in 10 minutes.</p>`,
});
},
}),
],
});The sign-in flow
- User submits their email to
POST /auth/email-otp/send. - KavachOS generates a six-digit code (cryptographically random) and calls your
sendEmailfunction. - User enters the code in your UI and submits to
POST /auth/email-otp/verify. - On success, a session cookie is set.
Sending a code
POST /auth/email-otp/send
await fetch('/auth/email-otp/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'user@example.com' }),
});Verifying the code
POST /auth/email-otp/verify
const res = await fetch('/auth/email-otp/verify', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'user@example.com', code: '482910' }),
});
const { userId, sessionId } = await res.json();Options
| Option | Type | Default | Description |
|---|---|---|---|
sendEmail | function | required | Called with { to, code } |
codeLength | number | 6 | Length of the OTP code |
codeTtl | number | 600 | Code lifetime in seconds (default: 10 minutes) |
maxAttempts | number | 5 | Failed attempts before the code is invalidated |
createUserIfNotFound | boolean | true | Auto-create accounts for new emails |
Codes are rate-limited to one per minute per email address. Requests within the window return 429. Build a countdown timer into your UI.