Skip to main content

Overview

Query parameter authentication is a simpler approach that passes authentication data through the URL. This method is ideal for quick implementation and debugging.
Query parameters are visible in URLs and browser history. While the nonce token is short-lived, consider using postMessage for production environments.

How It Works

1

Generate Nonce Token

Your backend calls the Doshi API to get a nonce token
2

Build URL

Construct the webview URL with the token and other data as query parameters
3

Load Webview

Load the iframe with the constructed URL
4

Webview Authenticates

Webview extracts parameters and authenticates the user, handling 2FA if needed

React Implementation

Complete Example with API Call

import React, { useEffect, useState, useMemo } from "react";

interface AuthData {
  token: string;
  email?: string;
  segment?: string;
  branchId?: string;
  is2FaEnabled?: boolean;
  dob?: string;
  organizationId?: string;
  partnerUserId?: string;
  firstName?: string;
  lastName?: string;
}

interface Props {
  userEmail: string;
  segment?: string;
  branchId?: string;
  // 2FA props
  is2FaEnabled?: boolean;
  dob?: string;
  organizationId?: string;
  partnerUserId?: string;
  firstName?: string;
  lastName?: string;
}

const WebviewWithQueryParams: React.FC<Props> = ({ 
  userEmail,
  segment,
  branchId,
  is2FaEnabled = false,
  dob,
  organizationId,
  partnerUserId,
  firstName,
  lastName
}) => {
  const [authToken, setAuthToken] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  // Step 1: Get nonce token from your backend
  useEffect(() => {
    async function fetchToken() {
      try {
        // Call YOUR backend which securely calls Doshi API
        const response = await fetch('/api/generate-doshi-token', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            email: userEmail,
            branchId
          })
        });

        if (!response.ok) {
          throw new Error('Failed to generate token');
        }

        const data = await response.json();
        setAuthToken(data.token);
      } catch (error) {
        console.error('Failed to fetch auth token:', error);
        setError('Authentication failed. Please try again.');
      } finally {
        setIsLoading(false);
      }
    }

    fetchToken();
  }, [userEmail, branchId]);

  // Step 2: Build URL with token and params
  const webviewUrl = useMemo(() => {
    if (!authToken) return null;

    const baseUrl = "https://embed.doshi.app";
    const params = new URLSearchParams();
    
    // Required
    params.append('token', authToken);
    
    // Optional parameters
    if (userEmail) params.append('email', userEmail);
    if (segment) params.append('segment', segment);
    if (branchId) params.append('branchId', branchId);

    // 2FA parameters
    if (is2FaEnabled) {
      params.append('is2FaEnabled', 'true');
      if (dob) params.append('dob', dob);
      if (organizationId) params.append('organizationId', organizationId);
      if (partnerUserId) params.append('partnerUserId', partnerUserId);
      if (firstName) params.append('firstName', firstName);
      if (lastName) params.append('lastName', lastName);
    }
    
    return `${baseUrl}?${params.toString()}`;
  }, [
    authToken, 
    userEmail, 
    segment, 
    branchId, 
    is2FaEnabled,
    dob,
    organizationId,
    partnerUserId,
    firstName,
    lastName
  ]);

  if (isLoading) {
    return (
      <div className="flex items-center justify-center h-screen">
        <div className="text-center">
          <div className="spinner mb-4"></div>
          <p>Loading...</p>
        </div>
      </div>
    );
  }

  if (error) {
    return (
      <div className="flex items-center justify-center h-screen">
        <div className="text-center text-red-600">
          <p>{error}</p>
          <button 
            onClick={() => window.location.reload()}
            className="mt-4 px-4 py-2 bg-blue-600 text-white rounded"
          >
            Retry
          </button>
        </div>
      </div>
    );
  }

  if (!webviewUrl) {
    return <div>Failed to authenticate</div>;
  }

  return (
    <div className="webview-container h-screen w-full">
      <iframe
        src={webviewUrl}
        className="h-full w-full"
        frameBorder="0"
        allowFullScreen
      />
    </div>
  );
};

export default WebviewWithQueryParams;

Usage Example

import WebviewWithQueryParams from './WebviewWithQueryParams';

function App() {
  return (
    <div className="app">
      <h1>My Application</h1>
      <WebviewWithQueryParams 
        userEmail="[email protected]"
        segment="premium"
        branchId="branch_789"
      />
    </div>
  );
}

// With 2FA enabled
function AppWith2FA() {
  return (
    <div className="app">
      <h1>My Application</h1>
      <WebviewWithQueryParams 
        userEmail="[email protected]"
        segment="premium"
        branchId="branch_789"
        is2FaEnabled={true}
        dob="1990-01-15"
        organizationId="org_123"
        partnerUserId="partner_123"
        firstName="John"
        lastName="Doe"
      />
    </div>
  );
}

Vanilla JavaScript Implementation

Parent Window

// Configuration
const config = {
  doshiEmbedUrl: 'https://embed.doshi.app',
  backendUrl: '/api/generate-doshi-token'
};

// User data
const userData = {
  email: "[email protected]",
  segment: "premium",
  branchId: "branch_789"
};

// 2FA data (optional)
const twoFactorData = {
  is2FaEnabled: true,
  dob: "1990-01-15",
  organizationId: "org_123",
  partnerUserId: "partner_123",
  firstName: "John",
  lastName: "Doe"
};

// Step 1: Get nonce token from your backend
async function fetchAuthToken() {
  try {
    const response = await fetch(config.backendUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        email: userData.email,
        branchId: userData.branchId
      })
    });

    if (!response.ok) {
      throw new Error('Failed to generate token');
    }

    const data = await response.json();
    return data.token;
  } catch (error) {
    console.error('Failed to fetch auth token:', error);
    showError('Authentication failed. Please try again.');
    return null;
  }
}

// Step 2: Build URL with query parameters
function buildWebviewUrl(token, userData, twoFactorData) {
  const params = new URLSearchParams();
  
  // Required
  params.append('token', token);
  
  // Optional parameters
  if (userData.email) params.append('email', userData.email);
  if (userData.segment) params.append('segment', userData.segment);
  if (userData.branchId) params.append('branchId', userData.branchId);

  // 2FA parameters
  if (twoFactorData.is2FaEnabled) {
    params.append('is2FaEnabled', 'true');
    if (twoFactorData.dob) params.append('dob', twoFactorData.dob);
    if (twoFactorData.organizationId) {
      params.append('organizationId', twoFactorData.organizationId);
    }
    if (twoFactorData.partnerUserId) {
      params.append('partnerUserId', twoFactorData.partnerUserId);
    }
    if (twoFactorData.firstName) {
      params.append('firstName', twoFactorData.firstName);
    }
    if (twoFactorData.lastName) {
      params.append('lastName', twoFactorData.lastName);
    }
  }
  
  return `${config.doshiEmbedUrl}?${params.toString()}`;
}

// Step 3: Create and add webview to the page
async function createDoshiEmbed() {
  // Show loading state
  showLoading();

  // Get token
  const token = await fetchAuthToken();
  if (!token) return;

  // Build URL
  const webviewUrl = buildWebviewUrl(token, userData, twoFactorData);

  // Create iframe
  const webview = document.createElement("iframe");
  webview.src = webviewUrl;
  webview.className = "h-full w-full";
  webview.frameBorder = "0";
  webview.allowFullscreen = true;

  // Add load event listener
  webview.addEventListener('load', () => {
    console.log('Doshi embed loaded successfully');
    hideLoading();
  });

  webview.addEventListener('error', () => {
    console.error('Failed to load Doshi embed');
    showError('Failed to load authentication. Please try again.');
  });

  // Add to page
  const container = document.querySelector("#doshi-container");
  container.innerHTML = ''; // Clear any existing content
  container.appendChild(webview);
}

// UI Helper functions
function showLoading() {
  const container = document.querySelector("#doshi-container");
  container.innerHTML = `
    <div class="loading-state">
      <div class="spinner"></div>
      <p>Loading...</p>
    </div>
  `;
}

function hideLoading() {
  // Loading is automatically hidden when iframe loads
}

function showError(message) {
  const container = document.querySelector("#doshi-container");
  container.innerHTML = `
    <div class="error-message">
      <p>${message}</p>
      <button onclick="createDoshiEmbed()">Retry</button>
    </div>
  `;
}

// Initialize
createDoshiEmbed();

HTML Structure

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Doshi Integration</title>
  <style>
    body {
      margin: 0;
      padding: 0;
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    }

    #doshi-container {
      width: 100%;
      height: 100vh;
      position: relative;
    }
    
    .loading-state,
    .error-message {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      height: 100vh;
      text-align: center;
    }

    .spinner {
      width: 40px;
      height: 40px;
      border: 4px solid #f3f3f3;
      border-top: 4px solid #3b82f6;
      border-radius: 50%;
      animation: spin 1s linear infinite;
      margin-bottom: 1rem;
    }

    @keyframes spin {
      0% { transform: rotate(0deg); }
      100% { transform: rotate(360deg); }
    }
    
    .error-message {
      color: #dc2626;
    }

    .error-message button,
    .loading-state button {
      margin-top: 1rem;
      padding: 0.5rem 1rem;
      background-color: #3b82f6;
      color: white;
      border: none;
      border-radius: 0.375rem;
      cursor: pointer;
      font-size: 1rem;
    }

    .error-message button:hover,
    .loading-state button:hover {
      background-color: #2563eb;
    }
  </style>
</head>
<body>
  <div id="doshi-container"></div>
  <script src="doshi-integration.js"></script>
</body>
</html>

Child Window (Webview) - Reading Query Parameters

This section is for reference only. The Doshi iframe already implements this functionality.

Reading Parameters

// Inside the webview, extract query parameters
function getAuthDataFromUrl() {
  const params = new URLSearchParams(window.location.search);
  
  return {
    token: params.get('token') || '',
    email: params.get('email') || '',
    segment: params.get('segment') || '',
    branchId: params.get('branchId') || '',
    // 2FA parameters
    is2FaEnabled: params.get('is2FaEnabled') === 'true',
    dob: params.get('dob') || '',
    organizationId: params.get('organizationId') || '',
    partnerUserId: params.get('partnerUserId') || '',
    firstName: params.get('firstName') || '',
    lastName: params.get('lastName') || ''
  };
}

// Use the auth data
const authData = getAuthDataFromUrl();
console.log('Authenticated user:', authData.email);

// Validate required data
if (!authData.token) {
  console.error('Missing required authentication token');
  showError('Authentication token missing');
}

// If 2FA is enabled, show OTP screen
if (authData.is2FaEnabled) {
  handle2FAFlow(authData);
}

Clearing Sensitive Parameters

Clear sensitive parameters from the URL after reading to prevent exposure in browser history
// Clear sensitive params from URL after reading
function clearUrlParams() {
  if (window.history.replaceState) {
    const url = new URL(window.location.href);
    url.search = ''; // Clear all query params
    window.history.replaceState({}, document.title, url.toString());
  }
}

// Extract auth data
const authData = getAuthDataFromUrl();

// Store in memory
window.authData = authData;

// Clear URL (optional, for security)
clearUrlParams();

URL Encoding

Properly encode special characters in parameters:
function buildSecureUrl(baseUrl, data) {
  const params = new URLSearchParams();
  
  Object.entries(data).forEach(([key, value]) => {
    if (value !== null && value !== undefined && value !== '') {
      // URLSearchParams automatically encodes values
      params.append(key, String(value));
    }
  });
  
  const url = `${baseUrl}?${params.toString()}`;
  
  // Check URL length
  if (url.length > 2000) {
    console.warn('URL exceeds recommended length. Consider using postMessage.');
  }
  
  return url;
}

URL Parameter Reference

Required Parameters

token
string
required
The nonce token received from the Doshi API

Optional Parameters

email
string
User’s email address
segment
string
Used for handling multiple learning paths under the same organization
branchId
string
Branch or location identifier

2FA Parameters

is2FaEnabled
boolean
Set to true to enable 2FA flow in the iframe
dob
string
User’s date of birth in YYYY-MM-DD format (required for 2FA)
organizationId
string
Your organization ID (required for 2FA)
partnerUserId
string
Your internal user ID (required for 2FA)
firstName
string
User’s first name (required for 2FA)
lastName
string
User’s last name (required for 2FA)

Comparison: postMessage vs Query Parameters

FeatureQuery ParameterspostMessage
SecurityVisible in URLsHidden from URLs
ComplexitySimpleModerate
Real-timeNoYes
URL LengthLimited (~2000 chars)No limit
DebuggingEasy (visible in URL)Requires dev tools
2FA SupportYesYes
Best ForSimple auth, quick setupSensitive data, production apps

Common Issues

Problem: URL exceeds browser limits (typically 2000 characters).Solutions:
  1. Use shorter tokens or references
  2. Switch to postMessage method
  3. Only include essential parameters
Problem: Special characters in parameters causing issues.Solution:
    // Use URLSearchParams for automatic encoding
    const params = new URLSearchParams();
    params.append('email', '[email protected]'); // Properly encoded
    params.append('name', 'John & Jane'); // Properly encoded
Problem: Query parameters not accessible in the webview.Solution:
    // Debug parameter reading
    console.log('Current URL:', window.location.href);
    console.log('Search params:', window.location.search);
    
    const params = new URLSearchParams(window.location.search);
    console.log('All params:', Object.fromEntries(params));
    
    // Check for specific param
    const token = params.get('token');
    if (!token) {
      console.error('Token parameter missing from URL');
    }

Next Steps