Skip to content

Background Script

Use ExtensionLogin in Manifest V3 service workers.

Overview

This example shows how to:

  • Initialize ExtensionLogin in a background service worker
  • Handle authentication from the background
  • Communicate between popup and background
  • Persist user state across extension restarts

Project Structure

background-auth/
├── manifest.json
├── background.js
├── popup/
│   ├── popup.html
│   └── popup.js
└── package.json

Manifest Configuration

json
{
  "manifest_version": 3,
  "name": "Background Auth Extension",
  "version": "1.0.0",

  "background": {
    "service_worker": "background.js",
    "type": "module"
  },

  "action": {
    "default_popup": "popup/popup.html"
  },

  "permissions": [
    "storage"
  ],

  "host_permissions": [
    "https://api.extensionlogin.com/*"
  ]
}

Background Service Worker

javascript
// background.js
import ExtensionLogin from 'extensionlogin';

// Initialize ExtensionLogin in background
ExtensionLogin.init({
  apiKey: 'el_live_your_api_key_here',
  autoIdentify: true
});

// Listen for auth state changes
ExtensionLogin.onAuthChange((user) => {
  if (user) {
    console.log('[Background] User logged in:', user.email);
    // Notify all extension views
    notifyAllViews({ type: 'AUTH_CHANGED', user });
  } else {
    console.log('[Background] User logged out');
    notifyAllViews({ type: 'AUTH_CHANGED', user: null });
  }
});

// Handle messages from popup/content scripts
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  handleMessage(message, sender).then(sendResponse);
  return true; // Keep channel open for async response
});

async function handleMessage(message, sender) {
  switch (message.type) {
    case 'GET_USER':
      return {
        success: true,
        user: ExtensionLogin.getUser()
      };

    case 'IDENTIFY':
      try {
        const result = await ExtensionLogin.identify(message.data);
        return result;
      } catch (error) {
        return {
          success: false,
          error: { code: 'UNKNOWN', message: error.message }
        };
      }

    case 'LOGOUT':
      await ExtensionLogin.logout();
      return { success: true };

    case 'IS_AUTHENTICATED':
      return {
        authenticated: ExtensionLogin.isAuthenticated()
      };

    default:
      return { error: 'Unknown message type' };
  }
}

// Notify all views (popup, content scripts, etc.)
function notifyAllViews(message) {
  // Send to popup if open
  chrome.runtime.sendMessage(message).catch(() => {
    // Popup not open, ignore
  });

  // Send to all tabs
  chrome.tabs.query({}, (tabs) => {
    tabs.forEach((tab) => {
      chrome.tabs.sendMessage(tab.id, message).catch(() => {
        // Tab doesn't have content script, ignore
      });
    });
  });
}

// Handle extension install/update
chrome.runtime.onInstalled.addListener((details) => {
  if (details.reason === 'install') {
    console.log('[Background] Extension installed');
  } else if (details.reason === 'update') {
    console.log('[Background] Extension updated');
  }
});
javascript
// popup/popup.js

// Helper to send messages to background
async function sendToBackground(type, data = {}) {
  return chrome.runtime.sendMessage({ type, data });
}

// DOM Elements
const loginView = document.getElementById('login-view');
const userView = document.getElementById('user-view');
const emailInput = document.getElementById('email');
const nameInput = document.getElementById('name');
const submitBtn = document.getElementById('submit-btn');
const logoutBtn = document.getElementById('logout-btn');
const userNameEl = document.getElementById('user-name');
const userEmailEl = document.getElementById('user-email');
const errorEl = document.getElementById('error');

// Initialize
async function init() {
  const { user } = await sendToBackground('GET_USER');

  if (user) {
    showUserView(user);
  } else {
    showLoginView();
  }
}

// Handle login
submitBtn.addEventListener('click', async () => {
  const email = emailInput.value.trim();
  const name = nameInput.value.trim();

  if (!email) {
    showError('Please enter your email');
    return;
  }

  submitBtn.disabled = true;
  submitBtn.textContent = 'Signing in...';
  hideError();

  const result = await sendToBackground('IDENTIFY', {
    email,
    name: name || undefined
  });

  submitBtn.disabled = false;
  submitBtn.textContent = 'Get Started';

  if (result.success) {
    showUserView(result.user);
  } else {
    showError(result.error.message);
  }
});

// Handle logout
logoutBtn.addEventListener('click', async () => {
  await sendToBackground('LOGOUT');
  showLoginView();
});

// Listen for auth changes from background
chrome.runtime.onMessage.addListener((message) => {
  if (message.type === 'AUTH_CHANGED') {
    if (message.user) {
      showUserView(message.user);
    } else {
      showLoginView();
    }
  }
});

// UI helpers
function showLoginView() {
  loginView.classList.remove('hidden');
  userView.classList.add('hidden');
}

function showUserView(user) {
  loginView.classList.add('hidden');
  userView.classList.remove('hidden');
  userNameEl.textContent = user.name || 'User';
  userEmailEl.textContent = user.email;
}

function showError(message) {
  errorEl.textContent = message;
  errorEl.classList.remove('hidden');
}

function hideError() {
  errorEl.classList.add('hidden');
}

// Start
init();
html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Auth Extension</title>
  <style>
    body {
      width: 300px;
      padding: 20px;
      font-family: system-ui, sans-serif;
    }
    .hidden { display: none; }
    input {
      width: 100%;
      padding: 10px;
      margin-bottom: 10px;
      border: 1px solid #ddd;
      border-radius: 6px;
    }
    button {
      width: 100%;
      padding: 12px;
      background: #3b82f6;
      color: white;
      border: none;
      border-radius: 6px;
      cursor: pointer;
    }
    button:disabled { background: #9ca3af; }
    .error {
      color: #dc2626;
      margin-top: 10px;
      font-size: 14px;
    }
    .user-info {
      text-align: center;
    }
    .user-info h2 {
      margin-bottom: 8px;
    }
    .user-info p {
      color: #666;
      margin-bottom: 20px;
    }
    #logout-btn {
      background: #f3f4f6;
      color: #374151;
    }
  </style>
</head>
<body>
  <div id="login-view">
    <h1>Welcome</h1>
    <input type="email" id="email" placeholder="Email">
    <input type="text" id="name" placeholder="Name (optional)">
    <button id="submit-btn">Get Started</button>
    <div id="error" class="error hidden"></div>
  </div>

  <div id="user-view" class="user-info hidden">
    <h2 id="user-name"></h2>
    <p id="user-email"></p>
    <button id="logout-btn">Sign Out</button>
  </div>

  <script src="popup.js"></script>
</body>
</html>

Why Use Background Script?

Benefits

  1. Persistent State: Service worker maintains auth state even when popup is closed
  2. Centralized Logic: All auth logic in one place
  3. Cross-View Communication: Easy to sync state across popup, content scripts, etc.
  4. Background Tasks: Can handle auth-related background tasks

When to Use

  • Multiple views need access to user state
  • You need to perform background authentication
  • Auth state should persist across popup opens/closes
  • Content scripts need to check auth status

Service Worker Lifecycle

Manifest V3 service workers can be terminated when idle. ExtensionLogin handles this by:

  1. Storing user state in chrome.storage
  2. Automatically restoring state when service worker restarts
  3. Using autoIdentify: true to restore user on init
javascript
// This handles service worker restarts automatically
ExtensionLogin.init({
  apiKey: 'el_live_xxx',
  autoIdentify: true  // Restores user from storage
});

Checking Auth from Content Scripts

javascript
// content.js

// Check if user is authenticated
async function checkAuth() {
  const { authenticated } = await chrome.runtime.sendMessage({
    type: 'IS_AUTHENTICATED'
  });
  return authenticated;
}

// Get current user
async function getUser() {
  const { user } = await chrome.runtime.sendMessage({
    type: 'GET_USER'
  });
  return user;
}

// Listen for auth changes
chrome.runtime.onMessage.addListener((message) => {
  if (message.type === 'AUTH_CHANGED') {
    if (message.user) {
      onUserLoggedIn(message.user);
    } else {
      onUserLoggedOut();
    }
  }
});

Error Handling

Handle service worker termination gracefully:

javascript
// background.js
chrome.runtime.onSuspend?.addListener(() => {
  console.log('[Background] Service worker suspending');
  // State is automatically persisted by ExtensionLogin
});

chrome.runtime.onStartup?.addListener(() => {
  console.log('[Background] Extension starting up');
  // ExtensionLogin will restore user automatically
});

Next Steps

Built for Chrome Extension Developers