TOTP Hosted Integration
Implement TOTP authentication using Akedly's hosted pages for the fastest deployment with zero UI development. This approach provides a complete white-labeled experience with your branding while handling all complex UI states, error handling, and cross-platform compatibility automatically.
Overview
With hosted integration, Akedly provides complete TOTP setup and authentication pages that match your company branding. Users are redirected to these pages when they need to set up TOTP or authenticate, then returned to your application with the results.
How It Works:
- Create a TOTP Pipeline in your dashboard with callback URLs
- Redirect users to Akedly's hosted setup page:
https://app.akedly.io/totp/setup
- Users complete TOTP setup on the hosted page with your branding
- Users are redirected back to your application
- For authentication, redirect to hosted auth page:
https://app.akedly.io/totp/request
- Receive webhooks and frontend callbacks with results
Key Benefits:
- Zero Frontend Development: No UI components to build
- Cross-Platform Support: Works on web, mobile, and desktop automatically
- Error Handling: All edge cases handled (expired requests, rate limiting, etc.)
- White-Label Branding: Uses your company name, logo, and colors
- Mobile Optimized: Automatic mobile/desktop UI adaptation
- Webhook Integration: Real-time server notifications
Prerequisites
Before starting, ensure you have completed the basic Akedly setup:
- Akedly Account: Sign up if you haven't already
- API Key: Available in your dashboard under "View API Key"
- Company Profile: Complete your company information in the dashboard for branding
- Test Environment: Start with test mode before going live
Company Branding: Your company name, logo, and description from the dashboard will appear on all hosted TOTP pages, providing a seamless branded experience for your users.
Step 1: Create TOTP Pipeline
A TOTP Pipeline configures how hosted pages integrate with your application through callback URLs and redirects.
Create Pipeline in Dashboard
Navigate to TOTP Pipelines in your Akedly dashboard and create a new pipeline:
Pipeline Configuration:
- Name
Pipeline Name
- Type
- string
- Description
Descriptive name for internal reference (e.g., "Production Mobile App TOTP")
- Name
Description
- Type
- string
- Description
Optional description for team documentation
- Name
Frontend Callback URL
- Type
- string
- Description
Where users are redirected after completing setup or authentication on web browsers
- Name
Backend Callback URL
- Type
- string
- Description
Webhook endpoint for server-to-server notifications (optional but recommended)
- Name
Mobile Redirect URL
- Type
- string
- Description
Deep link for mobile app returns (e.g., "myapp://totp-complete")
- Name
Custom Scheme
- Type
- string
- Description
Your mobile app's URL scheme (e.g., "myapp")
- Name
Type
- Type
- string
- Description
"Test" for development, "Live" for production
Example Pipeline Configuration
Pipeline Setup Examples
{
"name": "Web App TOTP Authentication",
"description": "TOTP integration for main web application",
"frontendCallbackURL": "https://myapp.com/auth/totp-complete",
"backendCallbackURL": "https://api.myapp.com/webhooks/totp",
"type": "Live"
}
Pipeline URLs: Akedly automatically detects mobile devices and uses the Mobile Redirect URL for mobile users, Frontend Callback URL for desktop users. Backend Callback URL sends server-to-server notifications regardless of device type.
Step 2: Implement User Setup Flow
When a user needs to set up TOTP authentication, redirect them to Akedly's hosted setup page. The hosted page handles QR code generation, deep links, manual setup options, and device detection automatically.
Setup URL Structure
https://app.akedly.io/totp/setup?pipeline={pipelineID}&identifier={userIdentifier}
Required Parameters:
pipeline
: The TOTP Pipeline ID from Step 1identifier
: Unique user identifier (email, username, or user ID)
Implementation Examples
Setup Flow Implementation
// Store your pipeline ID (from dashboard)
const TOTP_PIPELINE_ID = 'your_pipeline_id_here'
function setupTOTPForUser(userEmail) {
// Construct hosted setup URL
const setupURL = `https://app.akedly.io/totp/setup?pipeline=${TOTP_PIPELINE_ID}&identifier=${encodeURIComponent(userEmail)}`
// Redirect user to hosted setup page
window.location.href = setupURL
// User will complete setup on Akedly's hosted page
// They'll be redirected back to your frontendCallbackURL when done
}
// Example usage
document.getElementById('setup-totp-btn').addEventListener('click', () => {
const userEmail = getCurrentUserEmail() // Your function to get user email
setupTOTPForUser(userEmail)
})
What Happens During Setup
- User Redirection: User is redirected to
https://app.akedly.io/totp/setup?pipeline=ID&identifier=email
- Hosted Page Loading: Akedly loads the setup page with your company branding
- TOTP User Creation: System automatically creates TOTP configuration for the user
- Setup Options Display: Page shows QR code, deep link, or manual setup based on device
- User Setup: User adds account to their authenticator app
- Verification: User enters first TOTP code to verify setup
- Completion: User is redirected back to your application
Automatic Setup: The hosted page automatically calls the TOTP creation API and handles all setup steps. You don't need to make any API calls for the setup process when using hosted integration.
Step 3: Implement Authentication Flow
When you need to authenticate a user with TOTP (e.g., at login, for sensitive operations), create an authentication request and redirect them to the hosted authentication page.
Authentication Flow Steps
Authentication Implementation
// Complete authentication flow for web applications
async function authenticateUserWithTOTP(userEmail) {
try {
// Step 1: Create authentication request
const response = await fetch(
'https://api.akedly.io/api/v1/totp/request-auth',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
APIKey: 'your_api_key_here',
identifier: userEmail,
pipelineID: 'your_pipeline_id_here',
}),
},
)
const data = await response.json()
if (data.status === 'success') {
const authRequestID = data.data.authRequestID
const expiresAt = data.data.expiresAt
// Step 2: Redirect to hosted authentication page
const authURL = `https://app.akedly.io/totp/request?requestID=${authRequestID}`
// Store request ID for potential cleanup
sessionStorage.setItem('totp_auth_request', authRequestID)
sessionStorage.setItem('totp_auth_expires', expiresAt)
// Redirect user to hosted auth page
window.location.href = authURL
// User will enter TOTP code on hosted page
// They'll be redirected back with results
} else {
// Handle errors (user not found, quota exceeded, etc.)
showError(data.message)
}
} catch (error) {
console.error('Authentication request failed:', error)
showError('Failed to start authentication. Please try again.')
}
}
// Helper function to check if user has TOTP enabled
async function checkTOTPStatus(userEmail) {
try {
// This would be your own API endpoint to check TOTP status
const response = await fetch(
`/api/users/${encodeURIComponent(userEmail)}/totp-status`,
)
const data = await response.json()
return data.totpEnabled
} catch (error) {
console.error('Failed to check TOTP status:', error)
return false
}
}
// Example login flow integration
async function handleLogin(email, password) {
// Step 1: Verify email/password
const loginResponse = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
})
if (loginResponse.ok) {
// Step 2: Check if user has TOTP enabled
const hasTOTP = await checkTOTPStatus(email)
if (hasTOTP) {
// Step 3: Require TOTP authentication
await authenticateUserWithTOTP(email)
// User will be redirected to hosted TOTP page
} else {
// User doesn't have TOTP, complete login
completeLogin()
}
} else {
showError('Invalid email or password')
}
}
What Happens During Authentication
- Auth Request Creation: Your backend calls the TOTP request API to create an authentication request
- User Redirection: User is redirected to
https://app.akedly.io/totp/request?requestID=ID
- Hosted Auth Page: Akedly displays TOTP input form with your branding
- Code Entry: User enters 6-digit code from their authenticator app
- Verification: System verifies the code and processes billing
- Completion: User is redirected back to your application with results
Automatic Processing: The hosted authentication page handles all API calls, error states, rate limiting, and expiration automatically. You only need to create the initial request and handle the callback.
Step 4: Handle Callbacks
After users complete setup or authentication on hosted pages, they're redirected back to your application. You'll also receive webhook notifications for server-side processing.
Frontend Callback Handling
Users are redirected to your frontendCallbackURL
with query parameters containing the results.
Callback Handling
// Handle callbacks when users return from hosted pages
function handleTOTPCallback() {
const urlParams = new URLSearchParams(window.location.search)
// Common parameters for both setup and authentication
const status = urlParams.get('status')
const identifier = urlParams.get('identifier')
const timestamp = urlParams.get('timestamp')
if (status === 'success') {
// Check if this was setup or authentication
const totpID = urlParams.get('totpID')
const requestID = urlParams.get('requestID')
if (totpID) {
// This was a setup completion
handleSetupSuccess(identifier, totpID)
} else if (requestID) {
// This was an authentication completion
handleAuthSuccess(identifier, requestID)
}
} else if (status === 'cancelled') {
const reason = urlParams.get('reason')
handleCancellation(reason)
} else if (status === 'expired') {
handleExpiration()
} else if (status === 'error') {
const errorReason = urlParams.get('reason')
handleError(errorReason)
}
// Clean up URL parameters
if (urlParams.has('status')) {
const cleanURL = window.location.pathname
window.history.replaceState({}, document.title, cleanURL)
}
}
function handleSetupSuccess(identifier, totpID) {
console.log(`TOTP setup successful for ${identifier}, ID: ${totpID}`)
// Update UI to show TOTP is now enabled
document.getElementById('totp-status').textContent =
'Two-Factor Authentication: Enabled'
document.getElementById('setup-totp-btn').style.display = 'none'
document.getElementById('disable-totp-btn').style.display = 'block'
// Show success message
showSuccessMessage('Two-factor authentication has been set up successfully!')
// Update user's TOTP status in your system
updateUserTOTPStatus(identifier, true)
}
function handleAuthSuccess(identifier, requestID) {
console.log(
`TOTP authentication successful for ${identifier}, Request: ${requestID}`,
)
// Clear any stored auth request data
sessionStorage.removeItem('totp_auth_request')
sessionStorage.removeItem('totp_user_email')
// Complete login or sensitive operation
completeAuthentication(identifier)
// Show success message
showSuccessMessage('Authentication successful!')
// Redirect to intended destination
const intendedURL = sessionStorage.getItem('intended_url') || '/dashboard'
sessionStorage.removeItem('intended_url')
window.location.href = intendedURL
}
function handleCancellation(reason) {
console.log('User cancelled TOTP process:', reason)
showInfoMessage('Authentication was cancelled. You can try again anytime.')
}
function handleExpiration() {
console.log('TOTP request expired')
showWarningMessage('The authentication request expired. Please try again.')
}
function handleError(reason) {
console.error('TOTP process failed:', reason)
showErrorMessage(
'Authentication failed. Please try again or contact support.',
)
}
// Call callback handler when page loads
document.addEventListener('DOMContentLoaded', handleTOTPCallback)
Webhook Handling
Set up webhook endpoints to receive server-to-server notifications about TOTP events.
Webhook Implementation
const express = require('express')
const app = express()
// Middleware to parse JSON payloads
app.use(express.json())
// TOTP webhook endpoint
app.post('/api/webhooks/totp', (req, res) => {
try {
const {
status,
verified,
requestID,
identifier,
billingAmount,
pipelineID,
totpRequestStatus,
totpRequestID,
totpID,
timestamp,
} = req.body
console.log('TOTP webhook received:', {
status,
identifier,
requestID: requestID || totpRequestID,
verified,
billingAmount,
})
if (status === 'success') {
if (totpID) {
// TOTP setup completed
handleTOTPSetupComplete(identifier, totpID)
} else if (verified) {
// TOTP authentication successful
handleTOTPAuthSuccess(
identifier,
requestID || totpRequestID,
billingAmount,
)
}
} else {
// Handle failures
handleTOTPFailure(identifier, status, requestID || totpRequestID)
}
// Always respond with 200 to acknowledge receipt
res.status(200).json({ received: true })
} catch (error) {
console.error('Webhook processing error:', error)
res.status(500).json({ error: 'Internal server error' })
}
})
async function handleTOTPSetupComplete(identifier, totpID) {
try {
// Update user in database
await db.users.update(
{ email: identifier },
{
totp_enabled: true,
totp_id: totpID,
updated_at: new Date(),
},
)
// Send confirmation email
await sendEmail(
identifier,
'TOTP Setup Complete',
'Two-factor authentication has been successfully enabled for your account.',
)
// Log for analytics
analytics.track('totp_setup_completed', {
user_email: identifier,
totp_id: totpID,
timestamp: new Date(),
})
console.log(`TOTP setup completed for ${identifier}`)
} catch (error) {
console.error(`Failed to process TOTP setup for ${identifier}:`, error)
}
}
async function handleTOTPAuthSuccess(identifier, requestID, billingAmount) {
try {
// Update authentication log
await db.auth_logs.create({
user_email: identifier,
auth_type: 'totp',
request_id: requestID,
status: 'success',
billing_amount: billingAmount,
timestamp: new Date(),
})
// Update user's last authentication time
await db.users.update({ email: identifier }, { last_totp_auth: new Date() })
// Log for analytics
analytics.track('totp_auth_success', {
user_email: identifier,
request_id: requestID,
billing_amount: billingAmount,
timestamp: new Date(),
})
console.log(
`TOTP authentication successful for ${identifier}, charged ${billingAmount} EGP`,
)
} catch (error) {
console.error(
`Failed to process TOTP auth success for ${identifier}:`,
error,
)
}
}
async function handleTOTPFailure(identifier, status, requestID) {
try {
// Log failed attempt
await db.auth_logs.create({
user_email: identifier,
auth_type: 'totp',
request_id: requestID,
status: 'failed',
failure_reason: status,
timestamp: new Date(),
})
// Check for suspicious activity (multiple failures)
const recentFailures = await db.auth_logs.count({
where: {
user_email: identifier,
auth_type: 'totp',
status: 'failed',
timestamp: { $gte: new Date(Date.now() - 15 * 60 * 1000) }, // Last 15 minutes
},
})
if (recentFailures >= 5) {
// Alert security team
await sendSecurityAlert(
identifier,
'Multiple TOTP authentication failures',
)
}
console.log(`TOTP authentication failed for ${identifier}: ${status}`)
} catch (error) {
console.error(`Failed to process TOTP failure for ${identifier}:`, error)
}
}
// Start server
app.listen(3000, () => {
console.log('Webhook server running on port 3000')
})
Callback URL Parameters
- Name
status
- Type
- string
- Description
Result status: "success", "cancelled", "expired", or "error"
- Name
identifier
- Type
- string
- Description
User identifier that was used in the setup/authentication
- Name
totpID
- Type
- string
- Description
Present for setup completion - the TOTP user ID for future reference
- Name
requestID
- Type
- string
- Description
Present for authentication completion - the authentication request ID
- Name
reason
- Type
- string
- Description
Present for cancelled/error status - describes why the process failed
- Name
timestamp
- Type
- string
- Description
ISO timestamp when the event occurred
Mobile App Integration
For mobile applications, hosted integration works seamlessly with both external browser and WebView approaches. Mobile users are automatically redirected using deep links.
Deep Link Configuration
Configure your mobile app to handle deep link returns from hosted pages.
Mobile Deep Link Setup
// React Native deep link integration
import { Linking } from 'react-native'
import { useEffect } from 'react'
const TOTP_PIPELINE_ID = 'your_pipeline_id_here'
export function useTOTPIntegration() {
// Handle deep link returns
useEffect(() => {
const handleUrl = (event) => {
handleTOTPDeepLink(event.url)
}
// Listen for deep links when app is open
const subscription = Linking.addEventListener('url', handleUrl)
// Handle deep link if app was launched from one
Linking.getInitialURL().then((url) => {
if (url) {
handleTOTPDeepLink(url)
}
})
return () => subscription?.remove()
}, [])
const setupTOTP = async (userEmail) => {
const setupURL = `https://app.akedly.io/totp/setup?pipeline=${TOTP_PIPELINE_ID}&identifier=${encodeURIComponent(userEmail)}`
const supported = await Linking.canOpenURL(setupURL)
if (supported) {
await Linking.openURL(setupURL)
} else {
throw new Error('Cannot open TOTP setup URL')
}
}
const authenticateWithTOTP = async (userEmail) => {
try {
// Create auth request
const response = await fetch(
'https://api.akedly.io/api/v1/totp/request-auth',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
APIKey: 'your_api_key_here',
identifier: userEmail,
pipelineID: 'your_pipeline_id_here',
}),
},
)
const data = await response.json()
if (data.status === 'success') {
const authRequestID = data.data.authRequestID
const authURL = `https://app.akedly.io/totp/request?requestID=${authRequestID}`
await Linking.openURL(authURL)
} else {
throw new Error(data.message)
}
} catch (error) {
console.error('TOTP authentication failed:', error)
throw error
}
}
const handleTOTPDeepLink = (url) => {
if (url.includes('totp-complete')) {
const params = new URLSearchParams(url.split('?')[1])
const status = params.get('status')
const identifier = params.get('identifier')
const totpID = params.get('totpID')
const requestID = params.get('requestID')
if (status === 'success') {
if (totpID) {
onTOTPSetupComplete(identifier, totpID)
} else if (requestID) {
onTOTPAuthComplete(identifier, requestID)
}
} else {
onTOTPError(status, params.get('reason'))
}
}
}
const onTOTPSetupComplete = (identifier, totpID) => {
// Handle successful setup
console.log('TOTP setup completed:', { identifier, totpID })
// Update app state, show success message, navigate to settings
}
const onTOTPAuthComplete = (identifier, requestID) => {
// Handle successful authentication
console.log('TOTP auth completed:', { identifier, requestID })
// Complete login, navigate to main app
}
const onTOTPError = (status, reason) => {
// Handle errors
console.log('TOTP error:', { status, reason })
// Show error message, return to previous screen
}
return {
setupTOTP,
authenticateWithTOTP,
}
}
// Usage in component
function SettingsScreen() {
const { setupTOTP } = useTOTPIntegration()
const userEmail = 'user@example.com' // Get from context/state
return (
<View>
<Button
title="Enable Two-Factor Authentication"
onPress={() => setupTOTP(userEmail)}
/>
</View>
)
}
Mobile Deep Link URL Schemes
Configure your mobile app's URL schemes for deep link returns:
iOS (Info.plist):
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.yourapp.totp</string>
<key>CFBundleURLSchemes</key>
<array>
<string>yourapp</string>
</array>
</dict>
</array>
Android (AndroidManifest.xml):
<activity android:name=".MainActivity">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="yourapp" />
</intent-filter>
</activity>
Error Handling
Hosted integration automatically handles most error scenarios, but you should implement proper callback handling for edge cases.
Common Error Scenarios
Error Handling Implementation
// Error handling for hosted TOTP integration
class TOTPErrorHandler {
static handleCallback(url) {
const params = new URLSearchParams(url.split('?')[1])
const status = params.get('status')
const reason = params.get('reason')
const identifier = params.get('identifier')
switch (status) {
case 'success':
return this.handleSuccess(params)
case 'cancelled':
return this.handleCancellation(reason)
case 'expired':
return this.handleExpiration(identifier)
case 'error':
return this.handleError(reason, identifier)
default:
return this.handleUnknownStatus(status)
}
}
static handleSuccess(params) {
const totpID = params.get('totpID')
const requestID = params.get('requestID')
const identifier = params.get('identifier')
if (totpID) {
// Setup success
return {
type: 'setup_success',
message: 'Two-factor authentication has been set up successfully!',
action: () => this.updateTOTPStatus(identifier, true),
redirect: '/settings/security',
}
} else if (requestID) {
// Auth success
return {
type: 'auth_success',
message: 'Authentication successful!',
action: () => this.completeAuthentication(identifier),
redirect: this.getIntendedUrl() || '/dashboard',
}
}
return {
type: 'unknown_success',
message: 'Operation completed successfully.',
redirect: '/',
}
}
static handleCancellation(reason) {
const messages = {
user_cancelled: 'You cancelled the authentication process.',
browser_closed: 'The authentication window was closed.',
timeout: 'The authentication process timed out.',
}
return {
type: 'cancelled',
message: messages[reason] || 'Authentication was cancelled.',
action: () => this.showRetryOption(),
redirect: null, // Stay on current page
}
}
static handleExpiration(identifier) {
return {
type: 'expired',
message: 'The authentication request expired. Please try again.',
action: () => this.offerRetry(identifier),
redirect: null,
}
}
static handleError(reason, identifier) {
const errorMessages = {
totp_not_setup: {
message: 'Two-factor authentication is not set up for this account.',
action: () => this.redirectToSetup(identifier),
redirect: '/settings/security',
},
quota_exceeded: {
message: 'Account quota exceeded. Please contact support.',
action: () => this.contactSupport(),
redirect: '/support',
},
invalid_pipeline: {
message: 'Configuration error. Please contact support.',
action: () => this.logConfigError(),
redirect: '/support',
},
network_error: {
message:
'Network error occurred. Please check your connection and try again.',
action: () => this.showRetryOption(),
redirect: null,
},
}
const errorInfo = errorMessages[reason] || {
message: 'An unexpected error occurred. Please try again.',
action: () => console.log('Unknown error:', reason),
redirect: null,
}
return {
type: 'error',
...errorInfo,
}
}
static handleUnknownStatus(status) {
console.error('Unknown TOTP callback status:', status)
return {
type: 'unknown',
message: 'An unexpected response was received. Please try again.',
redirect: '/',
}
}
// Helper methods
static updateTOTPStatus(identifier, enabled) {
// Update user's TOTP status in your system
fetch('/api/users/totp-status', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ identifier, enabled }),
})
}
static completeAuthentication(identifier) {
// Complete login process
sessionStorage.setItem('authenticated', 'true')
sessionStorage.setItem('user_email', identifier)
}
static getIntendedUrl() {
return sessionStorage.getItem('intended_url')
}
static showRetryOption() {
// Show UI element allowing user to retry
const retryBtn = document.getElementById('retry-totp')
if (retryBtn) {
retryBtn.style.display = 'block'
}
}
static offerRetry(identifier) {
if (
confirm(
'The authentication request expired. Would you like to try again?',
)
) {
// Restart authentication process
window.location.href = `/totp/auth?email=${encodeURIComponent(identifier)}`
}
}
static redirectToSetup(identifier) {
window.location.href = `/totp/setup?email=${encodeURIComponent(identifier)}`
}
static contactSupport() {
window.location.href = '/support'
}
static logConfigError() {
console.error('TOTP configuration error - invalid pipeline')
// Send error report to your logging service
}
}
// Usage
document.addEventListener('DOMContentLoaded', () => {
if (window.location.search.includes('status=')) {
const result = TOTPErrorHandler.handleCallback(window.location.href)
// Execute action if provided
if (result.action) {
result.action()
}
// Show message to user
if (result.message) {
showMessage(result.message, result.type)
}
// Redirect if specified
if (result.redirect) {
setTimeout(() => {
window.location.href = result.redirect
}, 2000)
}
}
})
function showMessage(message, type) {
const messageDiv = document.createElement('div')
messageDiv.className = `message ${type}`
messageDiv.textContent = message
document.body.insertBefore(messageDiv, document.body.firstChild)
// Auto-remove after 5 seconds
setTimeout(() => messageDiv.remove(), 5000)
}
Rate Limiting and Quota Handling
Hosted pages automatically handle rate limiting and quota exhaustion, but you should be prepared for these scenarios in your callbacks.
Automatic Handling: Hosted pages display appropriate error messages and countdowns for rate limiting. Users see clear instructions about waiting periods and retry guidance.
Testing & Go Live
Testing Your Integration
Testing Implementation
// Comprehensive testing for hosted TOTP integration
class TOTPIntegrationTester {
constructor(pipelineId, apiKey) {
this.pipelineId = pipelineId
this.apiKey = apiKey
this.testResults = []
}
async runAllTests() {
console.log('🧪 Starting TOTP Hosted Integration Tests...\n')
await this.testPipelineConfiguration()
await this.testSetupFlow()
await this.testAuthenticationFlow()
await this.testCallbackHandling()
await this.testErrorScenarios()
await this.testMobileIntegration()
this.printResults()
}
async testPipelineConfiguration() {
console.log('Testing Pipeline Configuration...')
try {
// Test pipeline exists and is accessible
const testURL = `https://app.akedly.io/totp/setup?pipeline=${this.pipelineId}&identifier=test@example.com`
const response = await fetch(testURL, { method: 'HEAD' })
if (response.status === 200) {
this.testResults.push({
test: 'Pipeline Configuration',
status: 'PASS',
})
} else if (response.status === 404) {
this.testResults.push({
test: 'Pipeline Configuration',
status: 'FAIL',
error: 'Pipeline not found',
})
} else {
this.testResults.push({
test: 'Pipeline Configuration',
status: 'FAIL',
error: `HTTP ${response.status}`,
})
}
} catch (error) {
this.testResults.push({
test: 'Pipeline Configuration',
status: 'ERROR',
error: error.message,
})
}
}
async testSetupFlow() {
console.log('Testing Setup Flow...')
try {
// Test setup URL generation
const setupURL = `https://app.akedly.io/totp/setup?pipeline=${this.pipelineId}&identifier=test-setup@example.com`
if (
setupURL.includes(this.pipelineId) &&
setupURL.includes('test-setup@example.com')
) {
this.testResults.push({ test: 'Setup URL Generation', status: 'PASS' })
} else {
this.testResults.push({ test: 'Setup URL Generation', status: 'FAIL' })
}
// Test callback URL handling
const callbackURL =
'https://yourapp.com/totp-callback?status=success&identifier=test-setup@example.com&totpID=test123'
const result = this.simulateCallback(callbackURL)
if (result.type === 'setup_success') {
this.testResults.push({
test: 'Setup Callback Handling',
status: 'PASS',
})
} else {
this.testResults.push({
test: 'Setup Callback Handling',
status: 'FAIL',
})
}
} catch (error) {
this.testResults.push({
test: 'Setup Flow',
status: 'ERROR',
error: error.message,
})
}
}
async testAuthenticationFlow() {
console.log('Testing Authentication Flow...')
try {
// Test auth request creation
const response = await fetch(
'https://api.akedly.io/api/v1/totp/request-auth',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
APIKey: this.apiKey,
identifier: 'test-auth@example.com',
pipelineID: this.pipelineId,
}),
},
)
const data = await response.json()
if (data.status === 'success' && data.data.authRequestID) {
this.testResults.push({ test: 'Auth Request Creation', status: 'PASS' })
// Test auth URL generation
const authURL = `https://app.akedly.io/totp/request?requestID=${data.data.authRequestID}`
if (authURL.includes(data.data.authRequestID)) {
this.testResults.push({ test: 'Auth URL Generation', status: 'PASS' })
} else {
this.testResults.push({ test: 'Auth URL Generation', status: 'FAIL' })
}
} else {
this.testResults.push({
test: 'Auth Request Creation',
status: 'FAIL',
error: data.message,
})
}
} catch (error) {
this.testResults.push({
test: 'Authentication Flow',
status: 'ERROR',
error: error.message,
})
}
}
async testCallbackHandling() {
console.log('Testing Callback Handling...')
const testCases = [
{
name: 'Success Setup Callback',
url: 'https://yourapp.com/callback?status=success&identifier=user@example.com&totpID=abc123',
expectedType: 'setup_success',
},
{
name: 'Success Auth Callback',
url: 'https://yourapp.com/callback?status=success&identifier=user@example.com&requestID=req123',
expectedType: 'auth_success',
},
{
name: 'Cancelled Callback',
url: 'https://yourapp.com/callback?status=cancelled&reason=user_cancelled',
expectedType: 'cancelled',
},
{
name: 'Expired Callback',
url: 'https://yourapp.com/callback?status=expired&identifier=user@example.com',
expectedType: 'expired',
},
{
name: 'Error Callback',
url: 'https://yourapp.com/callback?status=error&reason=quota_exceeded',
expectedType: 'error',
},
]
testCases.forEach((testCase) => {
try {
const result = this.simulateCallback(testCase.url)
if (result.type === testCase.expectedType) {
this.testResults.push({ test: testCase.name, status: 'PASS' })
} else {
this.testResults.push({
test: testCase.name,
status: 'FAIL',
error: `Expected ${testCase.expectedType}, got ${result.type}`,
})
}
} catch (error) {
this.testResults.push({
test: testCase.name,
status: 'ERROR',
error: error.message,
})
}
})
}
async testErrorScenarios() {
console.log('Testing Error Scenarios...')
// Test invalid pipeline ID
const invalidURL =
'https://app.akedly.io/totp/setup?pipeline=invalid&identifier=test@example.com'
try {
const response = await fetch(invalidURL, { method: 'HEAD' })
if (response.status === 404 || response.status === 400) {
this.testResults.push({
test: 'Invalid Pipeline Handling',
status: 'PASS',
})
} else {
this.testResults.push({
test: 'Invalid Pipeline Handling',
status: 'FAIL',
})
}
} catch (error) {
this.testResults.push({
test: 'Invalid Pipeline Handling',
status: 'ERROR',
error: error.message,
})
}
}
async testMobileIntegration() {
console.log('Testing Mobile Integration...')
// Test mobile redirect URL format
const mobileCallbackURL =
'myapp://totp-complete?status=success&identifier=user@example.com&totpID=abc123'
try {
const result = this.simulateCallback(mobileCallbackURL)
if (result.type === 'setup_success') {
this.testResults.push({
test: 'Mobile Deep Link Callback',
status: 'PASS',
})
} else {
this.testResults.push({
test: 'Mobile Deep Link Callback',
status: 'FAIL',
})
}
} catch (error) {
this.testResults.push({
test: 'Mobile Deep Link Callback',
status: 'ERROR',
error: error.message,
})
}
}
simulateCallback(url) {
// Simulate TOTPErrorHandler.handleCallback for testing
const params = new URLSearchParams(url.split('?')[1])
const status = params.get('status')
const totpID = params.get('totpID')
const requestID = params.get('requestID')
if (status === 'success') {
if (totpID) {
return { type: 'setup_success' }
} else if (requestID) {
return { type: 'auth_success' }
}
}
return { type: status || 'unknown' }
}
printResults() {
console.log('\n📊 Test Results:')
console.log('==================')
this.testResults.forEach((result) => {
const emoji =
result.status === 'PASS' ? '✅' : result.status === 'FAIL' ? '❌' : '⚠️'
console.log(`${emoji} ${result.test}: ${result.status}`)
if (result.error) {
console.log(` Error: ${result.error}`)
}
})
const passCount = this.testResults.filter((r) => r.status === 'PASS').length
const totalCount = this.testResults.length
console.log(`\nResults: ${passCount}/${totalCount} tests passed`)
if (passCount === totalCount) {
console.log('🎉 All tests passed! Ready for production.')
} else {
console.log('⚠️ Some tests failed. Please review before going live.')
}
}
}
// Run tests
const tester = new TOTPIntegrationTester('your_pipeline_id', 'your_api_key')
await tester.runAllTests()
Testing Checklist
Setup Flow Testing:
- Pipeline configuration loads correctly
- Setup URLs generate with correct parameters
- Hosted setup page displays with your branding
- QR codes work with different authenticator apps
- Deep links open authenticator apps on mobile
- Manual setup keys work as fallback
- Success callbacks return to your app correctly
Authentication Flow Testing:
- Auth requests create successfully
- Hosted auth page displays with your branding
- Valid TOTP codes authenticate successfully
- Invalid codes show appropriate error messages
- Expired requests are handled correctly
- Success callbacks return to your app correctly
Error Scenario Testing:
- Invalid pipeline IDs are handled
- Network errors show appropriate messages
- Rate limiting displays countdown timers
- Quota exhaustion shows support contact info
- User cancellation returns to your app
Mobile Testing:
- Deep links are configured correctly
- Mobile detection works on hosted pages
- External browser flow works
- Deep link returns work
- WebView integration works (if using)
Go Live Checklist
Configuration:
- Switch from Test to Live pipeline type
- Configure production callback URLs
- Test webhook endpoints in production environment
Monitoring:
- Set up error logging for callbacks
- Monitor authentication success rates
- Set up alerts for high failure rates
- Track user adoption metrics
Documentation:
- Document integration for your team
- Provide user help documentation
- Set up support processes for TOTP issues
Summary
Hosted integration provides the fastest path to implementing TOTP authentication with zero UI development required. By leveraging Akedly's hosted pages, you get:
✅ Complete white-label experience with your company branding
✅ Cross-platform compatibility with automatic mobile/desktop detection
✅ Comprehensive error handling for all edge cases
✅ Webhook integration for real-time server notifications
✅ Mobile app support with deep links and external browser flow
The hosted approach handles all the complex UI states, device detection, error scenarios, and security considerations automatically, allowing you to focus on your core application logic.