Skip to content

Authentication

PGRestify provides comprehensive JWT-based authentication with session management, token refresh, role-based access control, and security best practices. The authentication system integrates seamlessly with PostgREST's security model.

JWT Authentication Setup

Basic Configuration

Configure authentication when creating your client:

typescript
import { createClient } from '@webcoded/pgrestify';

const client = createClient({
  url: 'http://localhost:3000',
  apikey: 'your-anon-key', // Anonymous/public key
  auth: {
    // Authentication endpoint
    url: 'http://localhost:3000/auth/v1',
    
    // Auto-refresh tokens
    autoRefreshToken: true,
    
    // Persist session in storage
    persistSession: true,
    
    // Storage mechanism (defaults to localStorage in browser)
    storage: localStorage,
    
    // Detect session from URL (for magic links, etc.)
    detectSessionInUrl: true
  }
});

Authentication Methods

Email/Password Authentication

typescript
interface User {
  id: string;
  email: string;
  created_at: string;
  email_confirmed_at?: string;
  user_metadata?: Record<string, any>;
}

// Sign up new user
async function signUp(email: string, password: string, metadata?: Record<string, any>) {
  try {
    const { data, error } = await client.auth.signUp({
      email,
      password,
      options: {
        data: metadata // Additional user metadata
      }
    });
    
    if (error) {
      throw new Error(error.message);
    }
    
    if (data.user) {
      console.log('User created:', data.user);
      
      if (data.session) {
        console.log('Session created:', data.session);
        // User is automatically signed in
      } else {
        console.log('Confirmation email sent');
        // User needs to confirm email
      }
    }
    
    return data;
  } catch (error) {
    console.error('Sign up failed:', error);
    throw error;
  }
}

// Sign in existing user
async function signIn(email: string, password: string) {
  try {
    const { data, error } = await client.auth.signIn({
      email,
      password
    });
    
    if (error) {
      throw new Error(error.message);
    }
    
    console.log('Signed in:', data.user);
    console.log('Session:', data.session);
    
    return data;
  } catch (error) {
    console.error('Sign in failed:', error);
    throw error;
  }
}

// Usage
await signUp('user@example.com', 'secure-password', {
  name: 'John Doe',
  preferences: { theme: 'dark' }
});

await signIn('user@example.com', 'secure-password');

Session Management

Session State

typescript
// Get current session
const session = client.auth.session();
console.log('Current session:', session);

if (session) {
  console.log('User:', session.user);
  console.log('Access token:', session.access_token);
  console.log('Refresh token:', session.refresh_token);
  console.log('Expires at:', new Date(session.expires_at * 1000));
}

// Get current user
const user = client.auth.user();
console.log('Current user:', user);

Session Events

typescript
// Listen for auth state changes
client.auth.onAuthStateChange((event, session) => {
  console.log('Auth event:', event);
  console.log('Session:', session);
  
  switch (event) {
    case 'SIGNED_IN':
      console.log('User signed in:', session?.user);
      // Redirect to dashboard, update UI, etc.
      handleSignIn(session?.user);
      break;
      
    case 'SIGNED_OUT':
      console.log('User signed out');
      // Redirect to login, clear local data, etc.
      handleSignOut();
      break;
      
    case 'TOKEN_REFRESHED':
      console.log('Token refreshed:', session?.access_token);
      // Optional: Update stored token
      break;
      
    case 'USER_UPDATED':
      console.log('User updated:', session?.user);
      // Update user profile in UI
      updateUserProfile(session?.user);
      break;
  }
});

Token Refresh Patterns

Automatic Refresh

typescript
// Configure automatic refresh
const client = createClient({
  url: 'http://localhost:3000',
  auth: {
    autoRefreshToken: true,
    
    // Refresh token before it expires
    jwt: {
      expiryMargin: 300 // 5 minutes before expiry
    }
  }
});

// The client will automatically refresh tokens
// You can listen for refresh events
client.auth.onAuthStateChange((event, session) => {
  if (event === 'TOKEN_REFRESHED') {
    console.log('Token auto-refreshed');
  }
});

Role-Based Access Control

User Roles

typescript
interface UserWithRole extends User {
  role: string;
  permissions: string[];
}

// Get user role from JWT claims
function getUserRole(): string | null {
  const session = client.auth.session();
  if (!session) return null;
  
  try {
    const payload = JSON.parse(atob(session.access_token.split('.')[1]));
    return payload.role || payload.user_role || null;
  } catch (error) {
    console.error('Failed to parse JWT:', error);
    return null;
  }
}

// Check user permissions
function hasPermission(permission: string): boolean {
  const session = client.auth.session();
  if (!session) return false;
  
  try {
    const payload = JSON.parse(atob(session.access_token.split('.')[1]));
    const permissions = payload.permissions || [];
    return permissions.includes(permission);
  } catch (error) {
    return false;
  }
}

// Role-based component rendering
function ProtectedComponent() {
  const userRole = getUserRole();
  
  if (!userRole) {
    return <div>Please sign in</div>;
  }
  
  if (userRole === 'admin') {
    return <AdminPanel />;
  } else if (userRole === 'user') {
    return <UserDashboard />;
  } else {
    return <div>Unauthorized</div>;
  }
}

React Integration

Authentication Hooks

typescript
import { useAuth } from '@webcoded/pgrestify/react';

function LoginForm() {
  const { signIn, signUp, loading, error } = useAuth();
  
  const handleSignIn = async (email: string, password: string) => {
    try {
      await signIn({ email, password });
      // Redirect handled automatically by auth state change
    } catch (error) {
      console.error('Sign in failed:', error);
    }
  };
  
  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      const formData = new FormData(e.currentTarget);
      handleSignIn(
        formData.get('email') as string,
        formData.get('password') as string
      );
    }}>
      <input name="email" type="email" placeholder="Email" required />
      <input name="password" type="password" placeholder="Password" required />
      <button type="submit" disabled={loading}>
        {loading ? 'Signing In...' : 'Sign In'}
      </button>
      {error && <div className="error">{error.message}</div>}
    </form>
  );
}

function AuthStatus() {
  const { user, session, signOut } = useAuth();
  
  if (!user) {
    return <LoginForm />;
  }
  
  return (
    <div>
      <p>Welcome, {user.email}</p>
      <p>Session expires: {new Date(session.expires_at * 1000).toLocaleString()}</p>
      <button onClick={signOut}>Sign Out</button>
    </div>
  );
}

Protected Routes

typescript
function ProtectedRoute({ children, requiredRole }: { 
  children: React.ReactNode; 
  requiredRole?: string;
}) {
  const { user, loading } = useAuth();
  const userRole = getUserRole();
  
  if (loading) {
    return <div>Loading...</div>;
  }
  
  if (!user) {
    return <Navigate to="/login" replace />;
  }
  
  if (requiredRole && userRole !== requiredRole) {
    return <div>Unauthorized - Required role: {requiredRole}</div>;
  }
  
  return <>{children}</>;
}

// Usage
function App() {
  return (
    <Routes>
      <Route path="/login" element={<LoginPage />} />
      <Route 
        path="/dashboard" 
        element={
          <ProtectedRoute>
            <Dashboard />
          </ProtectedRoute>
        } 
      />
      <Route 
        path="/admin" 
        element={
          <ProtectedRoute requiredRole="admin">
            <AdminPanel />
          </ProtectedRoute>
        } 
      />
    </Routes>
  );
}

Security Best Practices

Token Security

typescript
// 1. Token validation
function isValidToken(token: string): boolean {
  try {
    const payload = JSON.parse(atob(token.split('.')[1]));
    
    // Check expiration
    if (payload.exp && payload.exp * 1000 < Date.now()) {
      return false;
    }
    
    // Check issuer
    if (payload.iss !== 'your-expected-issuer') {
      return false;
    }
    
    // Check audience
    if (payload.aud !== 'your-expected-audience') {
      return false;
    }
    
    return true;
  } catch (error) {
    return false;
  }
}

Input Validation

typescript
// Validate authentication inputs
function validateCredentials(email: string, password: string): string[] {
  const errors: string[] = [];
  
  if (!email || !/\S+@\S+\.\S+/.test(email)) {
    errors.push('Valid email is required');
  }
  
  if (!password || password.length < 8) {
    errors.push('Password must be at least 8 characters');
  }
  
  if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(password)) {
    errors.push('Password must contain uppercase, lowercase, and number');
  }
  
  return errors;
}

async function secureSignUp(email: string, password: string) {
  const errors = validateCredentials(email, password);
  
  if (errors.length > 0) {
    throw new Error(errors.join(', '));
  }
  
  return client.auth.signUp({ email, password });
}

Error Handling

Authentication Errors

typescript
function handleAuthError(error: any): string {
  switch (error.message) {
    case 'Invalid login credentials':
      return 'Invalid email or password. Please try again.';
      
    case 'Email not confirmed':
      return 'Please check your email and confirm your account.';
      
    case 'Too many requests':
      return 'Too many attempts. Please wait a few minutes before trying again.';
      
    case 'Password should be at least 8 characters':
      return 'Password must be at least 8 characters long.';
      
    case 'JWT expired':
      return 'Your session has expired. Please sign in again.';
      
    default:
      return 'Authentication failed. Please try again.';
  }
}

Summary

PGRestify's authentication system provides:

  • JWT-Based Authentication: Secure, stateless authentication with automatic token refresh
  • Multiple Auth Methods: Email/password, OAuth, magic links, and more
  • Session Management: Automatic session persistence and state management
  • Role-Based Access: Built-in support for roles and permissions
  • React Integration: Purpose-built hooks for React applications
  • Security Features: Token validation, session timeout, and secure storage
  • Error Handling: Comprehensive error handling and user feedback

The authentication system integrates seamlessly with PostgREST's Row Level Security, providing a complete security solution for your applications.

Released under the MIT License.