Package Exports
- vista-auth
- vista-auth/client
- vista-auth/database
- vista-auth/guards
- vista-auth/middleware
- vista-auth/server
- vista-auth/ui
Readme
🔐 Vista Auth
Simple, powerful, and secure authentication for React apps
Works with any framework • Any database • Zero configuration needed
✨ Why Vista Auth?
Vista Auth is a lightweight, production-ready authentication solution that takes 5 minutes to set up and works with any React framework and any database.
Key Features
- 🚀 Universal Compatibility - Works with Next.js, Remix, Vite, CRA, Express
- 💾 Database Agnostic - Prisma, MongoDB, Supabase, PostgreSQL, Firebase, or none
- 🔒 Production-Ready Security - bcrypt hashing, JWT tokens, secure sessions
- 🎯 Minimal Code - 150 lines vs 500+ lines of other solutions
- 🕵️ Built-in RBAC - Role-based access control with route guards and middleware
- ⚡ Real-Time Sync - WebSocket support for multi-tab/device synchronization
- 🌐 Offline Support - IndexedDB fallback for offline authentication
- 🎨 UI Helpers Included - Toast notifications and error messages out-of-the-box
- 📦 CLI Auto-Setup - One command to get started
- 🔧 Zero Config Required - Sensible defaults, customize when needed
📦 Installation
npm install vista-authyarn add vista-authpnpm add vista-auth🚀 Quick Start
Step 1: Initialize with CLI
Run the interactive setup wizard:
npx vista-auth initThis creates:
- ✅
vista-auth.config.js- Server configuration - ✅
app/api/auth/route.js- API endpoints - ✅
providers.jsx- Client provider setup - ✅ Example components
Step 2: Wrap Your App
Next.js App Router:
// app/layout.tsx
import { AuthProvider } from 'vista-auth/client';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<AuthProvider apiEndpoint="/api/auth">
{children}
</AuthProvider>
</body>
</html>
);
}Next.js Pages Router:
// pages/_app.tsx
import { AuthProvider } from 'vista-auth/client';
import type { AppProps } from 'next/app';
export default function App({ Component, pageProps }: AppProps) {
return (
<AuthProvider apiEndpoint="/api/auth">
<Component {...pageProps} />
</AuthProvider>
);
}Vite/CRA:
// src/main.tsx or src/index.tsx
import { AuthProvider } from 'vista-auth/client';
ReactDOM.createRoot(document.getElementById('root')!).render(
<AuthProvider apiEndpoint="http://localhost:3000/api/auth">
<App />
</AuthProvider>
);Step 3: Create Auth API Route
Next.js (App Router):
// app/api/auth/route.ts
import { auth } from '@/vista-auth.config';
export async function POST(request: Request) {
const { action, ...data } = await request.json();
switch (action) {
case 'signIn':
return Response.json(await auth.signIn(data));
case 'signUp':
return Response.json(await auth.signUp(data));
case 'signOut':
return Response.json(await auth.signOut(data.sessionId));
case 'getSession':
return Response.json(await auth.getSession(data.token));
default:
return Response.json({ success: false, error: 'Invalid action' }, { status: 400 });
}
}Express:
// server.js
import express from 'express';
import { auth } from './vista-auth.config';
const app = express();
app.use(express.json());
app.post('/api/auth', async (req, res) => {
const { action, ...data } = req.body;
switch (action) {
case 'signIn':
res.json(await auth.signIn(data));
break;
case 'signUp':
res.json(await auth.signUp(data));
break;
case 'signOut':
res.json(await auth.signOut(data.sessionId));
break;
case 'getSession':
res.json(await auth.getSession(data.token));
break;
default:
res.status(400).json({ success: false, error: 'Invalid action' });
}
});
app.listen(3000);Step 4: Use in Components
'use client';
import { useAuth } from 'vista-auth/client';
export default function LoginPage() {
const { signIn, signUp, user, isAuthenticated, isLoading } = useAuth();
if (isLoading) {
return <div>Loading...</div>;
}
if (isAuthenticated) {
return (
<div>
<h1>Welcome, {user?.name}!</h1>
<p>Email: {user?.email}</p>
</div>
);
}
const handleSignIn = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
await signIn({
email: formData.get('email') as string,
password: formData.get('password') as string,
});
};
return (
<form onSubmit={handleSignIn}>
<input name="email" type="email" placeholder="Email" required />
<input name="password" type="password" placeholder="Password" required />
<button type="submit">Sign In</button>
</form>
);
}That's it! 🎉 You now have authentication working.
📚 Table of Contents
- Database Integration
- Role-Based Access Control
- Middleware
- UI Helpers
- Real-Time Session Sync
- Offline Support
- API Reference
- Complete Examples
- Configuration
- Security Features
- Comparison
🗄️ Database Integration
Vista Auth works with any database or no database at all. Choose the adapter that fits your stack:
Prisma
// vista-auth.config.ts
import { createVistaAuth } from 'vista-auth/server';
import { createPrismaAdapter } from 'vista-auth/database';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export const auth = createVistaAuth({
database: createPrismaAdapter(prisma),
jwtSecret: process.env.VISTA_AUTH_SECRET!,
});Prisma Schema:
model User {
id String @id @default(cuid())
email String @unique
passwordHash String
name String?
roles String[]
permissions String[]
metadata Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
sessions Session[]
}
model Session {
id String @id @default(cuid())
userId String
token String @unique
expiresAt DateTime
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}MongoDB
import { createMongoAdapter } from 'vista-auth/database';
import { MongoClient } from 'mongodb';
const client = new MongoClient(process.env.MONGODB_URI!);
const db = client.db('myapp');
export const auth = createVistaAuth({
database: createMongoAdapter(db),
jwtSecret: process.env.VISTA_AUTH_SECRET!,
});Supabase
import { createSupabaseAdapter } from 'vista-auth/database';
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
export const auth = createVistaAuth({
database: createSupabaseAdapter(supabase),
jwtSecret: process.env.VISTA_AUTH_SECRET!,
});PostgreSQL (Direct)
import { createPostgresAdapter } from 'vista-auth/database';
import { Pool } from 'pg';
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
export const auth = createVistaAuth({
database: createPostgresAdapter(pool),
jwtSecret: process.env.VISTA_AUTH_SECRET!,
});Firebase
import { createFirebaseAdapter } from 'vista-auth/database';
import { initializeApp } from 'firebase-admin/app';
import { getFirestore } from 'firebase-admin/firestore';
const app = initializeApp();
const db = getFirestore(app);
export const auth = createVistaAuth({
database: createFirebaseAdapter(db),
jwtSecret: process.env.VISTA_AUTH_SECRET!,
});Custom Database Adapter
Implement your own database adapter for any database:
import { DatabaseAdapter } from 'vista-auth/database';
const customAdapter: DatabaseAdapter = {
async findUserByEmail(email: string) {
return await db.query('SELECT * FROM users WHERE email = $1', [email]);
},
async findUserById(id: string) {
return await db.query('SELECT * FROM users WHERE id = $1', [id]);
},
async createUser(data) {
const user = await db.query(
'INSERT INTO users (email, password_hash, name, roles, permissions) VALUES ($1, $2, $3, $4, $5) RETURNING *',
[data.email, data.passwordHash, data.name, data.roles || [], data.permissions || []]
);
return user.rows[0];
},
async updateUser(id: string, data) {
const user = await db.query(
'UPDATE users SET name = $1, metadata = $2 WHERE id = $3 RETURNING *',
[data.name, data.metadata, id]
);
return user.rows[0];
},
async createSession(data) {
const session = await db.query(
'INSERT INTO sessions (user_id, token, expires_at) VALUES ($1, $2, $3) RETURNING *',
[data.userId, data.token, data.expiresAt]
);
return session.rows[0];
},
async findSessionByToken(token: string) {
const result = await db.query('SELECT * FROM sessions WHERE token = $1', [token]);
return result.rows[0];
},
async deleteSession(sessionId: string) {
await db.query('DELETE FROM sessions WHERE id = $1', [sessionId]);
},
async deleteExpiredSessions() {
await db.query('DELETE FROM sessions WHERE expires_at < NOW()');
},
};
export const auth = createVistaAuth({
database: customAdapter,
jwtSecret: process.env.VISTA_AUTH_SECRET!,
});No Database (Stateless JWT Only)
For serverless or simple applications:
export const auth = createVistaAuth({
database: null, // No database required
jwtSecret: process.env.VISTA_AUTH_SECRET!,
});🕵️ Role-Based Access Control (RBAC)
Vista Auth includes powerful RBAC features out-of-the-box.
Protect Routes with Components
import { ProtectedRoute } from 'vista-auth/guards';
function AdminDashboard() {
return (
<ProtectedRoute
roles={['admin']}
redirect="/login"
fallback={<div>Access Denied</div>}
>
<div>
<h1>Admin Dashboard</h1>
<p>Only admins can see this</p>
</div>
</ProtectedRoute>
);
}Route Guards Hooks
Require Authentication:
import { useRequireAuth } from 'vista-auth/guards';
function DashboardPage() {
useRequireAuth('/login'); // Redirects if not authenticated
return <h1>Dashboard</h1>;
}Require Specific Role:
import { useRequireRole } from 'vista-auth/guards';
function AdminPage() {
useRequireRole('admin', '/unauthorized'); // Redirects if not admin
return <h1>Admin Panel</h1>;
}Require Permission:
import { useRequirePermission } from 'vista-auth/guards';
function EditUserPage() {
useRequirePermission('users:edit', '/unauthorized');
return <h1>Edit User</h1>;
}Check Roles in Components
import { useAuth } from 'vista-auth/client';
function Navigation() {
const { hasRole, hasAnyRole, hasAllRoles, hasPermission } = useAuth();
return (
<nav>
<a href="/">Home</a>
{hasRole('admin') && (
<a href="/admin">Admin Panel</a>
)}
{hasAnyRole(['admin', 'moderator']) && (
<a href="/moderation">Moderation</a>
)}
{hasAllRoles(['admin', 'superuser']) && (
<a href="/superadmin">Super Admin</a>
)}
{hasPermission('posts:create') && (
<a href="/create-post">Create Post</a>
)}
</nav>
);
}Higher-Order Component (HOC)
import { withAuth } from 'vista-auth/guards';
function ProtectedComponent() {
return <h1>Protected Content</h1>;
}
export default withAuth(ProtectedComponent, {
roles: ['admin'],
redirect: '/login',
});🛡️ Middleware
Next.js Middleware
Create middleware.ts in your project root:
import { createNextMiddleware } from 'vista-auth/middleware';
export default createNextMiddleware({
// Public paths anyone can access
publicPaths: ['/login', '/signup', '/', '/about'],
// Role-based path protection
roleBasedPaths: {
'/admin/*': ['admin'],
'/dashboard/*': ['user', 'admin'],
'/moderator/*': ['moderator', 'admin'],
},
// Where to redirect unauthenticated users
loginUrl: '/login',
// Where to redirect unauthorized users
unauthorizedUrl: '/unauthorized',
// Custom JWT secret (optional)
jwtSecret: process.env.VISTA_AUTH_SECRET,
});
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};Express Middleware
import express from 'express';
import { createExpressMiddleware } from 'vista-auth/middleware';
const app = express();
const authMiddleware = createExpressMiddleware({
publicPaths: ['/login', '/signup', '/api/public/*'],
jwtSecret: process.env.VISTA_AUTH_SECRET,
});
app.use(authMiddleware);
app.get('/api/protected', (req, res) => {
res.json({ message: 'Protected data', user: req.user });
});🎨 UI Helpers
Vista Auth includes beautiful toast notifications:
import { showToast, showError, showWarning, showInfo } from 'vista-auth/ui';
function MyComponent() {
const handleSuccess = () => {
showToast('Login successful!', 3000); // 3 seconds
};
const handleError = () => {
showError('Invalid credentials', 5000);
};
const handleWarning = () => {
showWarning('Your session will expire in 5 minutes');
};
const handleInfo = () => {
showInfo('New features available!');
};
return (
<div>
<button onClick={handleSuccess}>Success</button>
<button onClick={handleError}>Error</button>
<button onClick={handleWarning}>Warning</button>
<button onClick={handleInfo}>Info</button>
</div>
);
}🔄 Real-Time Session Sync
Synchronize authentication state across multiple tabs and devices:
<AuthProvider
apiEndpoint="/api/auth"
config={{
sessionSyncEnabled: true,
websocketUrl: 'wss://your-domain.com/ws/auth',
}}
>
{children}
</AuthProvider>🌐 Offline Support
Vista Auth supports offline authentication with IndexedDB:
<AuthProvider
config={{
sessionStorage: 'indexedDB',
offlineFallback: true,
}}
>
{children}
</AuthProvider>🔧 API Reference
Client Hooks
useAuth()
const {
// State
user, // Current user object or null
session, // Current session object or null
isLoading, // true while checking authentication
isAuthenticated, // true if user is signed in
error, // Error message if any
// Actions
signIn, // (credentials) => Promise<void>
signUp, // (data) => Promise<void>
signOut, // () => Promise<void>
updateUser, // (data) => Promise<void>
// Role & Permission Checks
hasRole, // (role: string) => boolean
hasPermission, // (permission: string) => boolean
hasAnyRole, // (roles: string[]) => boolean
hasAllRoles, // (roles: string[]) => boolean
} = useAuth();Server API
createVistaAuth(config)
import { createVistaAuth } from 'vista-auth/server';
const auth = createVistaAuth({
database: adapter, // Database adapter or null
jwtSecret: string, // Secret for JWT signing
bcryptRounds: number, // bcrypt cost factor (default: 10)
sessionDuration: number, // Session duration in ms
});
// Methods
await auth.signUp({ email, password, name, roles, permissions });
await auth.signIn({ email, password });
await auth.getSession(token);
await auth.signOut(sessionId);
await auth.hashPassword(password);
await auth.verifyPassword(password, hash);
auth.generateToken(payload);
auth.verifyToken(token);💡 Complete Examples
Full Login Page with Validation
'use client';
import { useState } from 'react';
import { useAuth } from 'vista-auth/client';
import { showError, showToast } from 'vista-auth/ui';
export default function LoginPage() {
const { signIn, signUp, isLoading } = useAuth();
const [isSignUp, setIsSignUp] = useState(false);
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const email = formData.get('email') as string;
const password = formData.get('password') as string;
const name = formData.get('name') as string;
if (!email || !password) {
showError('Email and password are required');
return;
}
if (password.length < 8) {
showError('Password must be at least 8 characters');
return;
}
try {
if (isSignUp) {
await signUp({ email, password, name });
showToast('Account created successfully!');
} else {
await signIn({ email, password });
showToast('Welcome back!');
}
} catch (error) {
showError(error.message || 'Authentication failed');
}
};
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="max-w-md w-full space-y-8 p-8 bg-white rounded-lg shadow">
<h2 className="text-3xl font-bold text-center">
{isSignUp ? 'Create Account' : 'Sign In'}
</h2>
<form onSubmit={handleSubmit} className="space-y-6">
{isSignUp && (
<div>
<label htmlFor="name" className="block text-sm font-medium">
Name
</label>
<input
id="name"
name="name"
type="text"
className="mt-1 block w-full px-3 py-2 border rounded-md"
placeholder="John Doe"
/>
</div>
)}
<div>
<label htmlFor="email" className="block text-sm font-medium">
Email
</label>
<input
id="email"
name="email"
type="email"
required
className="mt-1 block w-full px-3 py-2 border rounded-md"
placeholder="you@example.com"
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium">
Password
</label>
<input
id="password"
name="password"
type="password"
required
className="mt-1 block w-full px-3 py-2 border rounded-md"
placeholder="••••••••"
/>
</div>
<button
type="submit"
disabled={isLoading}
className="w-full py-2 px-4 rounded-md text-white bg-blue-600 hover:bg-blue-700 disabled:opacity-50"
>
{isLoading ? 'Loading...' : isSignUp ? 'Sign Up' : 'Sign In'}
</button>
</form>
<div className="text-center">
<button
onClick={() => setIsSignUp(!isSignUp)}
className="text-sm text-blue-600 hover:text-blue-500"
>
{isSignUp ? 'Already have an account? Sign in' : "Don't have an account? Sign up"}
</button>
</div>
</div>
</div>
);
}Protected Dashboard
'use client';
import { useAuth } from 'vista-auth/client';
import { useRequireAuth } from 'vista-auth/guards';
import { showToast } from 'vista-auth/ui';
export default function DashboardPage() {
useRequireAuth('/login');
const { user, signOut, hasRole, hasPermission } = useAuth();
const handleSignOut = async () => {
await signOut();
showToast('Signed out successfully');
};
return (
<div className="min-h-screen bg-gray-100">
<nav className="bg-white shadow">
<div className="max-w-7xl mx-auto px-4 py-4 flex justify-between items-center">
<h1 className="text-2xl font-bold">Dashboard</h1>
<button
onClick={handleSignOut}
className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
>
Sign Out
</button>
</div>
</nav>
<main className="max-w-7xl mx-auto px-4 py-8">
<div className="bg-white rounded-lg shadow p-6 mb-6">
<h2 className="text-xl font-semibold mb-4">Welcome, {user?.name}!</h2>
<p className="text-gray-600">Email: {user?.email}</p>
<p className="text-gray-600">Roles: {user?.roles?.join(', ') || 'None'}</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{hasPermission('posts:view') && (
<div className="bg-white rounded-lg shadow p-6">
<h3 className="text-lg font-semibold mb-2">Posts</h3>
<p className="text-gray-600">Manage your posts</p>
</div>
)}
{hasRole('admin') && (
<div className="bg-white rounded-lg shadow p-6">
<h3 className="text-lg font-semibold mb-2">Admin Panel</h3>
<p className="text-gray-600">System administration</p>
</div>
)}
</div>
</main>
</div>
);
}🛠️ Configuration
Full Configuration Example
// vista-auth.config.ts
import { createVistaAuth } from 'vista-auth/server';
import { createPrismaAdapter } from 'vista-auth/database';
import { prisma } from './lib/prisma';
export const auth = createVistaAuth({
// Database adapter (optional)
database: createPrismaAdapter(prisma),
// Security settings
jwtSecret: process.env.VISTA_AUTH_SECRET!,
bcryptRounds: 12,
sessionDuration: 7 * 24 * 60 * 60 * 1000, // 7 days
// Lifecycle callbacks
onSignIn: (user) => {
console.log('User signed in:', user.email);
},
onSignOut: () => {
console.log('User signed out');
},
onSessionExpired: () => {
console.log('Session expired');
},
onError: (error) => {
console.error('Auth error:', error);
},
});Environment Variables
Create a .env.local file:
# Required: Secret for JWT signing
VISTA_AUTH_SECRET=your-super-secret-jwt-key-change-in-production
# Optional: Database connection
DATABASE_URL=postgresql://user:password@localhost:5432/myapp
# Optional: WebSocket URL
NEXT_PUBLIC_WS_URL=wss://your-domain.com/ws/auth🔒 Security Features
- ✅ bcrypt hashing with configurable cost factor
- ✅ JWT tokens with expiration
- ✅ Secure session management
- ✅ CSRF protection ready
- ✅ XSS protection
- ✅ Rate limiting ready
- ✅ Environment variable secrets
📊 Comparison with Other Solutions
| Feature | Vista Auth | NextAuth.js | Clerk | Auth0 |
|---|---|---|---|---|
| Setup Time | 5 minutes | 30+ minutes | 15 min | 20 min |
| Lines of Code | ~150 | 500+ | 200+ | 300+ |
| Works Without Database | ✅ | ❌ | ❌ | ✅ |
| Database Agnostic | ✅ | ⚠️ | ❌ | ✅ |
| Framework Agnostic | ✅ | ❌ | ⚠️ | ✅ |
| Built-in RBAC | ✅ | ❌ | ✅ | ✅ |
| Real-Time Sync | ✅ | ❌ | ✅ | ✅ |
| Offline Support | ✅ | ❌ | ❌ | ❌ |
| UI Components | ✅ | ❌ | ✅ | ✅ |
| Bundle Size | ~5KB | ~50KB | ~80KB | ~100KB |
| Pricing | Free | Free | Paid | Paid |
| Self-Hosted | ✅ | ✅ | ❌ | ❌ |
| TypeScript First | ✅ | ⚠️ | ✅ | ✅ |
| CLI Tool | ✅ | ❌ | ✅ | ❌ |
🤝 Contributing
We welcome contributions! Please see our Contributing Guide.
📄 License
MIT © Vista Auth
🌟 Why Choose Vista Auth?
- Simplicity - 150 lines vs 500+ in alternatives
- Flexibility - Works with any React framework and database
- Power - Built-in RBAC, real-time sync, offline support
- Security - Production-ready with bcrypt and JWT
- Developer Experience - TypeScript-first with great docs
📞 Support
- GitHub Issues: Report bugs or request features
- GitHub Discussions: Ask questions
- Documentation: Full docs
Ready to add authentication to your app?
npm install vista-auth
npx vista-auth init⭐ Star us on GitHub!