Framework Integration
Using Entity Auth with React, Next.js, and other popular frameworks
Entity Auth works seamlessly with popular web frameworks. Here are integration patterns for the most common scenarios.
React Integration
Recommended: @entityauth/react
Use the official provider and hooks instead of rolling your own.
// app/layout.tsx (or your root)
import { init as initEA } from '@entityauth/auth-client';
import { EntityAuthProvider, useEntityAuth, useMe } from '@entityauth/react';
initEA({
workspaceTenantId: process.env.NEXT_PUBLIC_ENTITY_AUTH_WORKSPACE_TENANT_ID!,
baseURL: process.env.NEXT_PUBLIC_ENTITY_AUTH_URL,
});
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<EntityAuthProvider>{children}</EntityAuthProvider>
</body>
</html>
);
}
export function Example() {
const { isAuthenticated, login, logout } = useEntityAuth();
const { me, loading } = useMe<{ id: string; email: string | null }>();
return (
<div>
<div>Authenticated: {String(isAuthenticated)}</div>
{loading ? 'Loading…' : <pre>{JSON.stringify(me, null, 2)}</pre>}
<button onClick={() => login({ email: 'a@b.com', password: 'secret' })}>Login</button>
<button onClick={() => logout()}>Logout</button>
</div>
);
}
Using the Hook in Components
// components/LoginForm.tsx
import { useState } from 'react';
import { useEntityAuth } from '@entityauth/react';
export function LoginForm() {
const { login, loading, isAuthenticated } = useEntityAuth();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const success = await login(email, password);
if (success) {
console.log('Login successful!');
}
};
if (isAuthenticated) {
return <div>Already logged in!</div>;
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
required
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
required
/>
<button type="submit" disabled={loading}>
{loading ? 'Signing in...' : 'Sign In'}
</button>
</form>
);
}
Protected Routes
// components/ProtectedRoute.tsx
import { useEntityAuth } from '@entityauth/react';
export function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { isAuthenticated, loading } = useEntityAuth();
if (loading) {
return <div>Loading...</div>;
}
if (!isAuthenticated) {
return <div>Please log in to access this page.</div>;
}
return <>{children}</>;
}
Next.js Integration
App Router (Recommended)
Next.js App Router
For Next.js 13+ with App Router, use React Server Components and Client Components appropriately.
Client Component for Authentication:
// components/auth-provider.tsx
'use client';
import { createContext, useContext, useEffect, useState } from 'react';
import { EntityAuthClient, init as initEA } from '@entityauth/auth-client';
initEA({
workspaceTenantId: process.env.NEXT_PUBLIC_ENTITY_AUTH_WORKSPACE_TENANT_ID!,
baseURL: process.env.NEXT_PUBLIC_ENTITY_AUTH_URL,
});
const ea = new EntityAuthClient();
interface AuthContextType {
user: any | null;
isAuthenticated: boolean;
loading: boolean;
login: (email: string, password: string) => Promise<boolean>;
logout: () => Promise<void>;
ea: EntityAuthClient;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<any | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const initAuth = async () => {
await ea.ready();
const token = ea.getAccessToken();
if (token) {
try {
const userData = await ea.me();
setUser(userData);
} catch (error) {
console.error('Failed to load user:', error);
}
}
setLoading(false);
};
initAuth();
const initAuth = async () => {
await ea.ready();
const token = ea.getAccessToken();
if (token) {
try {
const userData = await ea.me();
setUser(userData);
} catch (error) {
console.error('Failed to load user:', error);
}
}
setLoading(false);
};
initAuth();
const unsubscribe = ea.onTokenChange(async (token) => {
if (token) {
try {
const userData = await ea.getUserMe();
setUser(userData);
} catch (error) {
setUser(null);
}
} else {
setUser(null);
}
});
return unsubscribe;
}, []);
const login = async (email: string, password: string) => {
try {
await ea.login({ email, password });
return true;
} catch (error) {
return false;
}
};
const logout = async () => {
await ea.logout();
};
return (
<AuthContext.Provider
value={{
user,
isAuthenticated: !!user,
loading,
login,
logout,
ea
}}
>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}
Root Layout:
// app/layout.tsx
import { AuthProvider } from '../components/auth-provider';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<AuthProvider>
{children}
</AuthProvider>
</body>
</html>
);
}
Middleware for Protected Routes & SSO:
New in v0.0.32: Use entityAuthMiddleware for automatic ticket exchange and route protection.
// middleware.ts
import { entityAuthMiddleware } from '@entityauth/nextjs/middleware';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
return entityAuthMiddleware(request, {
entityAuthUrl: process.env.NEXT_PUBLIC_ENTITY_AUTH_URL || 'https://entity-auth.com',
workspaceTenantId: process.env.NEXT_PUBLIC_ENTITY_AUTH_WORKSPACE_TENANT_ID || '',
protectedRoutes: ['/dashboard/*', '/profile', '/settings'],
publicRoutes: ['/', '/pricing'],
loginUrl: '/auth',
});
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};
This middleware automatically:
- ✅ Exchanges SSO tickets server-side (no page reloads!)
- ✅ Protects authenticated routes
- ✅ Redirects unauthenticated users to login
- ✅ Handles cross-domain cookies properly
Pages Router (Legacy)
For Next.js with Pages Router:
// pages/_app.tsx
import type { AppProps } from 'next/app';
import { AuthProvider } from '../components/auth-provider';
export default function App({ Component, pageProps }: AppProps) {
return (
<AuthProvider>
<Component {...pageProps} />
</AuthProvider>
);
}
Advanced Patterns
Organization Context
// hooks/useOrganizations.ts
import { useCallback, useEffect, useState } from 'react';
import { useAuth } from './useAuth';
export function useOrganizations() {
const { ea } = useAuth();
const [organizations, setOrganizations] = useState([]);
const [currentOrg, setCurrentOrg] = useState(null);
const loadOrganizations = useCallback(async () => {
try {
const me = await ea.me();
const links = await SDK.queryRelations({ srcId: me.id, relation: 'member_of' });
const orgs = await Promise.all(links.map((l) => SDK.getEntity({ id: l.dstId })));
setOrganizations(orgs);
setCurrentOrg(orgs.find((o) => o.id === me.workspaceTenantId) ?? null);
} catch (error) {
console.error('Failed to load organizations:', error);
}
}, [ea]);
useEffect(() => {
loadOrganizations();
}, [loadOrganizations]);
const switchOrganization = useCallback(async (orgId: string) => {
try {
setCurrentOrg(organizations.find((o) => o.id === orgId) ?? null);
} catch (error) {
console.error('Failed to switch organization:', error);
}
}, [organizations]);
const createOrganization = useCallback(async (name: string, slug: string) => {
try {
const me = await ea.me();
const org = await SDK.createEntity({
workspaceTenantId: me.workspaceTenantId!,
kind: 'org',
properties: { name, slug, ownerId: me.id },
});
await SDK.linkRelation({
workspaceTenantId: me.workspaceTenantId!,
srcId: me.id,
relation: 'member_of',
dstId: org.id,
attrs: { role: 'owner' },
});
await loadOrganizations();
} catch (error) {
console.error('Failed to create organization:', error);
}
}, [ea, loadOrganizations]);
return {
organizations,
currentOrg,
switchOrganization,
createOrganization,
refetch: loadOrganizations
};
}
Session Management
Sessions are managed via login, refresh, and logout. The JS SDK does not expose list/revoke APIs; build UI around authentication state via ea.onTokenChange and ea.getAccessToken().
Best Practices
Security Considerations
- Never store access tokens in localStorage or sessionStorage
- Always use the SDK's fetch method for authenticated requests
- Handle token expiration gracefully in your UI
- Implement proper loading states during authentication flows
- Use the SDK's built-in token management - Don't try to manage tokens manually
- Handle loading states - Authentication state changes are async
- Implement proper error handling - Network requests can fail
- Use TypeScript - The SDK includes full type definitions
- Test authentication flows - Include login, logout, and token refresh scenarios
Example Application Structure
src/
├── hooks/
│ ├── useEntityAuth.ts
│ ├── useOrganizations.ts
│ └── useSessions.ts
├── components/
│ ├── AuthProvider.tsx
│ ├── LoginForm.tsx
│ ├── ProtectedRoute.tsx
│ └── OrganizationSwitcher.tsx
├── pages/ (or app/ for App Router)
│ ├── login.tsx
│ ├── dashboard.tsx
│ └── profile.tsx
└── lib/
└── auth.ts (SDK instance)
This structure provides a solid foundation for integrating Entity Auth into any React or Next.js application while maintaining security best practices and a good developer experience.