# OTP & Magic Link

> Passwordless sign-in for your app. Send one-time codes or magic links to email or SMS. Two SDK calls: request a code, then verify it.

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

1. User enters their email (or phone)
2. Aerostack sends a 6-digit code
3. User enters the code — they're signed in

No password required. No link to click. The code expires in 10 minutes.

## Send OTP

```tsx

function OtpStep1() {
  const { sendOTP } = useAuth()

  const handleSend = async () => {
    await sendOTP('user@example.com', 'email')
    // Show code input form
  }
}
```

```ts
await client.auth.sendOTP({ email: 'user@example.com' })
// or for SMS:
await client.auth.sendOTP({ phone: '+1234567890' })
```

```bash
POST /v1/public/projects/{slug}/auth/otp/send

{ "email": "user@example.com" }
# or
{ "phone": "+1234567890" }
```

## Verify OTP

```tsx
function OtpStep2() {
  const { verifyOTP, user } = useAuth()

  const handleVerify = async () => {
    await verifyOTP('user@example.com', '123456', 'email')
    // user is now signed in
  }
}
```

```ts
const result = await client.auth.verifyOTP({
  email: 'user@example.com',
  code: '123456',
})
// result.accessToken, result.user
```

```bash
POST /v1/public/projects/{slug}/auth/otp/verify

{
  "email": "user@example.com",
  "code": "123456"
}
```

**Response:**
```json
{
  "user": { "id": "...", "email": "user@example.com" },
  "accessToken": "eyJ...",
  "refreshToken": "..."
}
```

## Complete OTP login component

```tsx

  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>
      )}
    </>
  )
}
```

OTP verification is rate-limited to 3 attempts per code. After 3 failures, the code is invalidated and a new one must be requested.
