Skip to main content

Authentication Patterns Guide

Overview

This document establishes the correct authentication patterns for the application. Following these patterns ensures consistency, security, and maintainability.

✅ Correct Patterns

Server-Side Components (RSC)

import { auth } from '@lib/auth';

async function MyServerComponent() {
const session = await auth();

// Check if user is authenticated
const isAuthenticated = !!session?.user?.id;

// Check if user owns a resource
const isOwner = session?.user?.id === resourceOwnerId;

// Use user ID for stable comparisons
const currentUserId = session?.user?.id;

return (
<div>
{isAuthenticated && <p>Welcome back!</p>}
{isOwner && <button>Edit</button>}
</div>
);
}

Client-Side Components

'use client';

import { useSession } from 'next-auth/react';

function MyClientComponent() {
const { data: session } = useSession();

// Check if user is authenticated
const isAuthenticated = !!session?.user?.id;

// Check if user owns a resource
const isOwner = session?.user?.id === resourceOwnerId;

// Use user ID for stable comparisons
const currentUserId = session?.user?.id;

return (
<div>
{isAuthenticated && <p>Welcome back!</p>}
{isOwner && <button>Edit</button>}
</div>
);
}

❌ Incorrect Patterns (DO NOT USE)

Complex Username Matching

// ❌ WRONG - Error-prone and inconsistent
const isOwnProfile =
session?.user &&
(userProfile.id === session.user.id ||
userProfile.username === session.user.name ||
userProfile.username === session.user.email);

Using session.user.id (Database ID)

const isOwner = session?.user?.id === authorId;

Inconsistent Checks

// ❌ WRONG - Mix of different identifiers
const canEdit =
session?.user?.name === author || session?.user?.email === userEmail;

Key Principles

1. Always Use session.user.id

The session.user.id field is the most stable and reliable identifier:

// ✅ Correct
const isOwner = session?.user?.id === resourceOwnerId;

// ❌ Wrong
const isOwner = session?.user?.providerAccountId === resourceOwnerId;

2. Use Proper Authentication Method

  • Server components: Use auth() from @/lib/auth
  • Client components: Use useSession() from next-auth/react

3. Consistent Property Access

Always use optional chaining for safety:

// ✅ Correct
const userId = session?.user?.id;
const isAuthenticated = !!session?.user?.id;

// ❌ Wrong
const userId = session.user.id; // Could throw error

4. Simple Boolean Checks

For authentication status:

// ✅ Correct
const isAuthenticated = !!session?.user?.id;

// ❌ Wrong
const isAuthenticated = session && session.user && session.user.id;

Migration Notes

If you encounter legacy patterns using providerAccountId, replace them with session.user.id:

// Before
session?.user?.providerAccountId === authorId;

// After
session?.user?.id === authorId;

Session Structure

Our session object has this structure:

interface Session {
user: {
id: string; // ✅ Use this for all comparisons
name?: string; // Display name
email?: string; // User email
image?: string; // Profile image URL
providerAccountId?: string; // ⚠️ OAuth only, don't use for app logic
};
}

Examples

Profile Ownership Check

// ✅ Correct
const isOwnProfile = session?.user?.id === userProfile.id;

Post/Comment Ownership

// ✅ Correct
const canEdit = session?.user?.id === post.authorId;
const canDelete = session?.user?.id === comment.authorId;

Authorization for Actions

// ✅ Correct
const canReply = !!session?.user?.id;
const canCreatePost = !!session?.user?.id;

Testing

When writing tests, mock the session with the id field:

const mockSession = {
user: {
id: 'test-user-123',
name: 'Test User',
email: 'test@example.com'
}
};

Conclusion

Following these patterns ensures:

  • Consistency across the entire application
  • Security through proper identity verification
  • Maintainability with simple, predictable patterns
  • Reliability using stable identifiers

Always use session.user.id for all authentication and authorization checks!