Entity Auth

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

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

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
  1. Use the SDK's built-in token management - Don't try to manage tokens manually
  2. Handle loading states - Authentication state changes are async
  3. Implement proper error handling - Network requests can fail
  4. Use TypeScript - The SDK includes full type definitions
  5. 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.