Entity Auth

Basic Setup Template

Set up Next.js with Entity Auth in one command

Overview

The basic setup template scaffolds a complete Next.js application with Entity Auth integration out of the box. Run one command and get:

  • Next.js 15+ with TypeScript and Turbopack
  • Entity Auth SDK configured and initialized
  • React Context provider with useAuth() hook
  • Protected route middleware
  • Environment variable template
  • Pre-built AuthForm UI component with tabs for login/register
  • shadcn/ui automatically installed and configured

One command setup

This template handles all the boilerplate. You just wire up the provider in your layout and start building.

Quick Start

Run the setup command

npx @entityauth/cli@latest setup run entity-auth --node entityauth:setup:basic

This will:

  1. Run npx create-next-app@latest with TypeScript and Turbopack
  2. Install and configure shadcn/ui with all components
  3. Create an entity-auth/ directory with all integration files
  4. Include a components/ subdirectory with the AuthForm UI component
  5. Generate a .env.local.example with placeholder values

Configure environment variables

Copy the example file and fill in your workspace credentials:

cp entity-auth/.env.local.example .env.local

Get your credentials from the Entity Auth Dashboard:

  1. Create or select a workspace
  2. Go to the Setup tab
  3. Copy your tenant ID and URL

Your .env.local should look like:

ENTITY_AUTH_WORKSPACE_TENANT_ID=ws_prod_abc123
NEXT_PUBLIC_ENTITY_AUTH_WORKSPACE_TENANT_ID=ws_prod_abc123
NEXT_PUBLIC_ENTITY_AUTH_URL=https://ws_prod_abc123.entity-auth.com

Install the Entity Auth SDK and React package

pnpm add @entityauth/auth-client @entityauth/react

Initialize and wrap your app with EntityAuthProvider

Update your root layout to include the provider from @entityauth/react and initialize the SDK once:

import { init as initEA } from '@entityauth/auth-client';
import { EntityAuthProvider } 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>
  );
}

Use the useAuth hook in your components

'use client';

import { useAuth } from '../../entity-auth/auth-provider';

export default function Dashboard() {
  const { user, isAuthenticated, loading, logout } = useAuth();

  if (loading) {
    return <div>Loading...</div>;
  }

  if (!isAuthenticated) {
    return <div>Please log in</div>;
  }

  return (
    <div>
      <h1>Welcome, {user?.email}</h1>
      <button onClick={logout}>Logout</button>
    </div>
  );
}

What's Included

The setup creates an entity-auth/ directory with these files:

auth-client.tsx

Initializes the Entity Auth SDK and exports a singleton client instance.

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,
});

export const entityAuthClient = new EntityAuthClient({
  baseURL: process.env.NEXT_PUBLIC_ENTITY_AUTH_URL,
});

auth-provider.tsx

React Context provider with authentication state and methods:

  • State: user, isAuthenticated, loading
  • Methods: login(email, password), register(email, password), logout(), refresh()
  • Client: Direct access via client property
  • Hooks: useAuth() and useRequireAuth()

Features:

  • Automatically loads user on mount
  • Listens for token changes (logout, refresh)
  • Auto-login after registration
  • Error handling with user-friendly messages

middleware.ts

Next.js middleware for route protection:

  • Redirects unauthenticated users from protected routes (/dashboard, /profile, /settings)
  • Redirects authenticated users away from auth pages (/login, /register)
  • Checks ea_refresh cookie for authentication state
  • Preserves redirect parameter for post-login navigation

.env.local.example

Template with all required environment variables. Just replace placeholders with your actual values from the dashboard.

components/auth-form.tsx

A production-ready authentication UI component built with shadcn/ui that includes:

  • Tabbed interface: Switch between login and register views
  • Fully integrated: Works seamlessly with the EntityAuthClient
  • Customizable: Props for redirects, titles, branding, error/success handlers
  • Auto-redirect: Automatically redirects authenticated users
  • Form validation: Email and password validation
  • Error handling: Built-in error display with customizable handlers

Key features:

  • Uses shadcn/ui components (Button, Card, Input, Label, Tabs)
  • Auto-login after registration
  • Logout button when already authenticated
  • Customizable brand name, titles, and descriptions
  • Optional header and back link
  • Type-safe with full TypeScript support

Usage Examples

Using the Pre-built AuthForm Component

The easiest way to add authentication to your app is to use the included AuthForm component:

'use client';

import { AuthForm } from '../../entity-auth/components/auth-form';
import { entityAuthClient } from '../../entity-auth/auth-client';

export default function AuthPage() {
  return (
    <AuthForm
      client={entityAuthClient}
      redirectTo="/dashboard"
      title="Welcome"
      description="Sign in to your account or create a new one"
      brandName="My App"
    />
  );
}

Custom Login Form

If you need a custom UI, you can build your own form using the useAuth hook:

'use client';

import { useAuth } from '../entity-auth/auth-provider';
import { useState } from 'react';

export default function LoginPage() {
  const { login, isAuthenticated } = useAuth();
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    const result = await login(email, password);
    if (!result.success) {
      setError(result.error || 'Login failed');
    }
  };

  if (isAuthenticated) {
    return <div>Already logged in!</div>;
  }

  return (
    <form onSubmit={handleSubmit}>
      {error && <div style={{ color: 'red' }}>{error}</div>}
      <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">Login</button>
    </form>
  );
}

Registration Form

'use client';

import { useAuth } from '../entity-auth/auth-provider';
import { useState } from 'react';

export default function RegisterPage() {
  const { register } = useAuth();
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    const result = await register(email, password);
    if (!result.success) {
      setError(result.error || 'Registration failed');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      {error && <div style={{ color: 'red' }}>{error}</div>}
      <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">Register</button>
    </form>
  );
}

Protected Route

'use client';

import { useRequireAuth } from '../entity-auth/auth-provider';

export default function ProtectedPage() {
  const { isAuthenticated, loading } = useRequireAuth();

  if (loading) {
    return <div>Loading...</div>;
  }

  if (!isAuthenticated) {
    return <div>Access denied. Please log in.</div>;
  }

  return <div>Protected content here</div>;
}

Using the Client Directly

For advanced use cases, access the client directly:

'use client';

import { useAuth } from '../entity-auth/auth-provider';

export default function AdvancedPage() {
  const { client } = useAuth();

  const handleSetUsername = async () => {
    try {
      await client.setUsername({ username: 'newusername' });
      console.log('Username set!');
    } catch (error) {
      console.error('Failed to set username:', error);
    }
  };

  const handleSwitchOrg = async (orgId: string) => {
    try {
      await client.switchOrg({ orgId });
      console.log('Switched organization!');
    } catch (error) {
      console.error('Failed to switch org:', error);
    }
  };

  return (
    <div>
      <button onClick={handleSetUsername}>Set Username</button>
      <button onClick={() => handleSwitchOrg('org_123')}>Switch Org</button>
    </div>
  );
}

Customization

Add More Protected Routes

Edit the PROTECTED_ROUTES array in middleware.ts:

const PROTECTED_ROUTES = ['/dashboard', '/profile', '/settings', '/admin'];

Modify Auth Routes

Update the AUTH_ROUTES array to change which pages authenticated users can't access:

const AUTH_ROUTES = ['/login', '/register', '/forgot-password'];

Extend the Auth Context

Add custom methods to the provider by editing auth-provider.tsx:

const getOrganizations = useCallback(async () => {
  try {
    const orgs = await entityAuthClient.getUserOrganizations();
    return orgs;
  } catch (error) {
    console.error('Failed to get organizations:', error);
    return [];
  }
}, []);

// Add to context value
return (
  <AuthContext.Provider
    value={{
      // ... existing values
      getOrganizations,
    }}
  >
    {children}
  </AuthContext.Provider>
);

AuthForm Component Props

The AuthForm component accepts these props for customization:

PropTypeDefaultDescription
clientEntityAuthClientrequiredThe Entity Auth client instance
redirectTostring"/dashboard"Path to redirect after successful auth
showHeaderbooleantrueShow header with branding
showBackLinkbooleantrueShow back link at bottom
backLinkHrefstring"/"Href for the back link
titlestring"Dashboard Access"Card title text
descriptionstring"Authenticate to access..."Card description text
brandNamestring"Entity Auth"Brand name in header/footer
onErrorfunctionundefinedCustom error handler (error, action) => void
onSuccessfunctionundefinedCustom success handler (action) => void

Next Steps

Ready to go

Your Next.js app is now fully integrated with Entity Auth. Just wrap your layout with the provider and start using the useAuth() hook!