Skip to content

Authentication — SDK

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.

  • 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

The useAuth() hook returns authentication state and all auth methods.

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()
interface User {
id: string
email: string
name?: string
avatar_url?: string
emailVerified: boolean
createdAt?: string
customFields?: Record<string, any>
}

const { signUp } = useAuth()
const result = await signUp('jane@example.com', 'securePassword123', {
name: 'Jane Doe',
customFields: { plan: 'free', referral: 'twitter' },
turnstileToken: token, // optional Cloudflare Turnstile
})
if (result.requiresVerification) {
// Show "check your email" UI
}

Parameters:

ParameterTypeRequiredDescription
emailstringYesNew user’s email address
passwordstringYesPassword (minimum length set in project settings)
opts.namestringNoDisplay name
opts.customFieldsRecord<string, any>NoArbitrary profile data
opts.turnstileTokenstringNoCloudflare Turnstile bot protection token

const { signIn, error } = useAuth()
try {
await signIn('jane@example.com', '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.


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('jane@example.com', 'email')
// or: await sendOTP('+1234567890', 'phone')
// Step 2: User enters the code
await verifyOTP('jane@example.com', '482916', 'email')
// user is now signed in

Parameters:

MethodParameterTypeDefault
sendOTPidentifierstring
sendOTPtype'email' | 'phone''email'
verifyOTPidentifierstring
verifyOTPcodestring
verifyOTPtype'email' | 'phone''email'

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)
}
}, [])
const { resendVerificationEmail } = useAuth()
await resendVerificationEmail('jane@example.com')

  1. Request a reset link

    const { requestPasswordReset } = useAuth()
    await requestPasswordReset('jane@example.com')
    // User receives an email with a reset link
  2. Set 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')

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)

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
FieldTypeDescription
namestringDisplay name
avatar_urlstringDirect URL to avatar image
avatar_image_idstringStorage image ID (auto-resolves to URL)
customFieldsRecord<string, any>Merged into existing custom fields
const { deleteAvatar } = useAuth()
await deleteAvatar()
const { refreshUser } = useAuth()
await refreshUser()
// user state is updated from the server

const { signOut } = useAuth()
await signOut()
// Invalidates the refresh token server-side
// Clears user and tokens from local state

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 })
}
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')))

src/pages/Login.tsx
import { useAuth } from '@aerostack/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>
)
}

MethodSignatureDescription
signIn(email, password, turnstileToken?) => PromiseSign in with credentials
signUp(email, password, opts?) => PromiseRegister a new user
signOut() => PromiseSign out and invalidate tokens
sendOTP(identifier, type?) => PromiseSend a one-time code
verifyOTP(identifier, code, type?) => PromiseVerify OTP and sign in
verifyEmail(token) => PromiseVerify email address
resendVerificationEmail(email) => PromiseResend verification link
requestPasswordReset(email, turnstileToken?) => PromiseSend password reset email
resetPassword(token, newPassword) => PromiseSet new password
refreshAccessToken(refreshToken) => PromiseRefresh the access token
refreshUser() => PromiseRe-fetch user profile
updateProfile(updates) => PromiseUpdate name, avatar, custom fields
deleteAvatar() => PromiseRemove the user’s avatar