Phone Authentication
Phone number-based authentication using SMS verification for secure user access.
How It Works
Phone authentication uses a two-step verification process:
- User enters phone number
- SMS verification code sent
- User enters code to verify and authenticate
- Access token issued upon successful verification
Supported formats: International format with country code (e.g., +1234567890)
Authentication Flow
1
Initiate Authentication
Request verification code
typescript
POST https://auth.dedot.io/v1/auth/phone/initiate
{
"phone": "+1234567890",
"channel": "sms" // or "whatsapp" if supported
}Response:
json
{
"success": true,
"message": "Verification code sent",
"sessionId": "sess_abc123def456",
"expiresIn": 300 // seconds
}2
Verify Code
Submit verification code to authenticate
typescript
POST https://auth.dedot.io/v1/auth/phone/verify
{
"phone": "+1234567890",
"code": "123456",
"sessionId": "sess_abc123def456"
}Response:
json
{
"success": true,
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "refresh_token_here",
"expiresIn": 3600,
"user": {
"id": "usr_123456",
"phone": "+1234567890",
"verified": true,
"createdAt": "2026-01-16T10:00:00Z"
}
}3
Use Access Token
Authenticate API requests
typescript
// Include in request headers
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
// Example API call
GET https://api.dedot.io/v1/profile
Authorization: Bearer <access_token>Complete Implementation
React Example
typescript
import { useState } from 'react'
function PhoneAuth() {
const [phone, setPhone] = useState('')
const [code, setCode] = useState('')
const [sessionId, setSessionId] = useState('')
const [step, setStep] = useState<'phone' | 'code'>('phone')
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const initiateAuth = async () => {
setLoading(true)
setError('')
try {
const response = await fetch('https://auth.dedot.io/v1/auth/phone/initiate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ phone, channel: 'sms' })
})
const data = await response.json()
if (data.success) {
setSessionId(data.sessionId)
setStep('code')
} else {
setError(data.message || 'Failed to send code')
}
} catch (err) {
setError('Network error. Please try again.')
} finally {
setLoading(false)
}
}
const verifyCode = async () => {
setLoading(true)
setError('')
try {
const response = await fetch('https://auth.dedot.io/v1/auth/phone/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ phone, code, sessionId })
})
const data = await response.json()
if (data.success) {
// Store tokens
localStorage.setItem('accessToken', data.accessToken)
localStorage.setItem('refreshToken', data.refreshToken)
// Redirect to app
window.location.href = '/dashboard'
} else {
setError(data.message || 'Invalid code')
}
} catch (err) {
setError('Network error. Please try again.')
} finally {
setLoading(false)
}
}
return (
<div className="auth-container">
{step === 'phone' ? (
<>
<input
type="tel"
placeholder="+1 234 567 8900"
value={phone}
onChange={(e) => setPhone(e.target.value)}
/>
<button onClick={initiateAuth} disabled={loading}>
{loading ? 'Sending...' : 'Send Code'}
</button>
</>
) : (
<>
<input
type="text"
placeholder="Enter 6-digit code"
value={code}
onChange={(e) => setCode(e.target.value)}
maxLength={6}
/>
<button onClick={verifyCode} disabled={loading}>
{loading ? 'Verifying...' : 'Verify'}
</button>
<button onClick={() => setStep('phone')}>
Change Number
</button>
</>
)}
{error && <div className="error">{error}</div>}
</div>
)
}Mobile (React Native) Example
typescript
import { useState } from 'react'
import { View, TextInput, TouchableOpacity, Text, ActivityIndicator } from 'react-native'
function PhoneAuthScreen() {
const [phone, setPhone] = useState('')
const [code, setCode] = useState('')
const [sessionId, setSessionId] = useState('')
const [step, setStep] = useState<'phone' | 'code'>('phone')
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const initiateAuth = async () => {
setLoading(true)
setError('')
try {
const response = await fetch('https://auth.dedot.io/v1/auth/phone/initiate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ phone, channel: 'sms' })
})
const data = await response.json()
if (data.success) {
setSessionId(data.sessionId)
setStep('code')
} else {
setError(data.message || 'Failed to send code')
}
} catch (err) {
setError('Network error. Please try again.')
} finally {
setLoading(false)
}
}
const verifyCode = async () => {
setLoading(true)
setError('')
try {
const response = await fetch('https://auth.dedot.io/v1/auth/phone/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ phone, code, sessionId })
})
const data = await response.json()
if (data.success) {
// Store tokens securely
await AsyncStorage.setItem('accessToken', data.accessToken)
await AsyncStorage.setItem('refreshToken', data.refreshToken)
// Navigate to app
navigation.navigate('Dashboard')
} else {
setError(data.message || 'Invalid code')
}
} catch (err) {
setError('Network error. Please try again.')
} finally {
setLoading(false)
}
}
return (
<View style={styles.container}>
{step === 'phone' ? (
<>
<TextInput
style={styles.input}
placeholder="+1 234 567 8900"
value={phone}
onChangeText={setPhone}
keyboardType="phone-pad"
/>
<TouchableOpacity
style={styles.button}
onPress={initiateAuth}
disabled={loading}
>
{loading ? (
<ActivityIndicator color="white" />
) : (
<Text style={styles.buttonText}>Send Code</Text>
)}
</TouchableOpacity>
</>
) : (
<>
<TextInput
style={styles.input}
placeholder="Enter 6-digit code"
value={code}
onChangeText={setCode}
keyboardType="number-pad"
maxLength={6}
/>
<TouchableOpacity
style={styles.button}
onPress={verifyCode}
disabled={loading}
>
{loading ? (
<ActivityIndicator color="white" />
) : (
<Text style={styles.buttonText}>Verify</Text>
)}
</TouchableOpacity>
</>
)}
{error && <Text style={styles.error}>{error}</Text>}
</View>
)
}Request Parameters
Initiate Authentication
| Parameter | Type | Required | Description |
|---|---|---|---|
phone | string | Yes | Phone number in international format (+1234567890) |
channel | string | No | Delivery channel: sms (default) or whatsapp |
Verify Code
| Parameter | Type | Required | Description |
|---|---|---|---|
phone | string | Yes | Same phone number used in initiate |
code | string | Yes | 6-digit verification code |
sessionId | string | Yes | Session ID from initiate response |
Response Codes
| Code | Status | Description |
|---|---|---|
| 200 | Success | Code sent or verification successful |
| 400 | Bad Request | Invalid phone format or missing parameters |
| 429 | Rate Limit | Too many requests, try again later |
| 500 | Server Error | Internal server error |
Security Best Practices
Do
- Validate phone format before sending
- Implement rate limiting on client side
- Store tokens securely (localStorage/AsyncStorage)
- Use HTTPS for all requests
- Clear session data after verification
- Implement retry logic with exponential backoff
Don't
- Don't store verification codes
- Don't expose session IDs in URLs
- Don't allow unlimited retry attempts
- Don't log tokens or sensitive data
- Don't hardcode phone numbers in code
- Don't skip phone format validation
Error Handling
typescript
async function authenticatePhone(phone: string, code?: string) {
try {
if (!code) {
// Initiate
const response = await fetch('https://auth.dedot.io/v1/auth/phone/initiate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ phone })
})
if (!response.ok) {
const error = await response.json()
switch (response.status) {
case 400:
throw new Error('Invalid phone number format')
case 429:
throw new Error('Too many attempts. Please try again later.')
case 500:
throw new Error('Service temporarily unavailable')
default:
throw new Error(error.message || 'Authentication failed')
}
}
return await response.json()
} else {
// Verify
// ... verification logic with similar error handling
}
} catch (error) {
// Handle network errors
if (error instanceof TypeError) {
throw new Error('Network error. Please check your connection.')
}
throw error
}
}Rate Limiting
Phone authentication has rate limits to prevent abuse:
- Initiate: 5 requests per phone number per hour
- Verify: 10 attempts per session
- Global: 100 requests per IP per hour
Exceeded limits return HTTP 429 with retry-after header.
Testing
Use these test phone numbers in development:
| Phone Number | Verification Code | Behavior |
|---|---|---|
| +15555550100 | 123456 | Always succeeds |
| +15555550101 | 654321 | Always fails (invalid code) |
| +15555550102 | Any code | Simulates network error |
Development Only
Test phone numbers only work in development environment. Production requires real phone verification.

