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
Quick Start โข Documentation โข Examples โข GitHub
โจ 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 localstorage
- ๐ 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:
// src/main.tsx
import { AuthProvider } from "vista-auth/client";
ReactDOM.createRoot(document.getElementById("root")!).render(
<AuthProvider apiEndpoint="http://localhost:5173/api/auth">
<App />
</AuthProvider>
);Create React App (CRA):
// 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 from everyone! ๐
Quick Start for Contributors
- โญ Star the repository
- ๐ด Fork the project
- ๐ Find an issue labeled
good first issueorhelp wanted - ๐ป Make your changes
- ๐ Submit a pull request
Ways to Contribute
- ๐ Report bugs - Found an issue? Let us know!
- โจ Suggest features - Have an idea? We'd love to hear it!
- ๐ Improve docs - Help others understand Vista Auth better
- ๐งช Write tests - Increase code coverage
- ๐ง Fix bugs - Help squash those pesky bugs
- ๐จ Add examples - Show others how to use Vista Auth
Get Started
- Contributing Guide - Complete guide for contributors
- Contribution Program - Recognition, rewards, and opportunities
- Code of Conduct - Our community guidelines
- Open Issues - Find something to work on
Recognition
All contributors are recognized in our README and release notes. Significant contributors receive swag and special recognition! ๐
Join our growing community of contributors! We can't wait to see what you'll build! ๐ช
๐ 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!