Glide Phone Authentication SDK for web applications
npm install @glideidentity/glide-fe-sdk-webThe official web SDK for integrating Glide's carrier-grade phone verification into your web applications.
- 🚀 Instant Verification - Direct carrier verification without SMS delays
- 🔐 Fraud Resistant - Can't be intercepted or spoofed like SMS codes
- 📱 Multi-Platform - Desktop QR codes, iOS App Clips, Android deep links
- 🎨 Customizable UI - Built-in modal with themes and view modes
- 🔧 Flexible Architecture - Use the full SDK or just the core types
- 🌐 Framework Support - React, Vue, Angular, and vanilla JavaScript
- 📦 Tree-Shakeable - Import only what you need
``bash`
npm install @glideidentity/glide-fe-sdk-web
The simplest way to integrate phone verification:
`javascript
import { PhoneAuthClient, USE_CASE } from '@glideidentity/glide-fe-sdk-web';
const client = new PhoneAuthClient({
endpoints: {
prepare: '/api/magical-auth/prepare',
reportInvocation: '/api/magical-auth/report-invocation',
process: '/api/magical-auth/process',
}
});
// Get a phone number
const result = await client.authenticate({
use_case: USE_CASE.GET_PHONE_NUMBER
});
console.log('Phone:', result.phone_number);
// Or verify a specific number
const verified = await client.authenticate({
use_case: USE_CASE.VERIFY_PHONE_NUMBER,
phone_number: '+14155551234'
});
console.log('Verified:', verified.verified);
`
For more control over the authentication flow:
`javascript
import { PhoneAuthClient, USE_CASE } from '@glideidentity/glide-fe-sdk-web';
const client = new PhoneAuthClient({
endpoints: {
prepare: '/api/magical-auth/prepare',
reportInvocation: '/api/magical-auth/report-invocation',
process: '/api/magical-auth/process',
}
});
// Step 1: Prepare the request
const prepared = await client.prepare({
use_case: USE_CASE.VERIFY_PHONE_NUMBER,
phone_number: '+14155551234'
});
// Step 2: Invoke authentication (shows UI based on strategy)
const invokeResult = await client.invokeSecurePrompt(prepared);
const credential = await invokeResult.credential;
// Step 3: Process the credential
const result = await client.verifyPhoneNumber(credential, invokeResult.session);
console.log('Verified:', result.verified);
`
`typescript`
const client = new PhoneAuthClient({
// Backend endpoints (with defaults shown)
endpoints: {
prepare: '/api/magical-auth/prepare', // Prepare request endpoint
process: '/api/magical-auth/process', // Process credential endpoint
reportInvocation: '/api/magical-auth/report-invocation', // ASR tracking endpoint
polling: undefined, // Optional: Status polling endpoint
},
// Polling configuration
pollingInterval: 2000, // Polling interval in ms (default: 2000)
maxPollingAttempts: 30, // Max attempts before timeout (default: 30)
timeout: 30000, // API request timeout in ms (default: 30000)
// Debug options
debug: false, // Enable console logging (default: false)
devtools: {
showMobileConsole: false // Show on-screen console on mobile (default: false)
},
// Dev environment routing (e.g., 'dev7' adds 'developer' header)
devEnv: undefined,
});
Customize the authentication UI and behavior:
`typescript`
const result = await client.invokeSecurePrompt(prepared, {
// Prevent SDK from showing any UI (use your own)
preventDefaultUI: false,
// Polling configuration (overrides client config)
pollingInterval: 2000,
maxPollingAttempts: 30,
// Modal customization (for desktop QR strategy)
modalOptions: {
theme: 'auto', // 'dark' | 'light' | 'auto'
viewMode: 'toggle', // 'toggle' | 'dual' | 'pre-step'
title: 'Scan to Verify', // Custom title text
description: '', // Optional subtitle
showCloseButton: true, // Show X button
closeOnBackdropClick: true, // Close on outside click
closeOnEscape: true, // Close on Escape key
}
});
The SDK provides a built-in modal for desktop QR code display.
| Mode | Description | Best For |
|------|-------------|----------|
| toggle | Single QR with iOS/Android switch | Clean, minimal UI (default) |dual
| | Side-by-side QR codes | Users who may not know their OS |pre-step
| | OS selection screen first | Guided experience |
| Theme | Description |
|-------|-------------|
| auto | Follows system preference (default) |dark
| | Dark background with light text |light
| | Light background with dark text |
To use your own UI instead of the built-in modal:
`javascript
const result = await client.invokeSecurePrompt(prepared, {
preventDefaultUI: true
});
// For desktop strategy, display your own QR code
if (result.strategy === 'desktop') {
// Access QR data from the prepare response
const qrData = prepared.data;
displayYourQRCode(qrData.ios_qr_image, qrData.android_qr_image);
// Wait for credential
const credential = await result.credential;
// Or cancel if needed
result.cancel?.();
}
`
The SDK automatically selects the best strategy based on the device:
| Strategy | Platform | Description |
|----------|----------|-------------|
| ts43 | Android Chrome 128+ | Native Digital Credentials API |link
| | iOS / Android | App Clip (iOS) or carrier app (Android) |desktop
| | Desktop browsers | QR code scanned by mobile |
`javascript
const result = await client.invokeSecurePrompt(prepared);
switch (result.strategy) {
case 'ts43':
// Android: Credential returned directly
const credential = await result.credential;
break;
case 'link':
// iOS: Opens App Clip, polls for completion
// Android: Opens carrier's privileged app, polls for completion
const credential = await result.credential;
// Optionally cancel polling
result.cancel?.();
break;
case 'desktop':
// Desktop: Shows QR modal, polls for completion
const credential = await result.credential;
result.cancel?.();
break;
}
`
`jsx
import { usePhoneAuth, USE_CASE } from '@glideidentity/glide-fe-sdk-web/react';
function PhoneVerification() {
const { authenticate, isLoading, error, result } = usePhoneAuth({
endpoints: {
prepare: '/api/magical-auth/prepare',
reportInvocation: '/api/magical-auth/report-invocation',
process: '/api/magical-auth/process',
}
});
const handleVerify = async () => {
try {
const result = await authenticate({
use_case: USE_CASE.VERIFY_PHONE_NUMBER,
phone_number: '+14155551234'
});
console.log('Verified:', result.verified);
} catch (err) {
console.error('Failed:', err);
}
};
return (
);
}
`
`vue
`
`html`
Add custom headers to all SDK requests:
`javascript`
const client = new PhoneAuthClient({
endpoints: { ... },
headers: {
common: {
'X-Custom-Header': 'your-value'
}
}
});
Provide your own logger implementation:
`javascript`
const client = new PhoneAuthClient({
endpoints: { ... },
logger: {
debug: (msg, data) => console.debug('[SDK]', msg, data),
info: (msg, data) => console.info('[SDK]', msg, data),
warn: (msg, data) => console.warn('[SDK]', msg, data),
error: (msg, data) => console.error('[SDK]', msg, data),
}
});
Bring your own HTTP client for full control:
`javascript`
const client = new PhoneAuthClient({
endpoints: { ... },
httpClient: {
post: async (url, body, options) => {
const response = await yourHttpLib.post(url, body, options);
return response.data;
},
get: async (url, options) => {
const response = await yourHttpLib.get(url, options);
return response.data;
}
}
});
For advanced use cases, you can import just the types and validators without the full client:
`typescript
import {
// Types
type PrepareRequest,
type PrepareResponse,
type InvokeResult,
type SessionInfo,
// Constants
USE_CASE,
AUTHENTICATION_STRATEGY,
ERROR_CODES,
// Validators
validatePhoneNumber,
validatePlmn,
// Type Guards
isTS43Strategy,
isLinkStrategy,
isDesktopStrategy,
isAuthError,
} from '@glideidentity/glide-fe-sdk-web/core';
// Use validators
const { valid, error } = validatePhoneNumber('+14155551234');
// Use type guards
if (isDesktopStrategy(result)) {
// TypeScript knows result has desktop-specific properties
}
`
`javascript
import { ERROR_CODES, isAuthError } from '@glideidentity/glide-fe-sdk-web';
try {
await client.authenticate({ ... });
} catch (error) {
if (isAuthError(error)) {
switch (error.code) {
case ERROR_CODES.USER_CANCELLED:
// User closed the modal or cancelled
break;
case ERROR_CODES.TIMEOUT:
// Authentication timed out
break;
case ERROR_CODES.NETWORK_ERROR:
// Network request failed
break;
default:
// Handle other errors
console.error(error.message);
}
}
}
`
`typescript
type UseCase = 'GetPhoneNumber' | 'VerifyPhoneNumber';
// Or use the constant
USE_CASE.GET_PHONE_NUMBER // 'GetPhoneNumber'
USE_CASE.VERIFY_PHONE_NUMBER // 'VerifyPhoneNumber'
`
`typescript`
interface PrepareRequest {
use_case: UseCase;
phone_number?: string; // Required for VerifyPhoneNumber
parent_session_id?: string; // For cross-device flows
}
`typescript`
interface InvokeResult {
strategy: 'ts43' | 'link' | 'desktop';
session: SessionInfo;
credential: Promise
cancel?: () => void; // Available for link and desktop
invocationReport: Promise<{ success: boolean; error?: string }>;
}
The SDK automatically tracks authentication attempts for Authentication Success Rate (ASR) metrics. This is handled via the reportInvocation endpoint.
| Aspect | Description |
|--------|-------------|
| Fire-and-forget | Never blocks the main authentication flow |
| Non-critical | Auth succeeds even if reporting fails |
| Observable | Developers CAN check if it worked (optional) |
| No latency impact | User doesn't wait for this API call |
The reportInvocation endpoint is included in the default config:
`typescript`
const client = new PhoneAuthClient({
endpoints: {
prepare: '/api/magical-auth/prepare',
process: '/api/magical-auth/process',
reportInvocation: '/api/magical-auth/report-invocation', // ASR tracking (default)
}
});
If you want to monitor whether ASR tracking succeeded:
`typescript
const result = await client.invokeSecurePrompt(prepared);
// Main flow continues immediately
const credential = await result.credential;
// Optional: Check report status (doesn't block)
result.invocationReport?.then(({ success, error }) => {
if (!success) {
console.warn('ASR tracking failed:', error);
// Optionally send to your own analytics
}
});
`
Your backend should implement a simple pass-through endpoint:
`typescript`
// POST /api/magical-auth/report-invocation
app.post('/api/magical-auth/report-invocation', async (req, res) => {
const { session_id } = req.body;
try {
const result = await glide.magicalAuth.reportInvocation({ session_id });
res.json({ success: result.success });
} catch (error) {
// Always return HTTP 200 - never fail the auth flow
res.json({ success: false, error: error.message });
}
});
`typescript
interface GetPhoneNumberResponse {
phone_number: string;
aud?: string; // Audience from carrier
sim_swap?: SimSwapInfo; // SIM swap detection info
}
interface VerifyPhoneNumberResponse {
phone_number: string;
verified: boolean;
aud?: string; // Audience from carrier
sim_swap?: SimSwapInfo; // SIM swap detection info
}
`
The SDK returns SIM swap detection information when available from carriers. This helps identify potential fraud.
`typescript`
interface SimSwapInfo {
/* Whether the SIM swap check completed successfully /
checked: boolean;
/* Risk level based on SIM change recency /
risk_level?: 'RISK_LEVEL_HIGH' | 'RISK_LEVEL_MEDIUM' | 'RISK_LEVEL_LOW' | 'RISK_LEVEL_UNKNOWN';
/* Human-readable time since last SIM change (e.g., "0-4 hours", "more than 3 years") /
age_band?: string;
/* When the check was performed (RFC3339) /
checked_at?: string;
/* Reason for failure if checked=false /
reason?: 'timeout' | 'carrier_not_supported' | 'disabled' | 'error';
}
| Level | Description | Recommended Action |
|-------|-------------|-------------------|
| RISK_LEVEL_HIGH | SIM changed within 7 days | Block or require additional verification |RISK_LEVEL_MEDIUM
| | SIM changed 7-30 days ago | Consider step-up authentication |RISK_LEVEL_LOW
| | SIM changed 30+ days ago | Normal processing |RISK_LEVEL_UNKNOWN
| | Could not determine | Use default policy |
`typescript
import {
isHighRiskSimSwap,
isMediumOrHighRiskSimSwap,
wasSimSwapChecked,
getMinDaysSinceSimSwap,
} from '@glideidentity/glide-fe-sdk-web';
const result = await client.getPhoneNumber(credential, session);
// Check if SIM swap was checked
if (wasSimSwapChecked(result.sim_swap)) {
// Check risk level
if (isHighRiskSimSwap(result.sim_swap)) {
// High risk - recent SIM change
requireAdditionalVerification();
} else if (isMediumOrHighRiskSimSwap(result.sim_swap)) {
// Medium risk - consider step-up auth
showWarning();
}
// Get minimum days since SIM change
const minDays = getMinDaysSinceSimSwap(result.sim_swap);
console.log(SIM unchanged for at least ${minDays} days);``
}
| Browser | Strategy | Requirements |
|---------|----------|--------------|
| Chrome Android 128+ | TS43 | Digital Credentials API |
| Safari iOS | Link | App Clips support |
| Chrome/Edge/Firefox Desktop | Desktop | Any modern version |
- Documentation
- API Reference
MIT