startsimpli-auth
v0.1.0
Published
Shared authentication package for StartSimpli Next.js apps
Downloads
116
Maintainers
Readme
@startsimpli/auth
Shared authentication package for StartSimpli Next.js applications. Provides JWT-based authentication with Django backend integration.
Features
- JWT access token authentication
- Automatic token refresh with refresh tokens (httpOnly cookies)
- React Context and hooks for client-side auth
- Server-side session validation for SSR and API routes
- Next.js middleware helpers
- Auth guards for API routes
- Permission/role checks (owner > admin > member > viewer)
- TypeScript support with full type safety
Installation
This package is part of the StartSimpli monorepo. It uses NPM workspaces and TypeScript path aliases for direct source imports during development.
# In your Next.js app's package.json
{
"dependencies": {
"@startsimpli/auth": "*"
}
}Django Backend Integration
This package is designed to work with the StartSimpli Django API (start-simpli-api).
Required Django endpoints:
POST /api/v1/auth/token/- Login (returns JWT access token + sets refresh token cookie)POST /api/v1/auth/token/refresh/- Refresh access token using cookiePOST /api/v1/auth/logout/- LogoutGET /api/v1/auth/me/- Get current user data
Token flow:
- Login returns
{ access: "jwt-token", user: {...} } - Refresh token stored as httpOnly cookie (managed by Django)
- Access token stored in memory (not localStorage)
- Automatic refresh before expiration (default: 4 minutes)
Usage
Client-Side Authentication
1. Setup Auth Provider
Wrap your app with AuthProvider in your root layout:
// app/layout.tsx
'use client';
import { AuthProvider } from '@startsimpli/auth/client';
export default function RootLayout({ children }) {
return (
<html>
<body>
<AuthProvider
config={{
apiBaseUrl: process.env.NEXT_PUBLIC_API_URL!,
onSessionExpired: () => {
window.location.href = '/login';
},
}}
>
{children}
</AuthProvider>
</body>
</html>
);
}2. Use Authentication Hook
// app/dashboard/page.tsx
'use client';
import { useAuth } from '@startsimpli/auth/client';
export default function DashboardPage() {
const { user, isLoading, isAuthenticated, logout } = useAuth();
if (isLoading) {
return <div>Loading...</div>;
}
if (!isAuthenticated) {
return <div>Please login</div>;
}
return (
<div>
<h1>Welcome, {user.firstName}!</h1>
<button onClick={logout}>Logout</button>
</div>
);
}3. Login Form
// app/login/page.tsx
'use client';
import { useState } from 'react';
import { useAuth } from '@startsimpli/auth/client';
import { useRouter } from 'next/navigation';
export default function LoginPage() {
const { login } = useAuth();
const router = useRouter();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
try {
await login(email, password);
router.push('/dashboard');
} catch (error) {
console.error('Login failed:', error);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<button type="submit">Login</button>
</form>
);
}4. Permission Checks
'use client';
import { usePermissions } from '@startsimpli/auth/client';
export default function SettingsPage() {
const { isAdmin, canEdit, currentRole } = usePermissions();
return (
<div>
<h1>Settings</h1>
<p>Your role: {currentRole}</p>
{isAdmin() && (
<button>Admin Settings</button>
)}
{canEdit() ? (
<button>Edit</button>
) : (
<p>View only</p>
)}
</div>
);
}Server-Side Authentication
1. Protect Server Components
// app/dashboard/page.tsx
import { getServerSession } from '@startsimpli/auth/server';
import { redirect } from 'next/navigation';
export default async function DashboardPage() {
const session = await getServerSession(process.env.API_BASE_URL!);
if (!session) {
redirect('/login');
}
return (
<div>
<h1>Welcome, {session.user.firstName}!</h1>
</div>
);
}2. Next.js Middleware
Protect routes with authentication middleware:
// middleware.ts
import { createAuthMiddleware } from '@startsimpli/auth/server';
export const middleware = createAuthMiddleware({
apiBaseUrl: process.env.API_BASE_URL!,
publicPaths: ['/login', '/register', '/forgot-password'],
loginPath: '/login',
});
export const config = {
matcher: [
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
};3. API Route Protection
Protect API routes with auth guards:
// app/api/users/route.ts
import { NextRequest } from 'next/server';
import { withAuth } from '@startsimpli/auth/server';
export const GET = withAuth(async (request: NextRequest, token: string) => {
const response = await fetch(`${process.env.API_BASE_URL}/api/v1/users/`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
return NextResponse.json(data);
});4. Role-Based API Protection
// app/api/admin/users/route.ts
import { NextRequest } from 'next/server';
import { withRole } from '@startsimpli/auth/server';
async function getUserRole() {
// Fetch user's current role from your API
return 'admin';
}
export const GET = withRole(
'admin',
getUserRole,
async (request: NextRequest, token: string) => {
// Only accessible to admins
return NextResponse.json({ message: 'Admin data' });
}
);Making Authenticated API Calls
'use client';
import { useAuth } from '@startsimpli/auth/client';
export default function DataFetcher() {
const { getAccessToken } = useAuth();
const fetchData = async () => {
const token = await getAccessToken();
if (!token) {
console.error('No access token');
return;
}
const response = await fetch('/api/data', {
headers: {
Authorization: `Bearer ${token}`,
},
});
return response.json();
};
return <button onClick={fetchData}>Fetch Data</button>;
}API Reference
Client Exports
AuthProvider
React context provider for authentication.
interface AuthProviderProps {
children: ReactNode;
config: AuthConfig;
initialSession?: Session | null;
}useAuth()
Hook to access authentication state and methods.
interface UseAuthReturn {
user: AuthUser | null;
session: Session | null;
isLoading: boolean;
isAuthenticated: boolean;
login: (email: string, password: string) => Promise<void>;
logout: () => Promise<void>;
refreshUser: () => Promise<void>;
getAccessToken: () => Promise<string | null>;
}usePermissions()
Hook for permission/role checks.
interface UsePermissionsReturn {
hasRole: (requiredRole: CompanyRole, companyId?: string) => boolean;
isOwner: (companyId?: string) => boolean;
isAdmin: (companyId?: string) => boolean;
canEdit: (companyId?: string) => boolean;
canView: (companyId?: string) => boolean;
currentRole: CompanyRole | null;
currentCompanyId: string | null;
}AuthClient
Low-level authentication client (used internally by hooks).
class AuthClient {
constructor(config: AuthConfig);
login(email: string, password: string): Promise<Session>;
logout(): Promise<void>;
refreshToken(): Promise<string>;
getCurrentUser(): Promise<AuthUser>;
getSession(): Session | null;
getAccessToken(): Promise<string | null>;
}Server Exports
getServerSession()
Get authenticated session from server-side cookies.
async function getServerSession(apiBaseUrl: string): Promise<Session | null>;validateSession()
Validate session and return user or null.
async function validateSession(apiBaseUrl: string): Promise<AuthUser | null>;requireAuth()
Require authenticated session (throws if not authenticated).
async function requireAuth(apiBaseUrl: string): Promise<Session>;createAuthMiddleware()
Create Next.js middleware for route protection.
function createAuthMiddleware(config: AuthMiddlewareConfig): Middleware;withAuth()
HOF to wrap API routes with auth guard.
function withAuth<T>(
handler: (request: NextRequest, token: string) => Promise<NextResponse<T>>
): (request: NextRequest) => Promise<NextResponse<T>>;withRole()
HOF to wrap API routes with role-based guard.
function withRole<T>(
requiredRole: CompanyRole,
getUserRole: () => Promise<CompanyRole | null>,
handler: (request: NextRequest, token: string) => Promise<NextResponse<T>>
): (request: NextRequest) => Promise<NextResponse<T>>;Types
type CompanyRole = 'owner' | 'admin' | 'member' | 'viewer';
interface AuthUser {
id: string;
email: string;
firstName: string;
lastName: string;
isEmailVerified: boolean;
createdAt: string;
updatedAt: string;
companies?: Array<{
id: string;
name: string;
role: CompanyRole;
}>;
currentCompanyId?: string;
}
interface Session {
user: AuthUser;
accessToken: string;
expiresAt: number;
}
interface AuthConfig {
apiBaseUrl: string;
tokenRefreshInterval?: number;
onSessionExpired?: () => void;
onUnauthorized?: () => void;
}Role Hierarchy
Roles follow a hierarchy where higher roles inherit permissions from lower roles:
owner (4) > admin (3) > member (2) > viewer (1)Use hasRolePermission(userRole, requiredRole) to check if a user has sufficient permissions.
Testing
npm test # Run tests
npm run test:watch # Watch mode
npm run test:coverage # Coverage reportDevelopment
npm run type-check # TypeScript validationLicense
Private package for StartSimpli monorepo.
