Authentication
The Aerostack SDK provides a complete authentication system: email/password registration, passwordless OTP, magic link login, email verification, password reset, token refresh, and profile management. All methods are available through the useAuth() React hook and the server-side sdk.auth module.
Beta — Auth APIs are stable but may receive non-breaking additions in minor versions.
What you can build
- SaaS login pages — Email/password registration with email verification
- Mobile OTP flows — Send a 6-digit code to email or phone, verify and sign in
- Magic link onboarding — Frictionless sign-in via email link
- Password reset — Self-service reset with secure token-based flow
- Profile management — Update name, avatar, and custom fields
- Bot protection — Cloudflare Turnstile integration on sign-in and sign-up
React: useAuth() hook
The useAuth() hook returns authentication state and all auth methods.
State
import { useAuth } from '@aerostack/react'
const {
user, // User | null -- current signed-in user
tokens, // { accessToken, refreshToken?, expiresAt? } | null
loading, // boolean -- true during any auth operation
error, // string | null -- last error message
isAuthenticated, // boolean -- shorthand for !!tokens?.accessToken
} = useAuth()User type
interface User {
id: string
email: string
name?: string
avatar_url?: string
emailVerified: boolean
createdAt?: string
customFields?: Record<string, any>
}Registration
const { signUp } = useAuth()
const result = await signUp('[email protected]', 'securePassword123', {
name: 'Jane Doe',
customFields: { plan: 'free', referral: 'twitter' },
turnstileToken: token, // optional Cloudflare Turnstile
})
if (result.requiresVerification) {
// Show "check your email" UI
}Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
email | string | Yes | New user’s email address |
password | string | Yes | Password (minimum length set in project settings) |
opts.name | string | No | Display name |
opts.customFields | Record<string, any> | No | Arbitrary profile data |
opts.turnstileToken | string | No | Cloudflare Turnstile bot protection token |
Sign in (email/password)
const { signIn, error } = useAuth()
try {
await signIn('[email protected]', 'securePassword123')
// user and tokens are now set
} catch (err) {
// error state is also updated
console.error(err.message)
}Supports optional turnstileToken as a third parameter for bot protection.
Passwordless OTP
Send a one-time code to an email or phone number, then verify it to sign in. No password required.
const { sendOTP, verifyOTP } = useAuth()
// Step 1: Send the code
await sendOTP('[email protected]', 'email')
// or: await sendOTP('+1234567890', 'phone')
// Step 2: User enters the code
await verifyOTP('[email protected]', '482916', 'email')
// user is now signed inParameters:
| Method | Parameter | Type | Default |
|---|---|---|---|
sendOTP | identifier | string | — |
sendOTP | type | 'email' | 'phone' | 'email' |
verifyOTP | identifier | string | — |
verifyOTP | code | string | — |
verifyOTP | type | 'email' | 'phone' | 'email' |
OTP codes expire after 10 minutes. After 3 failed verification attempts, the user is locked out for 60 minutes.
Email verification
When email verification is enabled in your project settings, new users receive a verification email after registration.
// In your /verify-email page component
const { verifyEmail } = useAuth()
useEffect(() => {
const token = new URLSearchParams(window.location.search).get('token')
if (token) {
verifyEmail(token)
}
}, [])Resend verification email
const { resendVerificationEmail } = useAuth()
await resendVerificationEmail('[email protected]')Password reset
Request a reset link
const { requestPasswordReset } = useAuth()
await requestPasswordReset('[email protected]')
// User receives an email with a reset linkSet the new password
// In your /reset-password page component
const { resetPassword } = useAuth()
const token = new URLSearchParams(window.location.search).get('token')
await resetPassword(token, 'newSecurePassword456')Token refresh
Access tokens expire after a configurable period (default: 15 minutes). The SDK automatically refreshes tokens on 401 responses, but you can also refresh manually.
const { refreshAccessToken, tokens } = useAuth()
const newTokens = await refreshAccessToken(tokens.refreshToken)Profile management
Update profile
const { updateProfile } = useAuth()
await updateProfile({
name: 'Jane Smith',
avatar_url: 'https://cdn.example.com/avatar.jpg',
customFields: { plan: 'pro', onboarded: true },
})
// user state is automatically refreshed| Field | Type | Description |
|---|---|---|
name | string | Display name |
avatar_url | string | Direct URL to avatar image |
avatar_image_id | string | Storage image ID (auto-resolves to URL) |
customFields | Record<string, any> | Merged into existing custom fields |
Delete avatar
const { deleteAvatar } = useAuth()
await deleteAvatar()Refresh user data
const { refreshUser } = useAuth()
await refreshUser()
// user state is updated from the serverSign out
const { signOut } = useAuth()
await signOut()
// Invalidates the refresh token server-side
// Clears user and tokens from local stateServer-side token verification
In your backend (Node.js, Cloudflare Worker, or edge function), verify access tokens sent by the client:
import { AerostackClient } from '@aerostack/sdk'
const { sdk } = new AerostackClient({ projectId, apiKey })
// In your request handler
const token = request.headers.get('Authorization')?.replace('Bearer ', '')
const user = await sdk.auth.verifyToken(token)
if (!user) {
return new Response('Unauthorized', { status: 401 })
}Hono middleware pattern
import { Hono } from 'hono'
import { createMiddleware } from 'hono/factory'
const requireAuth = createMiddleware(async (c, next) => {
const token = c.req.header('Authorization')?.replace('Bearer ', '')
if (!token) return c.json({ error: 'Missing token' }, 401)
const user = await sdk.auth.verifyToken(token)
if (!user) return c.json({ error: 'Invalid token' }, 401)
c.set('user', user)
await next()
})
app.get('/api/me', requireAuth, (c) => c.json(c.get('user')))Complete example: login page
import { useAuth } from '@aerostack/react'
import { useState } from 'react'
export function LoginPage() {
const { signIn, signUp, sendOTP, verifyOTP, error, loading } = useAuth()
const [mode, setMode] = useState<'password' | 'otp'>('password')
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [otpCode, setOtpCode] = useState('')
const [otpSent, setOtpSent] = useState(false)
const handlePasswordLogin = async (e: React.FormEvent) => {
e.preventDefault()
await signIn(email, password)
}
const handleSendOTP = async () => {
await sendOTP(email, 'email')
setOtpSent(true)
}
const handleVerifyOTP = async (e: React.FormEvent) => {
e.preventDefault()
await verifyOTP(email, otpCode, 'email')
}
return (
<div>
<div>
<button onClick={() => setMode('password')}>Password</button>
<button onClick={() => setMode('otp')}>OTP</button>
</div>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email address"
/>
{mode === 'password' ? (
<form onSubmit={handlePasswordLogin}>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<button type="submit" disabled={loading}>Sign in</button>
</form>
) : otpSent ? (
<form onSubmit={handleVerifyOTP}>
<input
value={otpCode}
onChange={(e) => setOtpCode(e.target.value)}
placeholder="Enter 6-digit code"
maxLength={6}
/>
<button type="submit" disabled={loading}>Verify</button>
</form>
) : (
<button onClick={handleSendOTP} disabled={loading}>
Send code to {email || '...'}
</button>
)}
{error && <p style={{ color: 'red' }}>{error}</p>}
</div>
)
}All useAuth methods
| Method | Signature | Description |
|---|---|---|
signIn | (email, password, turnstileToken?) => Promise | Sign in with credentials |
signUp | (email, password, opts?) => Promise | Register a new user |
signOut | () => Promise | Sign out and invalidate tokens |
sendOTP | (identifier, type?) => Promise | Send a one-time code |
verifyOTP | (identifier, code, type?) => Promise | Verify OTP and sign in |
verifyEmail | (token) => Promise | Verify email address |
resendVerificationEmail | (email) => Promise | Resend verification link |
requestPasswordReset | (email, turnstileToken?) => Promise | Send password reset email |
resetPassword | (token, newPassword) => Promise | Set new password |
refreshAccessToken | (refreshToken) => Promise | Refresh the access token |
refreshUser | () => Promise | Re-fetch user profile |
updateProfile | (updates) => Promise | Update name, avatar, custom fields |
deleteAvatar | () => Promise | Remove the user’s avatar |