OTP & Magic Link
Passwordless sign-in via one-time codes sent to email or SMS. The flow is two steps: send a code, then verify it.
How it works
- User enters their email (or phone)
- Aerostack sends a 6-digit code
- User enters the code — they’re signed in
No password required. No link to click. The code expires in 10 minutes.
Send OTP
import { useAuth } from '@aerostack/react'
function OtpStep1() {
const { sendOTP } = useAuth()
const handleSend = async () => {
await sendOTP('[email protected]', 'email')
// Show code input form
}
}Verify OTP
function OtpStep2() {
const { verifyOTP, user } = useAuth()
const handleVerify = async () => {
await verifyOTP('[email protected]', '123456', 'email')
// user is now signed in
}
}Complete OTP login component
import { useState } from 'react'
import { useAuth } from '@aerostack/react'
export function OtpLogin() {
const { sendOTP, verifyOTP, user, loading, error } = useAuth()
const [step, setStep] = useState<'email' | 'code'>('email')
const [email, setEmail] = useState('')
const [code, setCode] = useState('')
const handleSend = async (e) => {
e.preventDefault()
await sendOTP(email)
setStep('code')
}
const handleVerify = async (e) => {
e.preventDefault()
await verifyOTP(email, code)
}
if (user) return <p>Signed in as {user.email}</p>
return (
<>
{step === 'email' && (
<form onSubmit={handleSend}>
<input
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
placeholder="[email protected]"
/>
<button disabled={loading}>Send code</button>
</form>
)}
{step === 'code' && (
<form onSubmit={handleVerify}>
<p>Enter the 6-digit code sent to {email}</p>
<input
value={code}
onChange={e => setCode(e.target.value)}
maxLength={6}
placeholder="123456"
/>
{error && <p className="text-red-500">{error}</p>}
<button disabled={loading}>Verify</button>
<button type="button" onClick={() => setStep('email')}>Back</button>
</form>
)}
</>
)
}OTP verification is rate-limited to 3 attempts per code. After 3 failures, the code is invalidated and a new one must be requested.