@pixpilot/pkce-auth-chrome
v0.10.0
Published
Chrome Extension PKCE authentication package with seamless OAuth 2.0 integration
Downloads
24
Readme
@pixpilot/pkce-auth-chrome
PKCE OAuth 2.0 authentication for Chrome extensions with automatic state management.
Installation
pnpm add @pixpilot/pkce-auth-chromeQuick Start
Recommended: Class-Based API
The easiest way to use this library. Configure once and use for both steps:
import { ChromePKCEAuthClient } from '@pixpilot/pkce-auth-chrome';
// Configure once with shared settings
const auth = new ChromePKCEAuthClient({
loginUrl: 'https://oauth.example.com/authorize',
redirectUrl: chrome.runtime.getURL('callback.html'),
exchangeUrl: 'https://oauth.example.com/token',
clientId: 'your_client_id',
scope: 'read write',
});
// In background script: start flow
await auth.startAuthFlow();
// In callback.html: handle callback
const params = new URLSearchParams(window.location.search);
const tokens = await auth.handleAuthCallback({
authCode: params.get('code')!,
receivedState: params.get('state')!,
});Alternative: Individual Functions
Use individual functions if you need more control:
import { handleAuthCallback, startAuthFlow } from '@pixpilot/pkce-auth-chrome';
// 1. Start auth flow (generates PKCE, saves state, opens auth tab)
await startAuthFlow({
loginUrl: 'https://oauth.example.com/authorize',
redirectUrl: chrome.runtime.getURL('callback.html'),
exchangeUrl: 'https://oauth.example.com/token',
clientId: 'your_client_id',
scope: 'read write',
});
// 2. In callback.html, exchange code for tokens
const params = new URLSearchParams(window.location.search);
const tokens = await handleAuthCallback({
authCode: params.get('code')!,
receivedState: params.get('state')!,
exchangeUrl: 'https://oauth.example.com/token',
});Complete Example
manifest.json:
{
"permissions": ["storage", "tabs"],
"host_permissions": ["https://api.example.com/*"]
}background.js (start flow):
import { ChromePKCEAuthClient } from '@pixpilot/pkce-auth-chrome';
const auth = new ChromePKCEAuthClient({
loginUrl: 'https://oauth.example.com/authorize',
redirectUrl: chrome.runtime.getURL('callback.html'),
exchangeUrl: 'https://oauth.example.com/token',
clientId: 'your_client_id',
scope: 'read write',
});
await auth.startAuthFlow();callback.html:
import { ChromePKCEAuthClient } from '@pixpilot/pkce-auth-chrome';
const auth = new ChromePKCEAuthClient({
loginUrl: 'https://oauth.example.com/authorize',
redirectUrl: chrome.runtime.getURL('callback.html'),
exchangeUrl: 'https://oauth.example.com/token',
clientId: 'your_client_id',
scope: 'read write',
});
const params = new URLSearchParams(window.location.search);
if (params.get('code') && params.get('state')) {
await auth.handleAuthCallback({
authCode: params.get('code')!,
receivedState: params.get('state')!,
});
window.close();
}API
Auth Flow
startAuthFlow(config)- Start OAuth flowconfig.allowHttpLocalhost?: boolean- Allow HTTP localhost for development per RFC 8252 (default:false)
handleAuthCallback(config)- Exchange code for tokensconfig.authCode: string- The authorization code from the OAuth providerconfig.receivedState: string- The state parameter from the OAuth providerconfig.exchangeUrl: string- The OAuth token exchange endpoint URLconfig.clientId?: string- Optional OAuth client ID for token exchangeconfig.redirectUrl?: string- Optional redirect URI for token exchangeconfig.allowHttpLocalhost?: boolean- Allow HTTP localhost for development per RFC 8252
Token Management
getTokenData()- Get tokens with metadatasaveTokens(accessToken, refreshToken, expiresIn?, tokenType?)- Save tokensisTokenExpired()- Check if token is expiredclearAuthData()- Clear all auth data
Storage
saveAuthState(state, codeVerifier)- Save auth stategetAuthState(state)- Get auth state by state parametermarkCodeAsUsed(code)- Mark authorization code as usedisCodeUsed(code)- Check if code was used
Tab Management
openAuthTab(url)- Open URL in new tab
URL Validation
This package uses secure-by-default URL validation to protect against common OAuth vulnerabilities.
Security Defaults
Default behavior (secure by default):
- validateLoginUrl & validateRedirectUrl: HTTPS and chrome-extension:// only
- validateExchangeUrl: HTTPS only
💡 RFC 8252 Compliance: Set allowHttpLocalhost: true for development to enable OAuth 2.0 standard localhost support.
Secure Defaults (Production-Ready)
import { startAuthFlow } from '@pixpilot/pkce-auth-chrome';
// Default validators are secure (HTTPS-only)
await startAuthFlow({
loginUrl: 'https://auth.example.com/authorize', // ✓ Secure
redirectUrl: chrome.runtime.getURL('callback.html'),
exchangeUrl: 'https://oauth.example.com/token',
// allowHttpLocalhost: false (default) - Production-safe
});
// For local development, enable RFC 8252 localhost support
await startAuthFlow({
loginUrl: 'http://localhost:3000/authorize', // ✓ Allowed per RFC 8252
redirectUrl: chrome.runtime.getURL('callback.html'),
exchangeUrl: 'https://oauth.example.com/token',
allowHttpLocalhost: true, // OAuth 2.0 for Native Apps standard
});✅ Allowed URLs:
- HTTPS URLs (production APIs)
chrome-extension://URLs (your extension)http://localhostorhttp://127.0.0.1(local dev servers)
❌ Blocked URLs:
- Other HTTP URLs (security risk)
Production Mode (Recommended)
Production extensions should use the secure defaults (no localhost allowed):
import { startAuthFlow } from '@pixpilot/pkce-auth-chrome';
await startAuthFlow({
loginUrl: 'https://auth.example.com/authorize',
redirectUrl: chrome.runtime.getURL('callback.html'),
exchangeUrl: 'https://auth.example.com/token',
// allowHttpLocalhost: false (default) - Production-safe
});Or use explicit production validators:
import { getProductionValidator, startAuthFlow } from '@pixpilot/pkce-auth-chrome';
await startAuthFlow({
loginUrl: 'https://auth.example.com/authorize',
redirectUrl: chrome.runtime.getURL('callback.html'),
exchangeUrl: 'https://auth.example.com/token',
validateLoginUrl: getProductionValidator(), // No localhost allowed
validateExchangeUrl: getProductionValidator(), // CRITICAL: Secure token exchange
});Or use specific validators:
import { isHttpsOrChromeExtensionUrl, isHttpsUrl } from '@pixpilot/pkce-auth-chrome';
await startAuthFlow({
loginUrl: 'https://auth.example.com/authorize',
validateLoginUrl: isHttpsOrChromeExtensionUrl, // HTTPS or chrome-extension://
validateExchangeUrl: isHttpsUrl, // HTTPS only
});Available Validators
import {
getDefaultValidator,
getProductionValidator,
isChromeExtensionUrl,
isHttpsOrChromeExtensionOrLocalhostUrl,
isHttpsOrChromeExtensionUrl,
isHttpsUrl,
} from '@pixpilot/pkce-auth-chrome';
// Recommended: Production-safe validator with auto-detection
const validator = getProductionValidator();
// Development: Default with production warnings
const devValidator = getDefaultValidator();
// Custom combinations
await startAuthFlow({
loginUrl: 'https://auth.example.com',
validateLoginUrl: isHttpsUrl, // HTTPS only
validateRedirectUrl: isHttpsOrChromeExtensionUrl, // HTTPS or chrome-extension://
validateExchangeUrl: isHttpsOrChromeExtensionOrLocalhostUrl, // Dev mode
});Custom Validators
import type { UrlValidator } from '@pixpilot/pkce-auth-chrome';
// Your custom validation logic
const myValidator: UrlValidator = async (url: string) =>
url.startsWith('https://trusted-domain.com');
await startAuthFlow({
loginUrl: 'https://trusted-domain.com/auth',
validateLoginUrl: myValidator,
});Security Best Practices
Never use localhost validation in production builds
// ❌ BAD: Allows HTTP interception in production await startAuthFlow({ exchangeUrl: 'http://localhost:3000/token', }); // ✅ GOOD: Use getProductionValidator() or explicit HTTPS await startAuthFlow({ exchangeUrl: 'https://auth.example.com/token', validateExchangeUrl: getProductionValidator(), });Token exchange URL is most critical
- The
exchangeUrlreceives your authorization code - Must be HTTPS in production to prevent code interception
- Consider using
isHttpsUrlfor strictest validation
- The
Use environment detection
import { startAuthFlow } from '@pixpilot/pkce-auth-chrome'; // Auto-detects production vs development await startAuthFlow({ loginUrl: 'https://auth.example.com/authorize', redirectUrl: chrome.runtime.getURL('callback.html'), exchangeUrl: 'https://auth.example.com/token', allowHttpLocalhost: process.env.NODE_ENV !== 'production', });
Security Features
- ✅ Secure URL validation (HTTPS, chrome-extension://, localhost in dev)
- ✅ PKCE with SHA-256 challenge
- ✅ CSRF protection with state parameter
- ✅ Authorization code replay prevention
- ✅ State expiration (10 minutes)
- ✅ Token expiration tracking
- ✅ Automatic cleanup of used codes
- ✅ Open redirect protection
- ✅ Credential leak prevention
License
MIT
