insurEco

Integration Guide

insurEco System SSO

Back to Home
Developer Documentation

Integrate insurEco SSO

Add secure single sign-on to your application in minutes. Choose between HTML/JavaScript or React/Next.js implementations.

Download LLM

Interactive Button Demo

View live examples of all available button styles with copy-paste ready code snippets.

View Live Demo

insurEco 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:

code
┌─────────────┐                                      ┌─────────────┐
│   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 ID
  • redirect_uri (required): Callback URL (must be pre-registered)
  • scope (required): Space-separated scopes (e.g., "openid profile email")
  • state (recommended): Random string for CSRF protection
  • code_challenge (required): SHA-256 hash of code_verifier
  • code_challenge_method (required): Must be "S256"

Example:

code
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=S256

Token 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 code
  • redirect_uri (required for authorization_code): Must match the original
  • client_id (required): Your application's client ID
  • client_secret (required): Your application's client secret
  • code_verifier (required for authorization_code): Original PKCE verifier
  • refresh_token (required for refresh_token): The refresh token

Response:

json
{
  "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:

json
{
  "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)

json
{
  "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 ID
  • aud: Audience - your client_id
  • exp: Expiration time (Unix timestamp)
  • iat: Issued at time
  • bioId: User's unique identifier
  • email: User's email address
  • name: User's display name
  • roles: Array of user roles
  • scope: 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

javascript
// 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:

javascript
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:

javascript
// 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:

javascript
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:

javascript
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:

json
{
  "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

1. Add Sign In Button

html
<!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

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

html
<!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:

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)

html
<!-- 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

html
<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

html
<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

javascript
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

javascript
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

javascript
// 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

javascript
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

javascript
// 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

javascript
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.

Sign in with insurEco Button

Pre-built button components for integrating insurEco System SSO into your application.

React / Next.js Component

tsx
'use client';

import Image from 'next/image';

interface SignInWithInsurecoProps {
  onClick?: () => void;
  className?: string;
}

export function SignInWithInsureco({ onClick, className = '' }: SignInWithInsurecoProps) {
  const handleClick = () => {
    if (onClick) {
      onClick();
    } else {
      // Default behavior: redirect to your OAuth login endpoint
      window.location.href = '/api/auth/login';
    }
  };

  return (
    <button
      onClick={handleClick}
      className={`
        inline-flex items-center gap-3 px-6 py-3
        bg-white border-2 border-gray-300 rounded-lg
        hover:border-gray-400 hover:shadow-md
        transition-all duration-200
        font-medium text-gray-700
        ${className}
      `}
    >
      <Image
        src="https://docs.insureco.io/images/logo/insureco-globe.svg"
        alt="insurEco"
        width={24}
        height={24}
        className="animate-pulse"
      />
      Sign in with insurEco
    </button>
  );
}

Usage

tsx
import { SignInWithInsureco } from '@/components/SignInWithInsureco';

export default function LoginPage() {
  return (
    <div>
      <h1>Welcome</h1>
      <SignInWithInsureco />
    </div>
  );
}

Plain HTML/CSS Button

html
<!DOCTYPE html>
<html>
<head>
  <style>
    .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-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
      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;
      animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
    }

    @keyframes pulse {
      0%, 100% {
        opacity: 1;
      }
      50% {
        opacity: 0.7;
      }
    }
  </style>
</head>
<body>
  <a href="/api/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>

Tailwind CSS Version

tsx
export function SignInWithInsureco() {
  return (
    <a
      href="/api/auth/login"
      className="inline-flex items-center gap-3 px-6 py-3 bg-white border-2 border-gray-300 rounded-lg hover:border-gray-400 hover:shadow-md transition-all duration-200 font-medium text-gray-700"
    >
      <img
        src="https://docs.insureco.io/images/logo/insureco-globe.svg"
        alt="insurEco"
        className="w-6 h-6 animate-pulse"
      />
      Sign in with insurEco
    </a>
  );
}

Gradient Button (Premium Style)

tsx
'use client';

import Image from 'next/image';

export function SignInWithInsurecoGradient() {
  return (
    <button
      onClick={() => window.location.href = '/api/auth/login'}
      className="
        inline-flex items-center gap-3 px-8 py-4
        bg-gradient-to-r from-blue-600 to-indigo-600
        hover:from-blue-700 hover:to-indigo-700
        text-white font-semibold text-lg
        rounded-xl shadow-lg hover:shadow-xl
        transition-all duration-300 transform hover:scale-105
      "
    >
      <Image
        src="https://docs.insureco.io/images/logo/insureco-globe.svg"
        alt="insurEco"
        width={28}
        height={28}
        className="drop-shadow-md"
      />
      Sign in with insurEco System
    </button>
  );
}

Minimal Button

tsx
export function SignInWithInsurecoMinimal() {
  return (
    <a
      href="/api/auth/login"
      className="inline-flex items-center gap-2 px-4 py-2 text-sm text-gray-600 hover:text-gray-900 transition-colors"
    >
      <img
        src="https://docs.insureco.io/images/logo/insureco-globe.svg"
        alt="insurEco"
        className="w-5 h-5"
      />
      Sign in with insurEco
    </a>
  );
}

Button with Loading State

tsx
'use client';

import { useState } from 'react';
import Image from 'next/image';

export function SignInWithInsurecoLoading() {
  const [loading, setLoading] = useState(false);

  const handleSignIn = () => {
    setLoading(true);
    window.location.href = '/api/auth/login';
  };

  return (
    <button
      onClick={handleSignIn}
      disabled={loading}
      className="
        inline-flex items-center gap-3 px-6 py-3
        bg-white border-2 border-gray-300 rounded-lg
        hover:border-gray-400 hover:shadow-md
        transition-all duration-200
        font-medium text-gray-700
        disabled:opacity-50 disabled:cursor-not-allowed
      "
    >
      {loading ? (
        <>
          <svg className="animate-spin h-6 w-6 text-gray-600" viewBox="0 0 24 24">
            <circle
              className="opacity-25"
              cx="12"
              cy="12"
              r="10"
              stroke="currentColor"
              strokeWidth="4"
              fill="none"
            />
            <path
              className="opacity-75"
              fill="currentColor"
              d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
            />
          </svg>
          Redirecting...
        </>
      ) : (
        <>
          <Image
            src="https://docs.insureco.io/images/logo/insureco-globe.svg"
            alt="insurEco"
            width={24}
            height={24}
          />
          Sign in with insurEco
        </>
      )}
    </button>
  );
}

Dark Mode Support

tsx
export function SignInWithInsurecoDark() {
  return (
    <button
      onClick={() => window.location.href = '/api/auth/login'}
      className="
        inline-flex items-center gap-3 px-6 py-3
        bg-white dark:bg-gray-800
        border-2 border-gray-300 dark:border-gray-600
        rounded-lg
        hover:border-gray-400 dark:hover:border-gray-500
        hover:shadow-md
        transition-all duration-200
        font-medium text-gray-700 dark:text-gray-200
      "
    >
      <img
        src="https://docs.insureco.io/images/logo/insureco-globe.svg"
        alt="insurEco"
        className="w-6 h-6"
      />
      Sign in with insurEco
    </button>
  );
}

Full-Width Button (Mobile Friendly)

tsx
export function SignInWithInsurecoFullWidth() {
  return (
    <button
      onClick={() => window.location.href = '/api/auth/login'}
      className="
        w-full flex items-center justify-center gap-3 px-6 py-4
        bg-white border-2 border-gray-300 rounded-lg
        hover:border-gray-400 hover:shadow-md
        transition-all duration-200
        font-medium text-gray-700
      "
    >
      <img
        src="https://docs.insureco.io/images/logo/insureco-globe.svg"
        alt="insurEco"
        className="w-6 h-6"
      />
      Sign in with insurEco
    </button>
  );
}

Custom Styling Guide

Colors

  • **Primary**: Use #3b82f6 (blue-600) or your brand colors
  • **Border**: Use #d1d5db (gray-300) for light mode
  • **Text**: Use #374151 (gray-700) for readability

Spacing

  • **Padding**: 12px 24px (py-3 px-6)
  • **Gap**: 12px between icon and text
  • **Border Radius**: 8px for modern look

Icon Size

  • Standard: 24x24px
  • Large: 28x28px
  • Small: 20x20px

Accessibility

Always include:

  • alt text for the logo image
  • Proper contrast ratios (WCAG AA minimum)
  • Focus states for keyboard navigation
  • Loading states for better UX

Logo Variants

The insurEco globe logo is available in three variants:

Color Globe (Default)

  • **URL**: https://docs.insureco.io/images/logo/insureco-globe.svg
  • Use case: Standard buttons on light backgrounds
  • Best for: Most common use cases

White Globe

  • **URL**: https://docs.insureco.io/images/logo/insureco-globe-white.svg
  • Use case: Dark or colored backgrounds
  • Best for: Black buttons, dark mode, gradient backgrounds

Black Globe

  • **URL**: https://docs.insureco.io/images/logo/insureco-globe-black.svg
  • Use case: White or very light backgrounds
  • Best for: White buttons on dark backgrounds, minimal designs

Button Examples with Logo Variants

tsx
export function SignInWithInsurecoBlack() {
  return (
    <a
      href="/api/auth/login"
      className="inline-flex items-center gap-3 px-6 py-3 bg-black border-2 border-black rounded-lg hover:bg-gray-800 transition-all duration-200 font-medium text-white"
    >
      <img
        src="https://docs.insureco.io/images/logo/insureco-globe-white.svg"
        alt="insurEco"
        className="w-6 h-6"
      />
      Sign in with insurEco
    </a>
  );
}
tsx
export function SignInWithInsurecoWhite() {
  return (
    <a
      href="/api/auth/login"
      className="inline-flex items-center gap-3 px-6 py-3 bg-white border-2 border-white rounded-lg hover:bg-gray-50 transition-all duration-200 font-medium text-gray-900 shadow-md"
    >
      <img
        src="https://docs.insureco.io/images/logo/insureco-globe-black.svg"
        alt="insurEco"
        className="w-6 h-6"
      />
      Sign in with insurEco
    </a>
  );
}

Logo Usage Guidelines

1. Choose the right variant for your background:

- Color globe for light backgrounds

- White globe for dark/colored backgrounds

- Black globe for very light/white backgrounds

2. Maintain aspect ratio - don't stretch or distort

3. Minimum size: 20x20px for readability

4. Clear space: Maintain adequate padding around the logo

5. Don't modify: Use the logo as provided without alterations

Example Integration

tsx
// app/login/page.tsx
import { SignInWithInsureco } from '@/components/SignInWithInsureco';

export default function LoginPage() {
  return (
    <div className="min-h-screen flex items-center justify-center bg-gray-50">
      <div className="max-w-md w-full space-y-8 p-8 bg-white rounded-xl shadow-lg">
        <div className="text-center">
          <h1 className="text-3xl font-bold text-gray-900">Welcome Back</h1>
          <p className="mt-2 text-sm text-gray-600">
            Sign in to access your account
          </p>
        </div>

        <div className="mt-8 space-y-6">
          {/* Other login options */}

          <div className="relative">
            <div className="absolute inset-0 flex items-center">
              <div className="w-full border-t border-gray-300" />
            </div>
            <div className="relative flex justify-center text-sm">
              <span className="px-2 bg-white text-gray-500">Or continue with</span>
            </div>
          </div>

          <SignInWithInsureco />
        </div>
      </div>
    </div>
  );
}

Need help? Contact our support team or visit the full documentation.

Powered by insurEco System