Skip to content

Phone Authentication

Phone number-based authentication using SMS verification for secure user access.

How It Works

Phone authentication uses a two-step verification process:

  1. User enters phone number
  2. SMS verification code sent
  3. User enters code to verify and authenticate
  4. 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

ParameterTypeRequiredDescription
phonestringYesPhone number in international format (+1234567890)
channelstringNoDelivery channel: sms (default) or whatsapp

Verify Code

ParameterTypeRequiredDescription
phonestringYesSame phone number used in initiate
codestringYes6-digit verification code
sessionIdstringYesSession ID from initiate response

Response Codes

CodeStatusDescription
200SuccessCode sent or verification successful
400Bad RequestInvalid phone format or missing parameters
429Rate LimitToo many requests, try again later
500Server ErrorInternal 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 NumberVerification CodeBehavior
+15555550100123456Always succeeds
+15555550101654321Always fails (invalid code)
+15555550102Any codeSimulates network error

Development Only

Test phone numbers only work in development environment. Production requires real phone verification.

Next Steps