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
Generate Nonce Token
Your backend calls the Doshi API to get a nonce token
Build URL
Construct the webview URL with the token and other data as query parameters
Load Webview
Load the iframe with the constructed URL
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 : 100 vh ;
position : relative ;
}
.loading-state ,
.error-message {
display : flex ;
flex-direction : column ;
align-items : center ;
justify-content : center ;
height : 100 vh ;
text-align : center ;
}
.spinner {
width : 40 px ;
height : 40 px ;
border : 4 px solid #f3f3f3 ;
border-top : 4 px solid #3b82f6 ;
border-radius : 50 % ;
animation : spin 1 s linear infinite ;
margin-bottom : 1 rem ;
}
@keyframes spin {
0% { transform : rotate ( 0 deg ); }
100% { transform : rotate ( 360 deg ); }
}
.error-message {
color : #dc2626 ;
}
.error-message button ,
.loading-state button {
margin-top : 1 rem ;
padding : 0.5 rem 1 rem ;
background-color : #3b82f6 ;
color : white ;
border : none ;
border-radius : 0.375 rem ;
cursor : pointer ;
font-size : 1 rem ;
}
.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
The nonce token received from the Doshi API
Optional Parameters
Used for handling multiple learning paths under the same organization
Branch or location identifier
2FA Parameters
Set to true to enable 2FA flow in the iframe
User’s date of birth in YYYY-MM-DD format (required for 2FA)
Your organization ID (required for 2FA)
Your internal user ID (required for 2FA)
User’s first name (required for 2FA)
User’s last name (required for 2FA)
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
Problem: URL exceeds browser limits (typically 2000 characters).Solutions:
Use shorter tokens or references
Switch to postMessage method
Only include essential parameters
Special characters not encoding properly
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
Parameters not being read
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