Integrate insurEco SSO
Add secure single sign-on to your application in minutes. Choose between HTML/JavaScript or React/Next.js implementations.
SSO Overview
Comprehensive guide to insurEco System SSO architecture and features
HTML/JavaScript
Complete integration guide for vanilla JavaScript applications
Sign-In Button Components
Ready-to-use button components for React, Next.js, and HTML
Interactive Button Demo
View live examples of all available button styles with copy-paste ready code snippets.
View Live DemoinsurEco System SSO - Developer Overview
Introduction
insurEco System Single Sign-On (SSO) is a centralized identity and access management platform built on OAuth 2.0 and OpenID Connect standards. It provides secure, seamless authentication across all insurEco ecosystem applications, eliminating the need for users to manage multiple credentials while giving developers a robust, standards-compliant authentication solution.
Architecture Overview
Core Components
The insurEco SSO system consists of three primary components:
1. BIO ID Server - The central OAuth 2.0 authorization server
- Handles user authentication and authorization
- Issues access tokens and refresh tokens
- Manages user profiles, roles, and permissions
- Provides OAuth 2.0 endpoints (authorize, token, userinfo)
2. Client Applications - Your applications that integrate with SSO
- Redirect users to BIO ID for authentication
- Receive authorization codes and exchange them for tokens
- Make authenticated API requests using access tokens
3. User Directory - Centralized user database
- Single source of truth for user identities
- Stores user profiles, credentials, and permissions
- Supports multi-factor authentication (MFA)
- Maintains audit logs for security compliance
Technology Stack
- Protocol: OAuth 2.0 with PKCE (Proof Key for Code Exchange)
- Token Format: JWT (JSON Web Tokens) signed with HS256
- Transport Security: HTTPS/TLS 1.2+
- Database: MongoDB for user data and sessions
- Runtime: Node.js with Next.js 14+
How It Works
Authentication Flow
The insurEco SSO uses the OAuth 2.0 Authorization Code flow with PKCE for maximum security:
┌─────────────┐ ┌─────────────┐
│ Client │ │ BIO ID │
│ Application │ │ Server │
└──────┬──────┘ └──────┬──────┘
│ │
│ 1. User clicks "Sign in with insurEco" │
│─────────────────────────────────────────────────→ │
│ │
│ 2. Generate PKCE code_verifier & code_challenge │
│ │
│ 3. Redirect to /oauth/authorize with: │
│ - client_id │
│ - redirect_uri │
│ - code_challenge │
│──────────────────────────────────────────────────→│
│ │
│ 4. User authenticates │
│ (login form, MFA if enabled) │
│ │
│ 5. Redirect back with authorization code │
│←──────────────────────────────────────────────────│
│ │
│ 6. Exchange code for tokens at /api/oauth/token │
│ - authorization_code │
│ - code_verifier │
│──────────────────────────────────────────────────→│
│ │
│ 7. Receive tokens: │
│ - access_token (JWT, 1 hour lifetime) │
│ - refresh_token (7 days lifetime) │
│←──────────────────────────────────────────────────│
│ │
│ 8. Store tokens securely (HTTP-only cookies) │
│ │
│ 9. Make API requests with access_token │
│ │
│ 10. When access_token expires, use refresh_token │
│ to obtain new access_token │
│ │Key Security Features
1. PKCE (Proof Key for Code Exchange)
PKCE prevents authorization code interception attacks by requiring the client to prove possession of the code verifier:
- code_verifier: Cryptographically random string (43-128 characters)
- code_challenge: SHA-256 hash of the code_verifier, base64-url-encoded
- The authorization server stores the code_challenge
- During token exchange, the client sends the code_verifier
- The server verifies:
SHA256(code_verifier) == code_challenge
2. State Parameter
Prevents CSRF attacks by:
- Generating a random state value before redirecting to authorization
- Storing it in session storage
- Verifying it matches when the callback is received
3. Secure Token Storage
- Access Tokens: Stored in HTTP-only, Secure, SameSite cookies
- Refresh Tokens: Also stored in HTTP-only cookies
- Never exposed to JavaScript to prevent XSS attacks
4. Token Expiration
- Access Tokens: Short-lived (1 hour) - limits exposure window
- Refresh Tokens: Longer-lived (7 days) - enables seamless re-authentication
- Rotation: Refresh tokens are rotated on each use for maximum security
OAuth 2.0 Endpoints
Authorization Endpoint
**URL**: https://bio.insureco.io/oauth/authorize
Method: GET
Parameters:
response_type(required): Must be "code"client_id(required): Your application's client IDredirect_uri(required): Callback URL (must be pre-registered)scope(required): Space-separated scopes (e.g., "openid profile email")state(recommended): Random string for CSRF protectioncode_challenge(required): SHA-256 hash of code_verifiercode_challenge_method(required): Must be "S256"
Example:
https://bio.insureco.io/oauth/authorize?
response_type=code&
client_id=your_client_id&
redirect_uri=https://yourapp.com/api/auth/callback&
scope=openid%20profile%20email&
state=random_state_value&
code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&
code_challenge_method=S256Token Endpoint
**URL**: https://bio.insureco.io/api/oauth/token
Method: POST
Content-Type: application/x-www-form-urlencoded
Parameters:
grant_type(required): "authorization_code" or "refresh_token"code(required for authorization_code): The authorization coderedirect_uri(required for authorization_code): Must match the originalclient_id(required): Your application's client IDclient_secret(required): Your application's client secretcode_verifier(required for authorization_code): Original PKCE verifierrefresh_token(required for refresh_token): The refresh token
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "8xLOxBtZp8",
"scope": "openid profile email"
}UserInfo Endpoint
**URL**: https://bio.insureco.io/api/oauth/userinfo
Method: GET
Headers:
Authorization: "Bearer {access_token}"
Response:
{
"sub": "bio_12345",
"bioId": "bio_12345",
"email": "[email protected]",
"name": "John Doe",
"picture": "https://avatar.url/photo.jpg",
"email_verified": true,
"roles": ["user", "developer"],
"permissions": ["read:profile", "write:data"]
}Token Structure
Access Token (JWT)
{
"header": {
"alg": "HS256",
"typ": "JWT"
},
"payload": {
"iss": "https://bio.insureco.io",
"sub": "bio_12345",
"aud": "your_client_id",
"exp": 1735891200,
"iat": 1735887600,
"bioId": "bio_12345",
"email": "[email protected]",
"name": "John Doe",
"roles": ["user", "developer"],
"scope": "openid profile email"
},
"signature": "..."
}Key Claims:
iss: Issuer - always "https://bio.insureco.io"sub: Subject - the user's BIO IDaud: Audience - your client_idexp: Expiration time (Unix timestamp)iat: Issued at timebioId: User's unique identifieremail: User's email addressname: User's display nameroles: Array of user rolesscope: Granted OAuth scopes
User Roles and Permissions
The insurEco System includes a flexible role-based access control (RBAC) system:
Standard Roles
1. User - Basic authenticated user
- Access to personal profile
- Read access to public resources
2. Developer - Application developer
- Create and manage OAuth clients
- Access to API documentation
- Usage analytics
3. Admin - System administrator
- User management
- Role assignment
- System configuration
- Audit log access
4. Super Admin - Full system access
- All admin privileges
- Security settings
- System maintenance
Custom Roles
Applications can define custom roles specific to their domain:
- Roles are namespaced by application
- Permissions are granted per role
- Roles are included in access tokens
Multi-Factor Authentication (MFA)
insurEco SSO supports TOTP-based MFA:
Features
- TOTP Support: Time-based One-Time Passwords (RFC 6238)
- App Compatibility: Works with Google Authenticator, Authy, 1Password, etc.
- Recovery Codes: Backup codes for account recovery
- Flexible Enforcement: Can be required per application or user role
MFA Flow
1. User enables MFA in security settings
2. System generates QR code for TOTP setup
3. User scans QR code with authenticator app
4. User provides backup codes (stored securely)
5. On subsequent logins, TOTP code is required after password
Session Management
Session Lifecycle
1. Session Creation: When user successfully authenticates
2. Token Issuance: Access and refresh tokens generated
3. Session Tracking: Stored in database with metadata:
- Device information
- IP address
- Last activity timestamp
- Geolocation (optional)
4. Session Refresh: Using refresh tokens extends session
5. Session Termination:
- User logout
- Token expiration
- Forced logout (security event)
- Session revocation by admin
Session Security
- Device Fingerprinting: Tracks known devices
- Anomaly Detection: Flags suspicious login patterns
- Concurrent Sessions: Limit active sessions per user
- Session Monitoring: Users can view and revoke active sessions
Audit Logging
All authentication events are logged for security and compliance:
Logged Events
- Login attempts (success/failure)
- OAuth authorizations
- Token exchanges
- Token refreshes
- Logout events
- MFA setup/changes
- Password changes
- Session revocations
- Role changes
- Permission modifications
Log Data
Each log entry includes:
- Timestamp
- User identifier
- Event type
- IP address
- User agent
- Geolocation
- Success/failure status
- Additional metadata
Integration Best Practices
1. Token Management
// Good: Store tokens in HTTP-only cookies
response.cookies.set('access_token', token, {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 3600
});
// Bad: Storing in localStorage (vulnerable to XSS)
localStorage.setItem('access_token', token);2. Token Verification
Always verify tokens on your backend:
import { jwtVerify } from 'jose';
const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET);
async function verifyAccessToken(token) {
try {
const { payload } = await jwtVerify(token, JWT_SECRET, {
issuer: 'https://bio.insureco.io',
audience: process.env.CLIENT_ID
});
return payload;
} catch (error) {
// Token invalid or expired
return null;
}
}3. Error Handling
Implement proper error handling for OAuth flows:
// Check for OAuth errors in callback
const error = searchParams.get('error');
if (error) {
const errorDescription = searchParams.get('error_description');
switch (error) {
case 'access_denied':
// User denied authorization
break;
case 'invalid_request':
// Malformed request
break;
case 'server_error':
// Server-side error
break;
}
}4. Refresh Token Flow
Implement automatic token refresh:
async function getValidAccessToken() {
const accessToken = getStoredAccessToken();
// Check if token is still valid
try {
await verifyAccessToken(accessToken);
return accessToken;
} catch (error) {
// Token expired, refresh it
const refreshToken = getStoredRefreshToken();
return await refreshAccessToken(refreshToken);
}
}5. Logout Implementation
Proper logout requires:
async function logout() {
// 1. Clear local tokens
clearTokenCookies();
// 2. Optionally revoke tokens on server
await fetch('https://bio.insureco.io/api/oauth/revoke', {
method: 'POST',
body: JSON.stringify({ token: refreshToken })
});
// 3. Redirect to login page
window.location.href = '/login';
}Security Considerations
1. HTTPS Required
All OAuth communications must use HTTPS:
- Prevents token interception
- Protects user credentials
- Required by OAuth 2.0 specification
2. Redirect URI Validation
- Register exact redirect URIs with your OAuth client
- Never use wildcards in production
- Validate redirect_uri parameter matches registered URIs
3. State Parameter
Always use the state parameter:
- Prevents CSRF attacks
- Maintains application state across redirect
- Should be cryptographically random
4. Token Storage
- Do: Use HTTP-only, Secure cookies
- Don't: Store tokens in localStorage or sessionStorage
- Don't: Include tokens in URLs
5. Scope Limitation
Request only the scopes your application needs:
- Principle of least privilege
- Better user trust
- Easier to audit
Rate Limiting
To prevent abuse, the following rate limits apply:
- Authorization requests: 10 per minute per IP
- Token requests: 20 per minute per client
- UserInfo requests: 100 per minute per user
- Failed login attempts: 5 per 15 minutes per user
Exceeding rate limits returns HTTP 429 (Too Many Requests).
Error Codes
OAuth Error Responses
| Error Code | Description | Resolution |
|------------|-------------|------------|
| invalid_request | Malformed request | Check required parameters |
| invalid_client | Client authentication failed | Verify client_id and secret |
| invalid_grant | Invalid authorization code/refresh token | Request new authorization |
| unauthorized_client | Client not authorized for grant type | Check client configuration |
| unsupported_grant_type | Grant type not supported | Use authorization_code or refresh_token |
| invalid_scope | Requested scope invalid | Check available scopes |
| access_denied | User denied authorization | User must authorize access |
| server_error | Server error occurred | Retry or contact support |
HTTP Status Codes
| Status | Meaning | Action |
|--------|---------|--------|
| 200 | Success | Process response |
| 302 | Redirect | Follow redirect |
| 400 | Bad Request | Fix request parameters |
| 401 | Unauthorized | Token invalid/expired |
| 403 | Forbidden | Insufficient permissions |
| 429 | Too Many Requests | Implement backoff |
| 500 | Server Error | Retry with exponential backoff |
Monitoring and Analytics
Available Metrics
Track these metrics for your integration:
- Authentication Success Rate: Percentage of successful logins
- Token Exchange Success Rate: Percentage of successful token exchanges
- Average Login Time: Time from redirect to token receipt
- Active Sessions: Current number of active user sessions
- Token Refresh Rate: How often tokens are refreshed
- Error Rate: Percentage of requests resulting in errors
Health Check
Monitor the SSO server health:
**Endpoint**: https://bio.insureco.io/api/health
Response:
{
"status": "healthy",
"timestamp": "2025-12-02T05:00:00Z",
"services": {
"database": "healthy",
"cache": "healthy",
"email": "healthy"
}
}Support and Resources
Documentation
- Integration Guide: https://bio.insureco.io/how-to-integrate
- API Reference: https://docs.insureco.io/api
- Button Components: https://bio.insureco.io/demo/sign-in-button.html
Developer Portal
Access the developer portal at https://bio.insureco.io to:
- Register OAuth clients
- Manage API keys
- View usage analytics
- Access support resources
Support Channels
- Email: [email protected]
- Documentation: https://docs.insureco.io
- Status Page: https://status.insureco.io
Appendix
Glossary
- OAuth 2.0: Industry-standard protocol for authorization
- OpenID Connect: Identity layer built on OAuth 2.0
- JWT: JSON Web Token, a compact token format
- PKCE: Proof Key for Code Exchange, security extension for OAuth
- TOTP: Time-based One-Time Password
- RBAC: Role-Based Access Control
- SSO: Single Sign-On
- MFA: Multi-Factor Authentication
- CSRF: Cross-Site Request Forgery
- XSS: Cross-Site Scripting
Standards Compliance
insurEco SSO complies with:
- RFC 6749: OAuth 2.0 Authorization Framework
- RFC 7636: PKCE for OAuth Public Clients
- RFC 7519: JSON Web Token (JWT)
- RFC 6238: TOTP: Time-Based One-Time Password Algorithm
- OpenID Connect Core 1.0
Version History
- v1.0 (2025-01-01): Initial release
- OAuth 2.0 with PKCE
- JWT tokens
- Basic user management
- MFA support
- Audit logging
HTML/JavaScript SSO Integration Guide
Complete guide for integrating insurEco System SSO into vanilla HTML/JavaScript applications.
Table of Contents
- Quick Start
- Complete Implementation
- Button Components
- OAuth Flow Implementation
- Session Management
- Security Best Practices
Quick Start
1. Add Sign In Button
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<a href="/auth/login" class="insureco-signin-btn">
<img src="https://docs.insureco.io/images/logo/insureco-globe.svg" alt="insurEco">
Sign in with insurEco
</a>
</body>
</html>2. Add CSS
.insureco-signin-btn {
display: inline-flex;
align-items: center;
gap: 12px;
padding: 12px 24px;
background-color: white;
border: 2px solid #d1d5db;
border-radius: 8px;
font-size: 16px;
font-weight: 500;
color: #374151;
cursor: pointer;
transition: all 0.2s;
text-decoration: none;
}
.insureco-signin-btn:hover {
border-color: #9ca3af;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.insureco-signin-btn img {
width: 24px;
height: 24px;
}Complete Implementation
Single Page Application (SPA) Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My App - insurEco SSO</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
margin: 0;
padding: 0;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
/* Login Page */
.login-page {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.login-card {
background: white;
padding: 40px;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
text-align: center;
max-width: 400px;
width: 100%;
}
.login-card h1 {
margin-bottom: 10px;
color: #1f2937;
}
.login-card p {
margin-bottom: 30px;
color: #6b7280;
}
/* Button */
.insureco-signin-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 12px;
width: 100%;
padding: 14px 24px;
background-color: white;
border: 2px solid #d1d5db;
border-radius: 8px;
font-size: 16px;
font-weight: 500;
color: #374151;
cursor: pointer;
transition: all 0.2s;
text-decoration: none;
}
.insureco-signin-btn:hover {
border-color: #9ca3af;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.insureco-signin-btn img {
width: 24px;
height: 24px;
}
/* Dashboard */
.dashboard {
display: none;
}
.dashboard.active {
display: block;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
background: white;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
.user-info {
display: flex;
align-items: center;
gap: 12px;
}
.logout-btn {
padding: 8px 16px;
background: #ef4444;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
}
.logout-btn:hover {
background: #dc2626;
}
</style>
</head>
<body>
<!-- Login Page -->
<div id="loginPage" class="login-page">
<div class="login-card">
<h1>Welcome to My App</h1>
<p>Sign in to continue</p>
<button onclick="handleSignIn()" class="insureco-signin-btn">
<img src="https://docs.insureco.io/images/logo/insureco-globe.svg" alt="insurEco">
Sign in with insurEco
</button>
</div>
</div>
<!-- Dashboard -->
<div id="dashboard" class="dashboard">
<div class="header">
<h2>Dashboard</h2>
<div class="user-info">
<span id="userName">Loading...</span>
<button onclick="handleSignOut()" class="logout-btn">Sign Out</button>
</div>
</div>
<div class="container">
<h3>Welcome!</h3>
<p>You are successfully signed in with insurEco System.</p>
<div id="userDetails"></div>
</div>
</div>
<script>
// Configuration
const CONFIG = {
bioIdUrl: 'https://bio.insureco.io',
clientId: 'your-client-id',
redirectUri: window.location.origin + '/callback.html',
scope: 'openid profile email'
};
// PKCE Helper Functions
function generateRandomString(length) {
const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
const values = new Uint8Array(length);
crypto.getRandomValues(values);
return Array.from(values)
.map(v => charset[v % charset.length])
.join('');
}
async function sha256(plain) {
const encoder = new TextEncoder();
const data = encoder.encode(plain);
return crypto.subtle.digest('SHA-256', data);
}
function base64urlencode(buffer) {
const bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary)
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
async function generatePKCE() {
const codeVerifier = generateRandomString(128);
const hashed = await sha256(codeVerifier);
const codeChallenge = base64urlencode(hashed);
return { codeVerifier, codeChallenge };
}
// Sign In Flow
async function handleSignIn() {
// Generate PKCE
const { codeVerifier, codeChallenge } = await generatePKCE();
// Generate state for CSRF protection
const state = generateRandomString(32);
// Store in sessionStorage
sessionStorage.setItem('oauth_code_verifier', codeVerifier);
sessionStorage.setItem('oauth_state', state);
// Build authorization URL
const params = new URLSearchParams({
client_id: CONFIG.clientId,
redirect_uri: CONFIG.redirectUri,
response_type: 'code',
scope: CONFIG.scope,
state: state,
code_challenge: codeChallenge,
code_challenge_method: 'S256'
});
// Redirect to BIO ID
window.location.href = `${CONFIG.bioIdUrl}/oauth/authorize?${params.toString()}`;
}
// Sign Out
function handleSignOut() {
localStorage.removeItem('access_token');
localStorage.removeItem('user_info');
sessionStorage.clear();
showLogin();
}
// Check Session on Load
window.addEventListener('DOMContentLoaded', () => {
const accessToken = localStorage.getItem('access_token');
if (accessToken) {
loadUserInfo();
showDashboard();
} else {
showLogin();
}
});
function showLogin() {
document.getElementById('loginPage').style.display = 'flex';
document.getElementById('dashboard').classList.remove('active');
}
function showDashboard() {
document.getElementById('loginPage').style.display = 'none';
document.getElementById('dashboard').classList.add('active');
}
async function loadUserInfo() {
const userInfo = localStorage.getItem('user_info');
if (userInfo) {
const user = JSON.parse(userInfo);
document.getElementById('userName').textContent = user.name;
document.getElementById('userDetails').innerHTML = `
<p><strong>Email:</strong> ${user.email}</p>
<p><strong>User Type:</strong> ${user.userType}</p>
`;
}
}
</script>
</body>
</html>OAuth Callback Handler
Create callback.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Signing in...</title>
<style>
body {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.loading {
text-align: center;
color: white;
}
.spinner {
width: 50px;
height: 50px;
border: 4px solid rgba(255, 255, 255, 0.3);
border-top-color: white;
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="loading">
<div class="spinner"></div>
<h2>Signing you in...</h2>
</div>
<script>
// Configuration (must match main app)
const CONFIG = {
bioIdUrl: 'https://bio.insureco.io',
clientId: 'your-client-id',
clientSecret: 'your-client-secret', // Only needed for confidential clients
redirectUri: window.location.origin + '/callback.html'
};
async function handleCallback() {
try {
// Parse URL parameters
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const state = params.get('state');
const error = params.get('error');
if (error) {
throw new Error(params.get('error_description') || error);
}
// Validate state
const storedState = sessionStorage.getItem('oauth_state');
if (!state || state !== storedState) {
throw new Error('Invalid state parameter');
}
// Get code verifier
const codeVerifier = sessionStorage.getItem('oauth_code_verifier');
if (!codeVerifier) {
throw new Error('Missing code verifier');
}
// Exchange code for tokens
const tokenResponse = await fetch(`${CONFIG.bioIdUrl}/api/oauth/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: CONFIG.redirectUri,
client_id: CONFIG.clientId,
client_secret: CONFIG.clientSecret,
code_verifier: codeVerifier
})
});
if (!tokenResponse.ok) {
const errorData = await tokenResponse.json();
throw new Error(errorData.error_description || 'Token exchange failed');
}
const tokens = await tokenResponse.json();
// Fetch user info
const userInfoResponse = await fetch(`${CONFIG.bioIdUrl}/api/oauth/userinfo`, {
headers: {
'Authorization': `Bearer ${tokens.access_token}`
}
});
if (!userInfoResponse.ok) {
throw new Error('Failed to fetch user info');
}
const userInfo = await userInfoResponse.json();
// Store tokens and user info
localStorage.setItem('access_token', tokens.access_token);
localStorage.setItem('refresh_token', tokens.refresh_token);
localStorage.setItem('user_info', JSON.stringify(userInfo));
// Clean up session storage
sessionStorage.removeItem('oauth_state');
sessionStorage.removeItem('oauth_code_verifier');
// Redirect to main app
window.location.href = '/';
} catch (error) {
console.error('OAuth callback error:', error);
alert('Sign in failed: ' + error.message);
window.location.href = '/';
}
}
// Run callback handler
handleCallback();
</script>
</body>
</html>Button Components
Standard Button (Copy-Paste Ready)
<!-- Add to your HTML -->
<a href="/auth/login" class="insureco-btn">
<img src="https://docs.insureco.io/images/logo/insureco-globe.svg" alt="insurEco">
Sign in with insurEco
</a>
<!-- Add to your CSS -->
<style>
.insureco-btn {
display: inline-flex;
align-items: center;
gap: 12px;
padding: 12px 24px;
background: white;
border: 2px solid #d1d5db;
border-radius: 8px;
font-size: 16px;
font-weight: 500;
color: #374151;
text-decoration: none;
transition: all 0.2s;
}
.insureco-btn:hover {
border-color: #9ca3af;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.insureco-btn img {
width: 24px;
height: 24px;
}
</style>Gradient Button
<a href="/auth/login" class="insureco-btn-gradient">
<img src="https://docs.insureco.io/images/logo/insureco-globe.svg" alt="insurEco">
Sign in with insurEco
</a>
<style>
.insureco-btn-gradient {
display: inline-flex;
align-items: center;
gap: 12px;
padding: 14px 28px;
background: linear-gradient(to right, #2563eb, #4f46e5);
border: none;
border-radius: 12px;
font-size: 16px;
font-weight: 600;
color: white;
text-decoration: none;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
transition: all 0.3s;
}
.insureco-btn-gradient:hover {
background: linear-gradient(to right, #1d4ed8, #4338ca);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.2);
transform: translateY(-2px);
}
.insureco-btn-gradient img {
width: 24px;
height: 24px;
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
}
</style>Button with Loading State
<button id="signInBtn" onclick="handleSignIn()" class="insureco-btn-loading">
<img id="btnIcon" src="https://docs.insureco.io/images/logo/insureco-globe.svg" alt="insurEco">
<span id="btnText">Sign in with insurEco</span>
</button>
<style>
.insureco-btn-loading {
display: inline-flex;
align-items: center;
gap: 12px;
padding: 12px 24px;
background: white;
border: 2px solid #d1d5db;
border-radius: 8px;
font-size: 16px;
font-weight: 500;
color: #374151;
cursor: pointer;
transition: all 0.2s;
}
.insureco-btn-loading:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.insureco-btn-loading img {
width: 24px;
height: 24px;
}
.spinner {
width: 20px;
height: 20px;
border: 3px solid #f3f4f6;
border-top-color: #374151;
border-radius: 50%;
animation: spin 0.6s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
<script>
async function handleSignIn() {
const btn = document.getElementById('signInBtn');
const icon = document.getElementById('btnIcon');
const text = document.getElementById('btnText');
// Show loading state
btn.disabled = true;
icon.outerHTML = '<div class="spinner"></div>';
text.textContent = 'Redirecting...';
// Initiate OAuth flow
// ... your OAuth initialization code
window.location.href = '/api/auth/login';
}
</script>Session Management
Refresh Token Implementation
async function refreshAccessToken() {
const refreshToken = localStorage.getItem('refresh_token');
if (!refreshToken) {
throw new Error('No refresh token available');
}
const response = await fetch(`${CONFIG.bioIdUrl}/api/oauth/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: CONFIG.clientId,
client_secret: CONFIG.clientSecret
})
});
if (!response.ok) {
throw new Error('Token refresh failed');
}
const tokens = await response.json();
localStorage.setItem('access_token', tokens.access_token);
localStorage.setItem('refresh_token', tokens.refresh_token);
return tokens.access_token;
}
// Auto-refresh before token expires
function setupTokenRefresh() {
// Refresh 5 minutes before expiry (adjust as needed)
const refreshInterval = (15 * 60 - 5 * 60) * 1000; // 10 minutes
setInterval(async () => {
try {
await refreshAccessToken();
console.log('Token refreshed successfully');
} catch (error) {
console.error('Token refresh failed:', error);
handleSignOut();
}
}, refreshInterval);
}Protected API Calls
async function makeAuthenticatedRequest(url, options = {}) {
let accessToken = localStorage.getItem('access_token');
if (!accessToken) {
window.location.href = '/';
return;
}
try {
// Make request with access token
const response = await fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${accessToken}`
}
});
// If unauthorized, try refreshing token
if (response.status === 401) {
accessToken = await refreshAccessToken();
// Retry request with new token
return fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${accessToken}`
}
});
}
return response;
} catch (error) {
console.error('API request failed:', error);
throw error;
}
}
// Usage example
async function fetchUserData() {
const response = await makeAuthenticatedRequest('https://api.yourapp.com/user');
return response.json();
}Security Best Practices
1. Always Use HTTPS
// Ensure redirect URI uses HTTPS in production
const CONFIG = {
redirectUri: window.location.protocol === 'https:'
? window.location.origin + '/callback.html'
: 'http://localhost:3000/callback.html' // Development only
};2. Validate State Parameter
function validateState(receivedState) {
const storedState = sessionStorage.getItem('oauth_state');
if (!receivedState || !storedState) {
throw new Error('Missing state parameter');
}
if (receivedState !== storedState) {
throw new Error('State mismatch - possible CSRF attack');
}
// Clean up
sessionStorage.removeItem('oauth_state');
}3. Secure Token Storage
// Use sessionStorage for more secure, session-only storage
// Or implement secure cookie-based storage
class SecureStorage {
static setToken(key, value) {
// Use httpOnly cookies via your backend
// Or use sessionStorage with encryption
const encrypted = this.encrypt(value);
sessionStorage.setItem(key, encrypted);
}
static getToken(key) {
const encrypted = sessionStorage.getItem(key);
return encrypted ? this.decrypt(encrypted) : null;
}
static encrypt(data) {
// Implement encryption (example only)
return btoa(data); // Use proper encryption in production
}
static decrypt(data) {
// Implement decryption
return atob(data);
}
}4. Handle Errors Gracefully
async function handleOAuthError(error) {
console.error('OAuth Error:', error);
// Clear any stored state
sessionStorage.clear();
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
// Show user-friendly error
const errorMessages = {
'access_denied': 'You denied access to the application',
'invalid_state': 'Security validation failed. Please try again',
'invalid_request': 'Invalid request. Please try again'
};
const message = errorMessages[error.code] || 'An error occurred during sign in';
alert(message);
// Redirect to login
window.location.href = '/';
}Demo Files
View a complete working demo at:
Open this file in your browser to see all button styles and interactive examples.
Next Steps
1. Register your application as an OAuth client with BIO ID
2. Configure your redirect URIs
3. Implement the callback handler
4. Add protected routes
5. Test the complete OAuth flow
For backend implementation (Node.js, PHP, Python, etc.), see the main SSO Integration Guide.
Need help? Contact our support team or visit the full documentation.
Powered by insurEco System