Skip to content

Popup Authentication

A complete popup with email and Google OAuth authentication.

Overview

This example shows how to build a professional authentication popup with:

  • Email-based login
  • Google OAuth sign-in
  • Session persistence
  • Loading states
  • Error handling

Project Structure

auth-extension/
├── manifest.json
├── popup/
│   ├── popup.html
│   ├── popup.js
│   └── popup.css
├── icons/
│   ├── google.svg
│   └── icon128.png
└── package.json

Manifest Configuration

json
{
  "manifest_version": 3,
  "name": "Auth Extension",
  "version": "1.0.0",
  "description": "Extension with Google OAuth",

  "action": {
    "default_popup": "popup/popup.html",
    "default_icon": "icons/icon128.png"
  },

  "permissions": [
    "identity",
    "storage"
  ],

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

  "oauth2": {
    "client_id": "YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com",
    "scopes": [
      "https://www.googleapis.com/auth/userinfo.email",
      "https://www.googleapis.com/auth/userinfo.profile"
    ]
  }
}

HTML Structure

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Auth Extension</title>
  <link rel="stylesheet" href="popup.css">
</head>
<body>
  <!-- Loading State -->
  <div id="loading-view" class="view">
    <div class="spinner"></div>
  </div>

  <!-- Login View -->
  <div id="login-view" class="view hidden">
    <div class="header">
      <img src="../icons/icon128.png" alt="Logo" class="logo">
      <h1>Welcome</h1>
      <p class="subtitle">Sign in to continue</p>
    </div>

    <div class="auth-options">
      <button id="google-btn" class="btn btn-google">
        <img src="../icons/google.svg" alt="Google">
        Continue with Google
      </button>

      <div class="divider">
        <span>or</span>
      </div>

      <form id="email-form">
        <div class="form-group">
          <input
            type="email"
            id="email"
            placeholder="Email address"
            required
          >
        </div>
        <div class="form-group">
          <input
            type="text"
            id="name"
            placeholder="Your name (optional)"
          >
        </div>
        <button type="submit" class="btn btn-primary">
          Continue with Email
        </button>
      </form>
    </div>

    <div id="error-message" class="error hidden"></div>
  </div>

  <!-- Dashboard View -->
  <div id="dashboard-view" class="view hidden">
    <div class="user-header">
      <img id="user-avatar" src="" alt="Avatar" class="avatar">
      <div class="user-info">
        <h2 id="user-name"></h2>
        <p id="user-email"></p>
      </div>
    </div>

    <div class="dashboard-content">
      <div class="stat-card">
        <span class="stat-label">Status</span>
        <span class="stat-value">Active</span>
      </div>
      <div class="stat-card">
        <span class="stat-label">Member Since</span>
        <span id="member-since" class="stat-value"></span>
      </div>
    </div>

    <button id="logout-btn" class="btn btn-secondary">
      Sign Out
    </button>
  </div>

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

CSS Styling

css
/* popup.css */
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  width: 360px;
  min-height: 400px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: #1a1a1a;
}

.view {
  padding: 24px;
  background: white;
  min-height: 400px;
  display: flex;
  flex-direction: column;
}

.hidden {
  display: none !important;
}

/* Loading */
#loading-view {
  justify-content: center;
  align-items: center;
}

.spinner {
  width: 40px;
  height: 40px;
  border: 3px solid #e5e7eb;
  border-top-color: #3b82f6;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

/* Header */
.header {
  text-align: center;
  margin-bottom: 24px;
}

.logo {
  width: 64px;
  height: 64px;
  margin-bottom: 16px;
}

.header h1 {
  font-size: 24px;
  font-weight: 600;
  color: #1a1a1a;
}

.subtitle {
  color: #6b7280;
  margin-top: 4px;
}

/* Auth Options */
.auth-options {
  flex: 1;
}

.btn {
  width: 100%;
  padding: 12px 16px;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  transition: all 0.2s;
}

.btn-google {
  background: white;
  border: 1px solid #e5e7eb;
  color: #374151;
}

.btn-google:hover {
  background: #f9fafb;
  border-color: #d1d5db;
}

.btn-google img {
  width: 18px;
  height: 18px;
}

.btn-primary {
  background: #3b82f6;
  border: none;
  color: white;
}

.btn-primary:hover {
  background: #2563eb;
}

.btn-primary:disabled {
  background: #9ca3af;
  cursor: not-allowed;
}

.btn-secondary {
  background: #f3f4f6;
  border: none;
  color: #374151;
}

.btn-secondary:hover {
  background: #e5e7eb;
}

/* Divider */
.divider {
  display: flex;
  align-items: center;
  margin: 20px 0;
}

.divider::before,
.divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: #e5e7eb;
}

.divider span {
  padding: 0 12px;
  color: #9ca3af;
  font-size: 12px;
}

/* Form */
.form-group {
  margin-bottom: 12px;
}

input {
  width: 100%;
  padding: 12px;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  font-size: 14px;
  transition: border-color 0.2s;
}

input:focus {
  outline: none;
  border-color: #3b82f6;
}

input::placeholder {
  color: #9ca3af;
}

/* Error */
.error {
  margin-top: 16px;
  padding: 12px;
  background: #fee2e2;
  border-radius: 8px;
  color: #991b1b;
  font-size: 14px;
  text-align: center;
}

/* Dashboard */
.user-header {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 24px;
}

.avatar {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: #e5e7eb;
}

.user-info h2 {
  font-size: 18px;
  font-weight: 600;
}

.user-info p {
  color: #6b7280;
  font-size: 14px;
}

.dashboard-content {
  flex: 1;
}

.stat-card {
  display: flex;
  justify-content: space-between;
  padding: 16px;
  background: #f9fafb;
  border-radius: 8px;
  margin-bottom: 12px;
}

.stat-label {
  color: #6b7280;
  font-size: 14px;
}

.stat-value {
  font-weight: 500;
  color: #1a1a1a;
}

JavaScript Implementation

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

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

// Views
const loadingView = document.getElementById('loading-view');
const loginView = document.getElementById('login-view');
const dashboardView = document.getElementById('dashboard-view');

// Login Elements
const googleBtn = document.getElementById('google-btn');
const emailForm = document.getElementById('email-form');
const emailInput = document.getElementById('email');
const nameInput = document.getElementById('name');
const errorMessage = document.getElementById('error-message');

// Dashboard Elements
const userAvatar = document.getElementById('user-avatar');
const userName = document.getElementById('user-name');
const userEmail = document.getElementById('user-email');
const memberSince = document.getElementById('member-since');
const logoutBtn = document.getElementById('logout-btn');

// Check for existing user
async function init() {
  const user = ExtensionLogin.getUser();

  if (user) {
    showDashboard(user);
  } else {
    showLogin();
  }
}

// Google Sign-In
googleBtn.addEventListener('click', async () => {
  googleBtn.disabled = true;
  googleBtn.innerHTML = '<span class="spinner" style="width:20px;height:20px;"></span> Signing in...';
  hideError();

  try {
    const result = await ExtensionLogin.loginWithGoogle();

    if (result.success) {
      showDashboard(result.user);
    } else {
      showError(getErrorMessage(result.error.code));
    }
  } catch (error) {
    showError('Failed to connect. Please try again.');
  } finally {
    googleBtn.disabled = false;
    googleBtn.innerHTML = '<img src="../icons/google.svg" alt="Google"> Continue with Google';
  }
});

// Email Sign-In
emailForm.addEventListener('submit', async (e) => {
  e.preventDefault();

  const email = emailInput.value.trim();
  const name = nameInput.value.trim();

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

  const submitBtn = emailForm.querySelector('button[type="submit"]');
  submitBtn.disabled = true;
  submitBtn.textContent = 'Signing in...';
  hideError();

  try {
    const result = await ExtensionLogin.identify({
      email,
      name: name || undefined
    });

    if (result.success) {
      showDashboard(result.user);
    } else {
      showError(getErrorMessage(result.error.code));
    }
  } catch (error) {
    showError('Failed to connect. Please try again.');
  } finally {
    submitBtn.disabled = false;
    submitBtn.textContent = 'Continue with Email';
  }
});

// Logout
logoutBtn.addEventListener('click', async () => {
  logoutBtn.disabled = true;
  await ExtensionLogin.logout();
  showLogin();
  logoutBtn.disabled = false;
});

// View Helpers
function showLoading() {
  loadingView.classList.remove('hidden');
  loginView.classList.add('hidden');
  dashboardView.classList.add('hidden');
}

function showLogin() {
  loadingView.classList.add('hidden');
  loginView.classList.remove('hidden');
  dashboardView.classList.add('hidden');

  // Clear form
  emailInput.value = '';
  nameInput.value = '';
  hideError();
}

function showDashboard(user) {
  loadingView.classList.add('hidden');
  loginView.classList.add('hidden');
  dashboardView.classList.remove('hidden');

  // Set user info
  userName.textContent = user.name || 'User';
  userEmail.textContent = user.email;

  // Set avatar
  if (user.picture) {
    userAvatar.src = user.picture;
  } else {
    // Generate initials avatar
    const initials = (user.name || user.email)
      .split(' ')
      .map(n => n[0])
      .join('')
      .toUpperCase()
      .slice(0, 2);
    userAvatar.src = `https://ui-avatars.com/api/?name=${initials}&background=3b82f6&color=fff`;
  }

  // Set member since
  const date = new Date(user.createdAt);
  memberSince.textContent = date.toLocaleDateString('en-US', {
    month: 'short',
    year: 'numeric'
  });
}

// Error Helpers
function showError(message) {
  errorMessage.textContent = message;
  errorMessage.classList.remove('hidden');
}

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

function getErrorMessage(code) {
  const messages = {
    'INVALID_EMAIL': 'Please enter a valid email address.',
    'USER_CANCELLED': 'Sign-in was cancelled.',
    'OAUTH_NOT_CONFIGURED': 'Google sign-in is not available.',
    'RATE_LIMITED': 'Too many attempts. Please wait a moment.',
    'NETWORK_ERROR': 'Connection error. Please check your internet.'
  };
  return messages[code] || 'Something went wrong. Please try again.';
}

// Listen for auth changes
ExtensionLogin.onAuthChange((user) => {
  if (user) {
    showDashboard(user);
  } else {
    showLogin();
  }
});

// Initialize
init();

Google Icon SVG

Create icons/google.svg:

xml
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
  <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
  <path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
  <path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
  <path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
</svg>

Testing the Extension

  1. Build your extension:

    bash
    npm run build
  2. Load in Chrome:

    • Go to chrome://extensions
    • Enable "Developer mode"
    • Click "Load unpacked"
    • Select your build folder
  3. Test both authentication methods:

    • Try email sign-in
    • Try Google sign-in
    • Verify logout works
    • Check session persists after closing popup

Next Steps

Built for Chrome Extension Developers