This project demonstrates two authentication approaches in Next.js using the App Router:
- Session-based Authentication
- JWT Authentication
Both implementations follow security best practices and provide protection for routes in a Next.js application.
On this application, authentication is handled with Next.js server actions. No external backend service, since this is the simplest showcase of how you can implement a basic secure authentication in Next.js without additional authentication libraries.
For detailed real-production ready implementation that connects with Backend service, you can checkout to the feat/jwt-auth-external-api
branch. It has a major refactored authentication implementations with:
- external backend service
- layered architecture following clean architecture approach
- jwt-based authentication focus
You can also check that branch by going to this URL https://github.com/ifindev/nextjs-middleware-auth/tree/feat/jwt-auth-external-api. If you want to check the code on web, just open the web editor via this URL https://github.dev/ifindev/nextjs-middleware-auth/tree/feat/jwt-auth-external-api.
Have fun!
- Introduction
- Getting Started
- Authentication Implementations
- Usage
- Security Best Practices
- Contributing
This project showcases how to implement secure authentication in a Next.js application using two popular approaches:
- Session-based Authentication: Uses server-side sessions stored in HTTP-Only cookie
- JWT-based Authentication: Uses JSON Web Tokens with access and refresh token mechanisms both stored in HTTP-Only cookies
You can easily switch between these two authentication methods based on your project's requirements.
- Node.js 18+
- npm, yarn, or pnpm
- Clone the repository:
git clone https://github.com/yourusername/next-auth-example.git
cd next-auth-example
- Install dependencies:
pnpm install
- Set up environment variables:
Create a
.env
file with the following content:
SECRET_KEY=your_secret_key_for_session
JWT_ACCESS_SECRET=your_access_token_secret
JWT_REFRESH_SECRET=your_refresh_token_secret
- Run the development server:
pnpm run dev
Session-based authentication uses server-side sessions managed through cookies. When a user logs in, the server creates a session and sends a cookie with a session identifier to the client.
- User logs in with credentials
- Server verifies credentials and creates a session
- Session ID is stored in an HTTP-only cookie
- On subsequent requests, the server validates the session ID
- When the user logs out, the server invalidates the session
┌──────┐ ┌──────────┐ ┌──────────┐
│Client│ │Middleware│ │Server │
└──┬───┘ └────┬─────┘ └────┬─────┘
│ │ │
│ 1. Request Protected Page │ │
│─────────────────────────->│ │
│ │ │
│ │ 2. Check Session Cookie │
│ │─────────────────────────>│
│ │ │
│ │ 3. Session Valid/Invalid │
│ │<─────────────────────────│
│ │ │
│ 4. Redirect or Allow │ │
│<─────────────────────────-│ │
│ │ │
│ 5. Login Request │ │
│─────────────────────────────────────────────────────>│
│ │ │
│ │ │ 6. Verify Credentials
│ │ │──────────────────┐
│ │ │<─────────────────┘
│ │ │
│ 7. Set Session Cookie │ │
│<─────────────────────────────────────────────────────│
│ │ │
│ 8. Request Logout │ │
│─────────────────────────────────────────────────────>│
│ │ │
│ 9. Clear Session Cookie │ │
│<─────────────────────────────────────────────────────│
│ │ │
The session authentication implementation consists of:
- Session Creation: Using the
login
function insession-auth.ts
that sets an encrypted session cookie - Session Validation: Using the
updateSession
function to check and refresh the session - Middleware Protection: Using
sessionAuthMiddleware
to protect routes
// Session middleware
export async function sessionAuthMiddleware(request: NextRequest) {
const { isPublicRoute, isProtectedRoute } = checkRoute(request);
const session = await updateSession(request);
if (isPublicRoute && session) {
return NextResponse.redirect(new URL('/', request.url));
}
if (isProtectedRoute && !session) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
Pros:
- Simple implementation
- Server has full control over sessions
- Can invalidate sessions immediately
Cons:
- Requires session storage (can be a database, Redis, etc.)
- Not ideal for distributed systems without shared session storage
- Slightly more server overhead
JWT (JSON Web Token) authentication uses signed tokens to verify a user's identity. It employs both access tokens (short-lived) and refresh tokens (longer-lived) to maintain security.
- User logs in with credentials
- Server generates access token and refresh token
- Tokens are stored in HTTP-only cookies
- Access token is used for authentication until it expires
- When the access token expires, the refresh token is used to get a new access token
- When the user logs out, both tokens are invalidated
┌──────┐ ┌──────────┐ ┌──────────┐
│Client│ │Middleware│ │Server │
└──┬───┘ └────┬─────┘ └────┬─────┘
│ │ │
│ 1. Request Protected Page │ │
│─────────────────────────->│ │
│ │ │
│ │ 2. Verify Access Token │
│ │─────────────────────────>│
│ │ │
│ │ 3. Token Valid/Invalid │
│ │<─────────────────────────│
│ │ │
│ │ 4. If Invalid, Try Refresh Token
│ │─────────────────────────>│
│ │ │
│ │ 5. New Tokens or Fail │
│ │<─────────────────────────│
│ │ │
│ 6. Redirect or Allow │ │
│<─────────────────────────-│ │
│ │ │
│ 7. Login Request │ │
│─────────────────────────────────────────────────────>│
│ │ │
│ │ │ 8. Verify Credentials
│ │ │──────────────────┐
│ │ │<─────────────────┘
│ │ │
│ 9. Set Token Cookies │ │
│<─────────────────────────────────────────────────────│
│ │ │
│ 10. Request Logout │ │
│─────────────────────────────────────────────────────>│
│ │ │
│ 11. Clear Token Cookies │ │
│<─────────────────────────────────────────────────────│
│ │ │
The JWT authentication implementation consists of:
- Token Generation: Using
generateTokens
function to create access and refresh tokens - Token Validation: Using
verifyAccessToken
andverifyRefreshToken
functions - Token Refresh: Using
refreshTokens
to handle token refreshing - Middleware Protection: Using
jwtAuthMiddleware
to protect routes
// JWT middleware (simplified)
export async function jwtAuthMiddleware(request: NextRequest) {
const path = request.nextUrl.pathname;
const isPublicRoute = publicRoutes.includes(path);
// Get authentication status
const authStatus = await getAuthStatus(request);
// Handle public routes - redirect authenticated users to home
if (isPublicRoute && authStatus.status !== 'unauthenticated') {
return NextResponse.redirect(new URL('/', request.url));
}
// Handle protected routes - redirect unauthenticated users to login
if (!isPublicRoute && authStatus.status === 'unauthenticated') {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
Pros:
- Stateless authentication
- Good for distributed systems and microservices
- Can include additional user data in the token payload
Cons:
- Cannot invalidate tokens before they expire (without additional infrastructure)
- Slightly more complex implementation with refresh tokens
- Token size can increase payload size
To switch between authentication methods, modify the middleware.ts
file:
import { NextRequest } from 'next/server';
import { jwtAuthMiddleware } from './middlewares/jwt-auth';
import { sessionAuthMiddleware } from './middlewares/session-auth';
export async function middleware(request: NextRequest) {
// Use JWT authentication
return jwtAuthMiddleware(request);
// Or use Session authentication
// return sessionAuthMiddleware(request);
}
All routes except for the ones specified in publicRoutes
array are protected:
const publicRoutes = ['/login', '/register'];
To access protected routes, users must be authenticated. Unauthenticated users will be redirected to the login page.
// Using Session Auth
await sessionLogin(formData);
// Using JWT Auth
await jwtLogin(formData);
// Using Session Auth
await sessionLogout();
// Using JWT Auth
await jwtLogout();
// Using Session Auth
const session = await getSession();
// Using JWT Auth
const user = await getUserFromCookies();
This project implements several security best practices:
- HTTP-Only Cookies: Prevents JavaScript access to sensitive cookies
- Secure Flag: Ensures cookies are only sent over HTTPS in production
- SameSite Policy: Protects against CSRF attacks
- Token Expiration: Short-lived access tokens with refresh mechanisms
- CSRF Protection: Using SameSite cookies
- Proper Error Handling: Non-revealing error messages
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request