Universal OAuth 2.0 + PKCE Client for Modern Applications
A comprehensive, production-ready OAuth 2.0 + PKCE client library that works seamlessly across Browser, Node.js, React Native, and Web Workers. Built with security, developer experience, and universal compatibility in mind.
Unlike most OAuth libraries that lock you into a specific environment, Zenuxs OAuth works everywhere:
- β Browser (Chrome, Firefox, Safari, Edge)
- β Node.js (Server-side authentication)
- β React Native (iOS & Android)
- β Web Workers (Background authentication)
- π PKCE (RFC 7636) - Protection against authorization code interception
- π‘οΈ CSRF Protection - Built-in state parameter validation
- π Secure Token Storage - Flexible storage options (Memory, Session, Local)
- β‘ Automatic Token Refresh - Seamless token renewal before expiration
- π« Token Revocation - Properly invalidate tokens on logout
- π¦ Zero Dependencies - Lightweight and fast
- π― TypeScript Support - Full type definitions included
- π Multiple Auth Flows - Redirect, Popup, and Manual flows
- π‘ Event System - React to authentication state changes
- π¨ Framework Agnostic - Works with React, Vue, Angular, Svelte, or vanilla JS
- π Comprehensive Documentation - Clear examples and API reference
<script src="https://unpkg.com/zenuxs-oauth@2.3.1/dist/zenux-oauth.min.js"></script>npm install zenuxs-oauth
# or
yarn add zenuxs-oauthimport ZenuxOAuth from 'zenuxs-oauth';const ZenuxOAuth = require('zenuxs-oauth');const oauth = new ZenuxOAuth({
clientId: "your-client-id",
authServer: "https://api.auth.zenuxs.in",
redirectUri: window.location.origin + "/callback.html",
scopes: "openid profile email",
storage: "sessionStorage"
});
// Login with popup
async function login() {
try {
const tokens = await oauth.login({ popup: true });
console.log("Logged in!", tokens);
} catch (error) {
console.error("Login failed:", error);
}
}
// Get user info
async function getUserInfo() {
const user = await oauth.getUserInfo();
console.log("User:", user);
}
// Logout
async function logout() {
await oauth.logout({ revokeTokens: true });
}const ZenuxOAuth = require('zenuxs-oauth');
const oauth = new ZenuxOAuth({
clientId: process.env.CLIENT_ID,
authServer: "https://api.auth.zenuxs.in",
redirectUri: "https://yourapp.com/callback",
scopes: "openid profile email",
storage: "memory",
fetchFunction: require('node-fetch')
});
// Express.js route
app.get('/auth/login', async (req, res) => {
const authData = await oauth.login();
req.session.state = authData.state;
req.session.codeVerifier = authData.codeVerifier;
res.redirect(authData.url);
});
app.get('/auth/callback', async (req, res) => {
const tokens = await oauth.handleCallback(req.url);
req.session.tokens = tokens;
res.redirect('/dashboard');
});import ZenuxOAuth from 'zenuxs-oauth';
import { Linking } from 'react-native';
const oauth = new ZenuxOAuth({
clientId: "your-client-id",
authServer: "https://api.auth.zenuxs.in",
redirectUri: "myapp://callback",
scopes: "openid profile email",
storage: "memory"
});
async function login() {
const authData = await oauth.login();
await Linking.openURL(authData.url);
// Listen for callback
Linking.addEventListener('url', async (event) => {
if (event.url.startsWith('myapp://callback')) {
const tokens = await oauth.handleCallback(event.url);
console.log("Tokens:", tokens);
}
});
}// Redirects the entire page
oauth.login();// Opens authentication in a popup window
const tokens = await oauth.login({
popup: true,
popupWidth: 600,
popupHeight: 700
});// Get authorization URL for manual handling
const authData = await oauth.login();
console.log("Redirect user to:", authData.url);
// Handle callback manually with authData.state and authData.codeVerifierconst oauth = new ZenuxOAuth({
clientId: "your-client-id",
authServer: "https://api.auth.zenuxs.in",
autoRefresh: true, // Enable auto-refresh
refreshThreshold: 300 // Refresh 5 minutes before expiry
});
// Listen to refresh events
oauth.on('tokenRefresh', (newTokens) => {
console.log("Tokens automatically refreshed!");
});// Authentication events
oauth.on('login', (tokens) => {
console.log("User logged in");
});
oauth.on('logout', () => {
console.log("User logged out");
});
// Token management events
oauth.on('tokenRefresh', (newTokens) => {
console.log("Tokens refreshed");
});
oauth.on('tokenExpired', () => {
console.log("Token expired");
});
// Error handling
oauth.on('error', (error) => {
console.error("OAuth error:", error);
});
// State changes
oauth.on('stateChange', (change) => {
console.log("State changed:", change);
});Create a beautiful, functional callback page with zero effort:
<!DOCTYPE html>
<html>
<head>
<title>OAuth Callback</title>
</head>
<body>
<div id="zenux-oauth-callback-container"></div>
<script src="https://unpkg.com/zenuxs-oauth@2.3.1/dist/zenux-oauth.min.js"></script>
<script>
// Automatically handles OAuth callback and closes popup
window.zenuxOAuthCallback = new ZenuxOAuthCallbackHandler({
debug: true,
autoClose: true,
autoCloseDelay: 2000,
successMessage: "Authentication successful! Redirecting...",
errorMessage: "Authentication failed. Please try again."
});
</script>
</body>
</html>// Session Storage (default) - survives page reload, cleared on tab close
storage: "sessionStorage"
// Local Storage - persists across browser sessions
storage: "localStorage"
// Memory Storage - cleared on page reload (best for Node.js/React Native)
storage: "memory"
// Custom prefix for storage keys
storagePrefix: "myapp_auth_"// Check authentication status
if (oauth.isAuthenticated()) {
console.log("User is authenticated");
}
// Get current tokens
const tokens = oauth.getTokens();
// Check if token is expired
if (oauth.isTokenExpired()) {
await oauth.refreshTokens();
}
// Manually refresh tokens
const newTokens = await oauth.refreshTokens();
// Revoke specific token
await oauth.revokeToken(tokens.access_token, 'access_token');
// Revoke all tokens on logout
await oauth.logout({ revokeTokens: true });
// Introspect token validity
const tokenInfo = await oauth.introspectToken();
console.log("Token active:", tokenInfo.active);// Get user profile from userinfo endpoint
const user = await oauth.getUserInfo();
console.log(user.name, user.email, user.picture);
// Multiple userinfo endpoints supported
const oauth = new ZenuxOAuth({
clientId: "your-client-id",
authServer: "https://api.auth.zenuxs.in",
userinfoEndpoint: "/oauth/userinfo" // or custom endpoint
});// Export session (for cross-device sync or persistence)
const sessionData = oauth.exportSession();
localStorage.setItem('oauth_backup', JSON.stringify(sessionData));
// Import session (restore authentication state)
const savedSession = JSON.parse(localStorage.getItem('oauth_backup'));
oauth.importSession(savedSession);// Get pre-configured fetch with automatic token injection
const authFetch = oauth.getAuthenticatedFetch();
// Use it like regular fetch
const response = await authFetch('https://api.yourapp.com/protected', {
method: 'GET'
});
// Automatically adds Authorization header and handles token refreshconst oauth = new ZenuxOAuth({
// Required
clientId: "your-client-id",
// Server Configuration
authServer: "https://api.auth.zenuxs.in",
authorizeEndpoint: "/oauth/authorize",
tokenEndpoint: "/oauth/token",
userinfoEndpoint: "/oauth/userinfo",
revokeEndpoint: "/oauth/revoke",
introspectEndpoint: "/oauth/introspect",
// OAuth Parameters
redirectUri: window.location.origin + "/callback.html",
scopes: "openid profile email offline_access",
responseType: "code",
// Security
usePKCE: true, // Enable PKCE
useCSRF: true, // Enable CSRF protection (browser only)
validateState: true, // Validate state parameter
// Storage
storage: "sessionStorage", // sessionStorage | localStorage | memory
storagePrefix: "zenux_oauth_",
// Token Management
autoRefresh: true, // Enable automatic token refresh
refreshThreshold: 300, // Refresh 5 minutes before expiry
// UI Configuration (Browser only)
popupWidth: 600,
popupHeight: 700,
popupFeatures: "toolbar=no,location=no,status=no,menubar=no",
// Lifecycle Callbacks
onBeforeLogin: (config) => {
console.log("About to login");
},
onAfterLogin: (tokens) => {
console.log("Login successful");
},
onBeforeLogout: () => {
console.log("About to logout");
},
onAfterLogout: () => {
console.log("Logout complete");
},
// Additional Parameters
extraAuthParams: {
prompt: "login",
display: "popup"
},
extraTokenParams: {
client_secret: "secret" // Only for confidential clients
},
// Environment
environment: "browser", // Auto-detected: browser | node | react-native | worker
fetchFunction: fetch, // Custom fetch implementation
debug: true // Enable debug logging
});import { useState, useEffect } from 'react';
import ZenuxOAuth from 'zenuxs-oauth';
function useZenuxAuth(config) {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [oauth] = useState(() => new ZenuxOAuth(config));
useEffect(() => {
setIsAuthenticated(oauth.isAuthenticated());
oauth.on('login', async (tokens) => {
setIsAuthenticated(true);
const userInfo = await oauth.getUserInfo();
setUser(userInfo);
});
oauth.on('logout', () => {
setIsAuthenticated(false);
setUser(null);
});
setLoading(false);
return () => {
oauth.off('login');
oauth.off('logout');
};
}, [oauth]);
return {
isAuthenticated,
user,
loading,
login: (options) => oauth.login(options),
logout: (options) => oauth.logout(options),
getTokens: () => oauth.getTokens()
};
}
// Usage in component
function App() {
const { isAuthenticated, user, loading, login, logout } = useZenuxAuth({
clientId: "your-client-id",
authServer: "https://api.auth.zenuxs.in",
redirectUri: window.location.origin + "/callback.html",
scopes: "openid profile email"
});
if (loading) return <div>Loading...</div>;
return (
<div>
{isAuthenticated ? (
<div>
<h1>Welcome, {user?.name}!</h1>
<button onClick={() => logout({ revokeTokens: true })}>
Logout
</button>
</div>
) : (
<button onClick={() => login({ popup: true })}>
Login with Zenuxs
</button>
)}
</div>
);
}import { ref, onMounted, onUnmounted } from 'vue';
import ZenuxOAuth from 'zenuxs-oauth';
export function useZenuxAuth(config) {
const isAuthenticated = ref(false);
const user = ref(null);
const loading = ref(true);
let oauth;
onMounted(() => {
oauth = new ZenuxOAuth(config);
isAuthenticated.value = oauth.isAuthenticated();
oauth.on('login', async (tokens) => {
isAuthenticated.value = true;
user.value = await oauth.getUserInfo();
});
oauth.on('logout', () => {
isAuthenticated.value = false;
user.value = null;
});
loading.value = false;
});
onUnmounted(() => {
if (oauth) {
oauth.off('login');
oauth.off('logout');
}
});
return {
isAuthenticated,
user,
loading,
login: (options) => oauth.login(options),
logout: (options) => oauth.logout(options)
};
}import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import ZenuxOAuth from 'zenuxs-oauth';
@Injectable({
providedIn: 'root'
})
export class AuthService {
private oauth: any;
private isAuthenticatedSubject = new BehaviorSubject<boolean>(false);
private userSubject = new BehaviorSubject<any>(null);
public isAuthenticated$: Observable<boolean> = this.isAuthenticatedSubject.asObservable();
public user$: Observable<any> = this.userSubject.asObservable();
constructor() {
this.oauth = new ZenuxOAuth({
clientId: 'your-client-id',
authServer: 'https://api.auth.zenuxs.in',
redirectUri: window.location.origin + '/callback.html',
scopes: 'openid profile email'
});
this.isAuthenticatedSubject.next(this.oauth.isAuthenticated());
this.oauth.on('login', async (tokens: any) => {
this.isAuthenticatedSubject.next(true);
const user = await this.oauth.getUserInfo();
this.userSubject.next(user);
});
this.oauth.on('logout', () => {
this.isAuthenticatedSubject.next(false);
this.userSubject.next(null);
});
}
async login(options?: any): Promise<void> {
await this.oauth.login(options);
}
async logout(options?: any): Promise<void> {
await this.oauth.logout(options);
}
getTokens() {
return this.oauth.getTokens();
}
}| Feature | Zenuxs OAuth | Auth0-SPA | Firebase Auth | Hello.js | OAuth2-Client |
|---|---|---|---|---|---|
| Universal Support | β All platforms | β Browser only | β Browser only | ||
| PKCE Support | β Built-in | β Yes | β Yes | β No | |
| Popup Flow | β Native | β Yes | β No | β Yes | β No |
| Auto Token Refresh | β Configurable | β Yes | β Yes | β No | |
| Event System | β Comprehensive | β Good | β No | β No | |
| Zero Dependencies | β Yes | β No | β No | β Yes | β No |
| TypeScript | β Full support | β Yes | β Yes | β No | β Yes |
| Bundle Size | π’ ~15KB | π‘ ~50KB | π΄ ~150KB | π’ ~10KB | π‘ ~30KB |
| React Native | β Native | β No | β Separate pkg | β No | β No |
| Web Workers | β Yes | β No | β No | β No | β No |
| Custom Storage | β Flexible | β Fixed | β Fixed | ||
| Token Revocation | β Built-in | β Yes | β No | ||
| Session Export | β Yes | β No | β No | β No | β No |
| Learning Curve | π’ Low | π‘ Medium | π‘ Medium | π’ Low | π΄ High |
| Provider Lock-in | β None | π΄ Auth0 only | π΄ Firebase only | β None | |
| License | β MIT | β MIT | β MIT | β MIT |
- True Universal Support - One library for browser, Node.js, React Native, and Web Workers
- Zero Dependencies - No bloat, just pure OAuth functionality
- Developer Experience - Intuitive API with comprehensive events
- Flexibility - Works with any OAuth 2.0 provider, not locked to a specific service
- Modern Architecture - Built with PKCE, CSRF protection, and auto-refresh from the ground up
- Session Portability - Export/import sessions for cross-device authentication
- Lightweight - Only ~15KB minified + gzipped
const oauth = new ZenuxOAuth({
clientId: "your-client-id",
usePKCE: true // Always enabled by default
});const oauth = new ZenuxOAuth({
clientId: "your-client-id",
useCSRF: true, // Browser only
validateState: true // Verify state parameter
});// For web apps: Use sessionStorage (cleared on tab close)
storage: "sessionStorage"
// For SPAs with persistence: Use localStorage with caution
storage: "localStorage"
// For server-side: Always use memory storage
storage: "memory"await oauth.logout({
revokeTokens: true // Properly invalidate tokens
});oauth.on('tokenRefresh', (newTokens) => {
// Update your application state
updateAuthState(newTokens);
});
oauth.on('error', async (error) => {
if (error.code === 'TOKEN_REFRESH_FAILED') {
// Force re-login if refresh fails
await oauth.logout();
redirectToLogin();
}
});try {
const tokens = await oauth.login({
popup: true,
timeout: 300000 // 5 minutes timeout
});
} catch (error) {
if (error.code === 'LOGIN_TIMEOUT') {
console.log('Login took too long');
}
}try {
await oauth.login({ popup: true });
} catch (error) {
switch (error.code) {
case 'INVALID_CONFIG':
// Configuration validation failed
break;
case 'FETCH_UNAVAILABLE':
// Fetch API not available
break;
case 'POPUP_BLOCKED':
// Browser blocked popup window
alert('Please allow popups for this site');
break;
case 'AUTH_CANCELLED':
// User closed popup or cancelled authentication
console.log('User cancelled login');
break;
case 'LOGIN_TIMEOUT':
// Login process exceeded timeout
console.log('Login timeout');
break;
case 'STATE_MISMATCH':
// CSRF protection: state parameter mismatch
console.error('Security error detected');
break;
case 'NO_AUTH_CODE':
// Authorization code not received
break;
case 'TOKEN_EXCHANGE_FAILED':
// Failed to exchange code for tokens
break;
case 'TOKEN_REFRESH_FAILED':
// Failed to refresh access token
await oauth.logout();
break;
case 'NO_REFRESH_TOKEN':
// No refresh token available
break;
case 'NO_ACCESS_TOKEN':
// No access token available
break;
case 'USERINFO_FAILED':
// Failed to fetch user information
break;
case 'REVOKE_FAILED':
// Token revocation failed
break;
case 'INTROSPECT_FAILED':
// Token introspection failed
break;
default:
console.error('Unknown error:', error);
}
}// Global error handler
oauth.on('error', (error) => {
console.error('OAuth Error:', {
code: error.code,
message: error.message,
details: error.details,
environment: error.environment,
timestamp: error.timestamp
});
// Send to error tracking service
trackError(error);
});new ZenuxOAuth(config)login(options?)- Start OAuth flowhandleCallback(url)- Process OAuth callbacklogout(options?)- Logout user
getTokens()- Get current tokensisAuthenticated()- Check authentication statusisTokenExpired()- Check if token is expiredrefreshTokens()- Manually refresh tokensrevokeToken(token, tokenType)- Revoke specific token
getUserInfo()- Fetch user profileintrospectToken(token?)- Validate token
getSessionState()- Get current session stateexportSession()- Export session dataimportSession(data)- Import session data
on(event, handler)- Add event listeneroff(event, handler)- Remove event listener
getAuthenticatedFetch()- Get authenticated fetch functionupdateConfig(config)- Update configurationdestroy()- Cleanup resources
ZenuxOAuth.create(config)- Create new instanceZenuxOAuth.getInstance(config)- Get singleton instanceZenuxOAuth.destroyInstance()- Destroy singletonZenuxOAuth.createCallbackHandler(config)- Create callback handler
import ZenuxOAuth from 'zenuxs-oauth';
describe('ZenuxOAuth', () => {
let oauth;
beforeEach(() => {
oauth = new ZenuxOAuth({
clientId: 'test-client-id',
authServer: 'https://test-auth.example.com',
storage: 'memory'
});
});
afterEach(() => {
oauth.destroy();
});
test('should initialize correctly', () => {
expect(oauth).toBeDefined();
expect(oauth.isAuthenticated()).toBe(false);
});
test('should handle login flow', async () => {
const authData = await oauth.login();
expect(authData).toHaveProperty('url');
expect(authData).toHaveProperty('state');
expect(authData).toHaveProperty('codeVerifier');
});
test('should emit login event on successful authentication', (done) => {
oauth.on('login', (tokens) => {
expect(tokens).toHaveProperty('access_token');
done();
});
// Simulate login...
});
});We welcome contributions! Please see our Contributing Guide for details.
# Clone repository
git clone https://github.com/developers-rs5/zenuxs-oauth.git
cd zenuxs-oauth
# Install dependencies
npm install
# Run tests
npm test
# Build library
npm run build
# Run examples
npm run devMIT License Β© 2025 Zenuxs Team
Developed by Rishabh Sharma (rs)
- Documentation: https://docs.zenuxs.in
- GitHub: https://github.com/developers-rs5/zenuxs-oauth
- NPM: https://www.npmjs.com/package/zenuxs-oauth
- Discord: https://discord.zenuxs.in
- Issues: https://github.com/developers-rs5/zenuxs-oauth/issues
Need help? We're here for you:
- π Documentation: Check our comprehensive docs
- π¬ Discord: Join our community server
- π Issues: Report bugs on GitHub
- π§ Email: support@zenuxs.in
Special thanks to all contributors and the OAuth 2.0 community for making secure authentication accessible to everyone.
Made with β€οΈ by the Zenuxs Team