> ## Documentation Index
> Fetch the complete documentation index at: https://docs.doshi.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Query Parameter Authentication

> Implement simple URL-based authentication using query parameters

## Overview

Query parameter authentication is a simpler approach that passes authentication data through the URL. This method is ideal for quick implementation and debugging.

<Warning>
  Query parameters are visible in URLs and browser history. While the custom token is short-lived, consider using postMessage for production environments.
</Warning>

## How It Works

<Steps>
  <Step title="Generate Custom Token">
    Your backend calls the Doshi API to get a custom token
  </Step>

  <Step title="Build URL">
    Construct the webview URL with the token and other data as query parameters
  </Step>

  <Step title="Load Webview">
    Load the iframe with the constructed URL
  </Step>

  <Step title="Webview Authenticates">
    Webview extracts parameters and authenticates the user, handling 2FA if needed
  </Step>
</Steps>

## React Implementation

### Complete Example with API Call

```tsx theme={null}
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.v2.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

```tsx theme={null}
import WebviewWithQueryParams from './WebviewWithQueryParams';

function App() {
  return (
    <div className="app">
      <h1>My Application</h1>
      <WebviewWithQueryParams 
        userEmail="user@example.com"
        segment="premium"
        branchId="branch_789"
      />
    </div>
  );
}

// With 2FA enabled
function AppWith2FA() {
  return (
    <div className="app">
      <h1>My Application</h1>
      <WebviewWithQueryParams 
        userEmail="user@example.com"
        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

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

// User data
const userData = {
  email: "user@example.com",
  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

```html theme={null}
<!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

<Note>
  This section is for reference only. The Doshi iframe already implements this functionality.
</Note>

### Reading Parameters

```javascript theme={null}
// 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

<Tip>
  Clear sensitive parameters from the URL after reading to prevent exposure in browser history
</Tip>

```javascript theme={null}
// 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:

```javascript theme={null}
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

<ParamField query="token" type="string" required>
  The nonce token received from the Doshi API
</ParamField>

### Optional Parameters

<ParamField query="email" type="string">
  User's email address
</ParamField>

<ParamField query="segment" type="string">
  Used for handling multiple learning paths under the same organization
</ParamField>

<ParamField query="branchId" type="string">
  Branch or location identifier
</ParamField>

<Warning>
  `pathId` is not a URL parameter. Adding it to the iframe URL has no effect — it
  is silently ignored. Set the starting path when you generate the token instead.
  See [Setting the Starting Learning
  Path](/webview/quickstart#setting-the-starting-learning-path).
</Warning>

### 2FA Parameters

<ParamField query="is2FaEnabled" type="boolean">
  Set to `true` to enable 2FA flow in the iframe
</ParamField>

<ParamField query="dob" type="string">
  User's date of birth in YYYY-MM-DD format (required for 2FA)
</ParamField>

<ParamField query="organizationId" type="string">
  Your organization ID (required for 2FA)
</ParamField>

<ParamField query="partnerUserId" type="string">
  Your internal user ID (required for 2FA)
</ParamField>

<ParamField query="firstName" type="string">
  User's first name (required for 2FA)
</ParamField>

<ParamField query="lastName" type="string">
  User's last name (required for 2FA)
</ParamField>

## Comparison: postMessage vs Query Parameters

| Feature         | Query Parameters         | postMessage                     |
| --------------- | ------------------------ | ------------------------------- |
| **Security**    | Visible in URLs          | Hidden from URLs                |
| **Complexity**  | Simple                   | Moderate                        |
| **Real-time**   | No                       | Yes                             |
| **URL Length**  | Limited (\~2000 chars)   | No limit                        |
| **Debugging**   | Easy (visible in URL)    | Requires dev tools              |
| **2FA Support** | Yes                      | Yes                             |
| **Best For**    | Simple auth, quick setup | Sensitive data, production apps |

## Common Issues

<AccordionGroup>
  <Accordion title="URL too long">
    **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
  </Accordion>

  <Accordion title="Special characters not encoding properly">
    **Problem:** Special characters in parameters causing issues.

    **Solution:**

    ```javascript theme={null}
        // Use URLSearchParams for automatic encoding
        const params = new URLSearchParams();
        params.append('email', 'user+test@example.com'); // Properly encoded
        params.append('name', 'John & Jane'); // Properly encoded
    ```
  </Accordion>

  <Accordion title="Parameters not being read">
    **Problem:** Query parameters not accessible in the webview.

    **Solution:**

    ```javascript theme={null}
        // 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');
        }
    ```
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Security Guidelines" icon="shield" href="/webview/security">
    Review security considerations
  </Card>

  <Card title="Best Practices" icon="star" href="/webview/best-practices">
    Optimize your implementation
  </Card>
</CardGroup>
