Overview
Security is paramount when implementing webview authentication. This guide covers critical security considerations for both postMessage and query parameter methods.
IP Whitelisting
The Doshi API requires IP whitelisting. Contact [email protected] to whitelist your server IP addresses.
To get your server’s IP address:
# Run this command on your server
curl ifconfig.me
Send this IP address to [email protected] along with your organization name.
API Key Security
Never expose your API key in client-side code . Always call the Doshi API from your backend server.
Secure API Key Usage
// ❌ NEVER do this - API key exposed in frontend
const response = await fetch ( 'https://api.doshi.app/client/auth/token' , {
headers: {
'Authorization' : 'Bearer YOUR_API_KEY' // Exposed to users!
}
});
// ✅ Correct - Call your backend which securely stores the API key
const response = await fetch ( 'https://your-backend.com/api/generate-doshi-token' , {
method: 'POST' ,
body: JSON . stringify ({ userId: 'user_123' })
});
Backend Implementation Example
// Your backend server
app . post ( '/api/generate-doshi-token' , async ( req , res ) => {
// Verify the user is authenticated in your system
if ( ! req . session . userId ) {
return res . status ( 401 ). json ({ error: 'Unauthorized' });
}
// Call Doshi API with your API key (stored securely in environment variables)
const response = await fetch ( 'https://api.doshi.app/client/auth/token' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ process . env . DOSHI_API_KEY } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
email: req . user . email ,
partnerUserId: req . user . id
})
});
const data = await response . json ();
res . json ({ token: data . token });
});
Origin Verification
Always verify the origin of messages in production environments
For postMessage
window . addEventListener ( "message" , ( event ) => {
// ✅ Verify origin in production
if ( event . origin !== 'https://embed.doshi.app' ) {
console . warn ( 'Rejected message from unauthorized origin:' , event . origin );
return ;
}
// Process message
});
Specify Target Origins
// ❌ Never use in production
webview . contentWindow . postMessage ( data , "*" );
// ✅ Always specify exact origin in production
webview . contentWindow . postMessage ( data , "https://embed.doshi.app" );
Token Security
Token Lifecycle
The nonce token received from the API:
Is single-use for initial authentication
Has a short expiration time
Cannot be reused after the user session is established
// After getting the token, use it immediately
const { token } = await getTokenFromBackend ();
// Pass to iframe right away
passTokenToIframe ( token );
// Don't store the token for long periods
// Don't reuse the token for multiple sessions
Session Tokens
Once authenticated:
ID Token TTL : 1 hour
Refresh Token TTL : 12 hours
The iframe manages token refresh automatically
Sessions are isolated per iframe instance
Cross-Origin Communication
postMessage API Security
Always validate the structure and content of messages: try {
const data = typeof event . data === "string"
? JSON . parse ( event . data )
: event . data ;
// Validate message structure
if ( ! data . type || typeof data . type !== 'string' ) {
throw new Error ( 'Invalid message structure' );
}
// Validate expected message types
const validTypes = [ 'PING' , 'AUTH' , 'AUTH_SUCCESS' , 'AUTH_ERROR' ];
if ( ! validTypes . includes ( data . type )) {
throw new Error ( `Unexpected message type: ${ data . type } ` );
}
// Process valid message
} catch ( error ) {
console . error ( "Error processing message:" , error );
return ;
}
Implement comprehensive error handling: const handleMessage = ( event ) => {
try {
// Verify origin
if ( event . origin !== 'https://embed.doshi.app' ) return ;
// Parse and validate
const data = JSON . parse ( event . data );
// Process message
} catch ( error ) {
console . error ( 'Message handling failed:' , error );
// Log to monitoring service
logError ( error );
}
};
Always use HTTPS for secure communication: // ✅ Secure HTTPS URL
const webviewUrl = "https://embed.doshi.app" ;
// ❌ Never use HTTP in production
const insecureUrl = "http://embed.doshi.app" ;
Query Parameter Security
Query parameters are visible in URLs and browser history. The nonce token is short-lived but still sensitive.
Best Practices
Clear Parameters After Use
// After iframe loads, clear sensitive params
window . addEventListener ( 'load' , () => {
if ( window . history . replaceState ) {
const url = new URL ( window . location . href );
url . search = '' ; // Clear all query params
window . history . replaceState ({}, document . title , url . toString ());
}
});
Use Short-Lived Tokens
The nonce token from the API is already short-lived, but you can add additional validation:
// Backend - add timestamp to token generation
const tokenData = {
email: user . email ,
timestamp: Date . now ()
};
// Frontend - check token age before using
const isTokenFresh = ( timestamp : number ) => {
const maxAge = 5 * 60 * 1000 ; // 5 minutes
return Date . now () - timestamp < maxAge ;
};
Monitor Token Usage
// Log token usage for security monitoring
app . post ( '/api/generate-doshi-token' , async ( req , res ) => {
const token = await generateDoshiToken ( req . user );
// Log token generation
await logSecurityEvent ({
event: 'token_generated' ,
userId: req . user . id ,
timestamp: new Date (),
ip: req . ip
});
res . json ({ token });
});
Respect URL Length Limits
URLs should generally stay under 2000 characters for maximum browser compatibility
function buildWebviewUrl ( baseUrl , params ) {
const urlParams = new URLSearchParams ( params );
const fullUrl = ` ${ baseUrl } ? ${ urlParams . toString () } ` ;
// Check URL length
if ( fullUrl . length > 2000 ) {
console . warn ( 'URL exceeds recommended length. Consider using postMessage.' );
}
return fullUrl ;
}
2FA Security
When implementing 2FA:
// Only pass 2FA parameters when explicitly enabled
const authData = {
token: nonceToken ,
email: user . email ,
// Only include these if 2FA is actually enabled
... ( is2FaEnabled && {
is2FaEnabled: true ,
dob: user . dob ,
organizationId: org . id ,
partnerUserId: user . id ,
firstName: user . firstName ,
lastName: user . lastName
})
};
Don’t Send Unnecessary Data
// ❌ Don't send sensitive data if not needed
const authData = {
token: nonceToken ,
ssn: user . ssn , // Never send unless absolutely required
password: user . password // Never send passwords
};
// ✅ Only send required fields
const authData = {
token: nonceToken ,
email: user . email ,
segment: 'premium'
};
Security Checklist
Store API key securely in backend environment variables
Never expose API key in client-side code
Call Doshi API only from your backend server
Use HTTPS for all communication
Verify message origins in production
Never use ”*” wildcard for target origins in production
Validate all incoming messages and parameters
Clear sensitive data from URLs after reading
Implement comprehensive error handling
Log security events for monitoring
Test across different browsers and environments
Common Security Pitfalls
Pitfall Risk Solution API key in frontend Anyone can steal your key Call API from backend only Using "*" for postMessage target Any site can receive messages Specify exact origin Sensitive tokens in URLs Exposed in logs/history Use postMessage or clear params No origin verification Malicious sites can send messages Always check event.origin Missing input validation XSS vulnerabilities Validate all inputs Long-lived tokens Increased attack window Use short expiration times Storing tokens in localStorage XSS attacks can steal tokens Let iframe manage sessions
Environment-Specific Configuration
// config.ts
const config = {
development: {
doshiApiUrl: 'https://sandbox.api.doshi.app' ,
doshiEmbedUrl: 'https://staging-embed.doshi.app' ,
strictOriginCheck: false // For local testing
},
production: {
doshiApiUrl: 'https://api.doshi.app' ,
doshiEmbedUrl: 'https://embed.doshi.app' ,
strictOriginCheck: true
}
};
// Use environment-specific config
const env = process . env . NODE_ENV || 'development' ;
export default config [ env ] ;
Monitoring and Logging
// Log security-relevant events
const logSecurityEvent = async ( event : SecurityEvent ) => {
await logger . log ({
level: 'security' ,
event: event . type ,
userId: event . userId ,
ip: event . ip ,
timestamp: new Date (),
metadata: event . metadata
});
};
// Monitor for suspicious activity
app . post ( '/api/generate-doshi-token' , async ( req , res ) => {
// Rate limiting
const recentTokens = await getRecentTokensForUser ( req . user . id );
if ( recentTokens . length > 10 ) {
await logSecurityEvent ({
type: 'excessive_token_generation' ,
userId: req . user . id ,
ip: req . ip
});
return res . status ( 429 ). json ({ error: 'Too many requests' });
}
// Generate token...
});
Next Steps