Appearance
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.jsonManifest 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');
}
});Popup Script
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();Popup HTML
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
- Persistent State: Service worker maintains auth state even when popup is closed
- Centralized Logic: All auth logic in one place
- Cross-View Communication: Easy to sync state across popup, content scripts, etc.
- 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:
- Storing user state in
chrome.storage - Automatically restoring state when service worker restarts
- Using
autoIdentify: trueto 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
- Content Script - Access auth in content scripts
- Popup Authentication - Enhanced popup example
- Events - Listen for auth events