@glideidentity/glide-fe-sdk-web
v2.2.0
Published
Glide Phone Authentication SDK for web applications
Downloads
57
Readme
Glide Web Client SDK
The official web SDK for integrating Glide's carrier-grade phone verification into your web applications.
Features
- 🚀 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
Installation
npm install @glideidentity/glide-fe-sdk-webQuick Start
High-Level API (Recommended)
The simplest way to integrate phone verification:
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);Granular API
For more control over the authentication flow:
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);Configuration
Client Options
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,
});Invoke Options
Customize the authentication UI and behavior:
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
}
});Modal Customization
The SDK provides a built-in modal for desktop QR code display.
View Modes
| 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 |
Themes
| Theme | Description |
|-------|-------------|
| auto | Follows system preference (default) |
| dark | Dark background with light text |
| light | Light background with dark text |
Custom UI
To use your own UI instead of the built-in modal:
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?.();
}Authentication Strategies
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 |
Strategy-Specific Handling
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;
}Framework Integration
React
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 (
<button onClick={handleVerify} disabled={isLoading}>
{isLoading ? 'Verifying...' : 'Verify Phone'}
</button>
);
}Vue
<script setup>
import { usePhoneAuth, USE_CASE } from '@glideidentity/glide-fe-sdk-web/vue';
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 () => {
await authenticate({
use_case: USE_CASE.VERIFY_PHONE_NUMBER,
phone_number: '+14155551234'
});
};
</script>
<template>
<button @click="handleVerify" :disabled="isLoading">
{{ isLoading ? 'Verifying...' : 'Verify Phone' }}
</button>
</template>Vanilla JavaScript (Browser)
<script src="https://unpkg.com/@glideidentity/glide-fe-sdk-web/dist/browser/web-client-sdk.min.js"></script>
<script>
const { PhoneAuthClient, USE_CASE } = GlideWebClientSDK;
const client = new PhoneAuthClient({
endpoints: {
prepare: '/api/magical-auth/prepare',
reportInvocation: '/api/magical-auth/report-invocation',
process: '/api/magical-auth/process',
}
});
document.getElementById('verify-btn').onclick = async () => {
const result = await client.authenticate({
use_case: USE_CASE.VERIFY_PHONE_NUMBER,
phone_number: '+14155551234'
});
console.log('Verified:', result.verified);
};
</script>Advanced Features
Custom HTTP Headers
Add custom headers to all SDK requests:
const client = new PhoneAuthClient({
endpoints: { ... },
headers: {
common: {
'X-Custom-Header': 'your-value'
}
}
});Custom Logger
Provide your own logger implementation:
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),
}
});Custom HTTP Client
Bring your own HTTP client for full control:
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;
}
}
});Core Package
For advanced use cases, you can import just the types and validators without the full client:
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
}Error Handling
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);
}
}
}Type Reference
Use Cases
type UseCase = 'GetPhoneNumber' | 'VerifyPhoneNumber';
// Or use the constant
USE_CASE.GET_PHONE_NUMBER // 'GetPhoneNumber'
USE_CASE.VERIFY_PHONE_NUMBER // 'VerifyPhoneNumber'Prepare Request
interface PrepareRequest {
use_case: UseCase;
phone_number?: string; // Required for VerifyPhoneNumber
parent_session_id?: string; // For cross-device flows
}Invoke Result
interface InvokeResult {
strategy: 'ts43' | 'link' | 'desktop';
session: SessionInfo;
credential: Promise<string>; // Resolves to credential token
cancel?: () => void; // Available for link and desktop
invocationReport: Promise<{ success: boolean; error?: string }>;
}ASR Tracking (reportInvocation)
The SDK automatically tracks authentication attempts for Authentication Success Rate (ASR) metrics. This is handled via the reportInvocation endpoint.
Behavior
| 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 |
Configuration
The reportInvocation endpoint is included in the default config:
const client = new PhoneAuthClient({
endpoints: {
prepare: '/api/magical-auth/prepare',
process: '/api/magical-auth/process',
reportInvocation: '/api/magical-auth/report-invocation', // ASR tracking (default)
}
});Monitoring (Optional)
If you want to monitor whether ASR tracking succeeded:
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
}
});Backend Endpoint
Your backend should implement a simple pass-through endpoint:
// 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 });
}
});Responses
interface GetPhoneNumberResponse {
phone_number: string;
aud?: string; // Audience from carrier
sim_swap?: SimSwapInfo; // SIM swap detection info
device_swap?: DeviceSwapInfo; // Device swap (IMEI change) detection info
}
interface VerifyPhoneNumberResponse {
phone_number: string;
verified: boolean;
aud?: string; // Audience from carrier
sim_swap?: SimSwapInfo; // SIM swap detection info
device_swap?: DeviceSwapInfo; // Device swap (IMEI change) detection info
}SIM Swap Detection
The SDK returns SIM swap detection information when available from carriers. This helps identify potential fraud.
Response Fields
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';
}Risk Levels
| 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 |
Helper Functions
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`);
}Device Swap Detection
The SDK returns device swap (IMEI change) detection information when available from carriers. This is a parallel, independent signal alongside SIM swap detection. Not all carriers support this signal — the device_swap field is only present when the carrier provides it.
Response Fields
interface DeviceSwapInfo {
/** Whether the device swap check completed successfully */
checked: boolean;
/** Risk level based on device change recency */
risk_level?: 'RISK_LEVEL_HIGH' | 'RISK_LEVEL_MEDIUM' | 'RISK_LEVEL_LOW' | 'RISK_LEVEL_UNKNOWN';
/** Human-readable time since last device (IMEI) 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';
}Risk Levels
| Level | Description | Recommended Action |
|-------|-------------|-------------------|
| RISK_LEVEL_HIGH | Device changed within 7 days | Block or require additional verification |
| RISK_LEVEL_MEDIUM | Device changed 7-30 days ago | Consider step-up authentication |
| RISK_LEVEL_LOW | Device changed 30+ days ago | Normal processing |
| RISK_LEVEL_UNKNOWN | Could not determine | Use default policy |
Helper Functions
import {
isHighRiskDeviceSwap,
isMediumOrHighRiskDeviceSwap,
wasDeviceSwapChecked,
getMinDaysSinceDeviceSwap,
} from '@glideidentity/glide-fe-sdk-web';
const result = await client.getPhoneNumber(credential, session);
// Check if device swap was checked
if (wasDeviceSwapChecked(result)) {
// Check risk level
if (isHighRiskDeviceSwap(result)) {
// High risk - recent device change
requireAdditionalVerification();
} else if (isMediumOrHighRiskDeviceSwap(result)) {
// Medium risk - consider step-up auth
showWarning();
}
// Get minimum days since device change
const minDays = getMinDaysSinceDeviceSwap(result);
console.log(`Device unchanged for at least ${minDays} days`);
}Browser Support
| Browser | Strategy | Requirements | |---------|----------|--------------| | Chrome Android 128+ | TS43 | Digital Credentials API | | Safari iOS | Link | App Clips support | | Chrome/Edge/Firefox Desktop | Desktop | Any modern version |
Support
License
MIT
