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
Section titled “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
Section titled “Send OTP”import { useAuth } from '@aerostack/react'
function OtpStep1() { const { sendOTP } = useAuth()
const handleSend = async () => { await sendOTP('user@example.com', 'email') // Show code input form }}await client.auth.sendOTP({ email: 'user@example.com' })// or for SMS:await client.auth.sendOTP({ phone: '+1234567890' })POST /v1/public/projects/{slug}/auth/otp/send
{ "email": "user@example.com" }# or{ "phone": "+1234567890" }Verify OTP
Section titled “Verify OTP”function OtpStep2() { const { verifyOTP, user } = useAuth()
const handleVerify = async () => { await verifyOTP('user@example.com', '123456', 'email') // user is now signed in }}const result = await client.auth.verifyOTP({ email: 'user@example.com', code: '123456',})// result.accessToken, result.userPOST /v1/public/projects/{slug}/auth/otp/verify
{ "email": "user@example.com", "code": "123456"}Response:
{ "user": { "id": "...", "email": "user@example.com" }, "accessToken": "eyJ...", "refreshToken": "..."}Complete OTP login component
Section titled “Complete OTP login component”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="you@example.com" /> <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> )} </> )}