Module 20 — Authentication and Authorization
Authentication and authorization are core security concepts in any production Next.js application.
A course platform, SaaS application, admin dashboard, marketplace, or social platform needs to answer two fundamental questions:
Authentication: Who is this user?
Authorization: What is this user allowed to do?
These concepts are related, but they are not the same thing.
A secure application usually follows this architecture:
1User 2 ↓ 3Login 4 ↓ 5Authentication API 6 ↓ 7Session / Token 8 ↓ 9Protected Route 10 ↓ 11Server validates user 12 ↓ 13Authorization 14 ↓ 15Dashboard
Authentication vs Authorization
Authentication
Authentication verifies the identity of a user.
For example:
1Email 2Password 3 ↓ 4Authentication 5 ↓ 6User identified
The server determines:
1User ID: 123 2Email: user@example.com
Authentication answers:
Who are you?
Authorization
Authorization determines what the authenticated user can access.
For example:
1User 2 ↓ 3Authenticated 4 ↓ 5Role = Student 6 ↓ 7Can access courses 8 ↓ 9Cannot access admin dashboard
Authorization answers:
What are you allowed to do?
The Difference
Think about entering a building.
1Authentication 2 ↓ 3Show your identity 4 ↓ 5"Who are you?"
Then:
1Authorization 2 ↓ 3Check your permissions 4 ↓ 5"Which rooms can you enter?"
A user can be successfully authenticated but still not have permission to perform an action.
Complete Authentication Flow
A typical login architecture looks like:
1User 2 ↓ 3Login Form 4 ↓ 5POST /api/auth/login 6 ↓ 7Validate Credentials 8 ↓ 9Find User 10 ↓ 11Verify Password 12 ↓ 13Create Session 14 ↓ 15Set Secure Cookie 16 ↓ 17Redirect 18 ↓ 19Dashboard
The important point is that authentication happens on the server.
Login Form
A basic login form:
1"use client"; 2 3import { useState } from "react"; 4 5export default function LoginForm() { 6 const [email, setEmail] = 7 useState(""); 8 9 const [password, setPassword] = 10 useState(""); 11 12 async function handleSubmit( 13 event: React.FormEvent<HTMLFormElement> 14 ) { 15 event.preventDefault(); 16 17 // Send credentials to the server. 18 } 19 20 return ( 21 <form onSubmit={handleSubmit}> 22 <label htmlFor="email"> 23 Email 24 </label> 25 26 <input 27 id="email" 28 name="email" 29 type="email" 30 value={email} 31 onChange={(event) => 32 setEmail(event.target.value) 33 } 34 required 35 /> 36 37 <label htmlFor="password"> 38 Password 39 </label> 40 41 <input 42 id="password" 43 name="password" 44 type="password" 45 value={password} 46 onChange={(event) => 47 setPassword(event.target.value) 48 } 49 required 50 /> 51 52 <button type="submit"> 53 Login 54 </button> 55 </form> 56 ); 57}
The browser collects credentials, but the server must perform the actual authentication.
Authentication API
The login request might be:
1POST /api/auth/login
with:
1{ 2 "email": "user@example.com", 3 "password": "password" 4}
The server should:
1Receive credentials 2 ↓ 3Validate input 4 ↓ 5Find user 6 ↓ 7Verify password hash 8 ↓ 9Create session 10 ↓ 11Return authentication result
Never store plain-text passwords.
Password Storage
A password should not be stored like this:
1password = "mypassword123"
Instead, store a secure password hash.
Conceptually:
1User Password 2 ↓ 3Password Hashing Algorithm 4 ↓ 5Password Hash 6 ↓ 7Database
During login:
1Entered Password 2 ↓ 3Verify Against Hash 4 ↓ 5Match? 6 ↙ ↘ 7 Yes No 8 ↓ ↓ 9Login Reject
Use a well-established password hashing library rather than implementing password hashing yourself.
Sessions
A session represents an authenticated user's login state.
For example:
1User 2 ↓ 3Login 4 ↓ 5Server creates session 6 ↓ 7Session ID 8 ↓ 9Secure Cookie
On later requests:
1Browser 2 ↓ 3Session Cookie 4 ↓ 5Server 6 ↓ 7Find Session 8 ↓ 9Identify User
The server can then determine who is making the request.
Cookie-Based Sessions
A common architecture is:
1Browser 2 ↓ 3HttpOnly Cookie 4 ↓ 5Session ID 6 ↓ 7Server 8 ↓ 9Session Store 10 ↓ 11User
For authentication cookies, important security attributes commonly include:
1HttpOnly 2Secure 3SameSite
HttpOnly
An HttpOnly cookie cannot be read by normal client-side JavaScript.
This helps reduce the impact of certain XSS attacks involving cookie theft.
Secure
The browser sends the cookie only over HTTPS.
SameSite
Controls when cookies are sent in cross-site requests and helps reduce certain CSRF risks.
Example Cookie
A conceptual cookie configuration might look like:
1cookieStore.set( 2 "session", 3 sessionId, 4 { 5 httpOnly: true, 6 secure: true, 7 sameSite: "lax", 8 path: "/", 9 } 10);
In development, HTTPS-related behavior may need to be configured differently depending on your environment.
Access Tokens
Another authentication architecture uses access tokens.
1Login 2 ↓ 3Authentication Server 4 ↓ 5Access Token 6 ↓ 7Client 8 ↓ 9API Request 10 ↓ 11Authorization Header
A request may contain:
1Authorization: Bearer ACCESS_TOKEN
The API validates the token before processing the request.
Access Token Flow
1User 2 ↓ 3Login 4 ↓ 5Authentication Server 6 ↓ 7Access Token 8 ↓ 9Client 10 ↓ 11GET /api/courses 12 ↓ 13Authorization: Bearer token 14 ↓ 15API validates token 16 ↓ 17Response
Access tokens are commonly used when multiple clients or services need to communicate with an authentication system.
Refresh Tokens
Access tokens are often short-lived.
For example:
1Access Token 2 ↓ 3Short lifetime
A refresh token can be used to obtain a new access token without requiring the user to log in again.
The conceptual flow is:
1Login 2 ↓ 3Access Token + Refresh Token 4 ↓ 5Access Token expires 6 ↓ 7Refresh Token 8 ↓ 9Authentication Server 10 ↓ 11New Access Token
This creates:
1Short-lived access 2+ 3Longer-lived session renewal
Refresh-token handling requires careful security design, including storage, rotation, revocation, and theft detection.
Session vs Access Token
A simplified comparison:
| Feature | Session | Access Token |
|---|---|---|
| Server maintains session state | Usually | Not necessarily |
| Common browser storage | Secure cookie | Depends on architecture |
| Revocation | Usually straightforward | Requires token strategy |
| Stateless API | Not inherently | Common |
| Multi-service architecture | Possible | Common |
| Browser security | Strong with secure cookies | Depends heavily on storage |
Neither is automatically the correct solution for every application.
Choose based on your architecture.
Protected Pages
Suppose your application contains:
1/dashboard 2/profile 3/settings 4/admin
These pages should not be publicly accessible.
The architecture should be:
1Request 2 ↓ 3Protected Page 4 ↓ 5Check Authentication 6 ↓ 7Authenticated? 8 ↙ ↘ 9No Yes 10↓ ↓ 11Login Continue 12 ↓ 13 Dashboard
Server-Side Protection
A protected page can verify the user on the server before rendering sensitive content.
Conceptually:
1export default async function DashboardPage() { 2 const user = await getCurrentUser(); 3 4 if (!user) { 5 redirect("/login"); 6 } 7 8 return ( 9 <main> 10 <h1> 11 Welcome, {user.name} 12 </h1> 13 </main> 14 ); 15}
The important idea is:
1Server 2 ↓ 3Check Session 4 ↓ 5Check User 6 ↓ 7Render Protected Content
Do not rely only on hiding UI elements in the browser.
Protected APIs
Pages are not the only things that need protection.
Consider:
1POST /api/courses 2DELETE /api/courses/123 3PATCH /api/users/123
These endpoints must also validate authentication and authorization.
For example:
1POST /api/courses 2 ↓ 3Is user authenticated? 4 ↓ 5Is user authorized? 6 ↓ 7Validate request 8 ↓ 9Create course
Authentication Does Not Mean Authorization
Suppose:
1User = Ankit 2Authenticated = Yes 3Role = Student
The student can:
1View courses 2Enroll in courses 3Update own profile
But perhaps cannot:
1Create courses 2Delete courses 3Manage users 4Access admin settings
Therefore:
1Authenticated 2 ≠ 3Authorized
This distinction is extremely important.
Role-Based Access Control
Role-Based Access Control, or RBAC, assigns permissions based on roles.
For example:
1User 2├── student 3├── instructor 4└── admin
Each role can have different permissions.
Student
1View courses 2Enroll 3Track progress 4Update profile
Instructor
1Create courses 2Edit own courses 3View enrollments
Admin
1Manage users 2Manage courses 3Delete content 4Manage platform settings
Role Checking
Suppose the current user is:
1const user = { 2 id: "123", 3 role: "student", 4};
An admin operation can check:
1if (user.role !== "admin") { 2 throw new Error( 3 "Forbidden" 4 ); 5}
But authorization should be implemented centrally where possible rather than duplicated throughout the application.
Permission-Based Authorization
Roles can become too broad for complex applications.
Instead of:
1role = admin
you can model permissions:
1courses.read 2courses.create 3courses.update 4courses.delete 5 6users.read 7users.update 8users.delete
Then:
1Instructor 2 ├── courses.read 3 ├── courses.create 4 └── courses.update 5 6Student 7 ├── courses.read 8 └── enrollments.create
This provides more granular control.
Protected Admin Page
Conceptually:
1export default async function AdminPage() { 2 const user = await getCurrentUser(); 3 4 if (!user) { 5 redirect("/login"); 6 } 7 8 if (user.role !== "admin") { 9 redirect("/forbidden"); 10 } 11 12 return ( 13 <main> 14 <h1> 15 Admin Dashboard 16 </h1> 17 </main> 18 ); 19}
The architecture is:
1Request 2 ↓ 3Authentication 4 ↓ 5Authorization 6 ↓ 7Admin Dashboard
401 vs 403
HTTP status codes are important for authentication systems.
401 Unauthorized
Usually means:
The request does not have valid authentication credentials.
Example:
1No valid session 2No valid access token 3Expired credentials
403 Forbidden
Usually means:
The server knows who you are, but you are not allowed to perform this operation.
Example:
1Authenticated student 2 ↓ 3DELETE /api/courses/123 4 ↓ 5403 Forbidden
because the student doesn't have permission to delete courses.
Authentication Architecture With Next.js
A practical architecture might look like:
1app/ 2├── login/ 3│ └── page.tsx 4│ 5├── dashboard/ 6│ └── page.tsx 7│ 8├── admin/ 9│ └── page.tsx 10│ 11└── api/ 12 └── auth/ 13 ├── login/ 14 │ └── route.ts 15 └── logout/ 16 └── route.ts 17 18lib/ 19├── auth/ 20│ ├── session.ts 21│ ├── permissions.ts 22│ └── user.ts 23│ 24└── api/ 25 └── client.ts
This separates:
1Authentication 2Authorization 3API communication 4UI
Getting the Current User
A useful server-side abstraction is:
1export async function getCurrentUser() { 2 const session = 3 await getSession(); 4 5 if (!session) { 6 return null; 7 } 8 9 return session.user; 10}
Then pages can simply use:
1const user = 2 await getCurrentUser();
This prevents every page from implementing its own cookie/session logic.
Authentication Helper
You can create:
1lib/auth/session.ts
For example:
1export async function requireUser() { 2 const user = 3 await getCurrentUser(); 4 5 if (!user) { 6 throw new Error( 7 "Authentication required" 8 ); 9 } 10 11 return user; 12}
Then a protected server operation can use:
1const user = 2 await requireUser();
This creates a reusable security boundary.
Authorization Helper
You can also create:
1export function requireRole( 2 user: { 3 role: string; 4 }, 5 role: string 6) { 7 if (user.role !== role) { 8 throw new Error( 9 "Forbidden" 10 ); 11 } 12}
Usage:
1const user = 2 await requireUser(); 3 4requireRole( 5 user, 6 "admin" 7);
A production system may use more sophisticated permission checks, but the architecture remains similar.
Protected Mutation
Consider deleting a course.
Never rely on:
1Admin Delete Button 2 ↓ 3DELETE API
because someone can call the API directly without using the UI.
Instead:
1DELETE API 2 ↓ 3Authenticate 4 ↓ 5Authorize 6 ↓ 7Validate Course 8 ↓ 9Delete
The server must enforce the rule.
The UI Is Not a Security Boundary
You might hide an admin button:
1{user.role === "admin" && ( 2 <button> 3 Delete Course 4 </button> 5)}
That's good for user experience.
But it is not security.
A malicious user can still manually send:
1DELETE /api/courses/123
Therefore:
1UI restriction 2 ↓ 3UX 4 5Server authorization 6 ↓ 7Security
Always enforce permissions on the server.
Logout
Logout should invalidate the authentication state.
For a session-based system:
1Logout 2 ↓ 3Invalidate Session 4 ↓ 5Clear Cookie 6 ↓ 7Redirect /login
Conceptually:
1async function logout() { 2 await destroySession(); 3 4 redirect("/login"); 5}
Simply removing a button from the UI is not logout.
Session Expiration
Sessions should have an expiration policy.
For example:
1Session 2 ↓ 3Expires 4 ↓ 5User must authenticate again
Depending on your security requirements, you might also use:
1Idle timeout 2Absolute timeout 3Token expiration 4Session rotation 5Revocation
Sensitive applications should carefully consider session lifetime.
Session Rotation
Session identifiers should be rotated when appropriate, especially around authentication state changes.
A common conceptual flow is:
1Anonymous Session 2 ↓ 3Login 4 ↓ 5New Authenticated Session
This helps reduce risks associated with session fixation.
CSRF Considerations
Cookie-based authentication requires attention to cross-site request forgery.
For state-changing operations:
1POST 2PUT 3PATCH 4DELETE
your architecture should use appropriate CSRF protections when required by the authentication design.
Security mechanisms can include:
1SameSite cookies 2CSRF tokens 3Origin checks 4Framework protections
Do not assume that every authentication architecture has identical CSRF requirements.
XSS and Authentication
Cross-site scripting can be especially dangerous for authentication systems.
Avoid injecting untrusted HTML into pages.
Be particularly careful with:
1dangerouslySetInnerHTML
User-generated content should be sanitized according to your application's requirements.
A strong authentication architecture should consider:
1XSS 2CSRF 3Session theft 4Token theft 5Brute-force attacks 6Credential stuffing 7Session fixation 8Authorization bypass
Rate Limiting Login
Login endpoints are common targets for automated attacks.
A production login system should consider rate limiting:
1POST /api/auth/login 2 ↓ 3Rate Limit 4 ↓ 5Authenticate
For example, repeatedly attempting thousands of passwords should not be treated like normal traffic.
Additional defenses may include:
1Account lockout strategies 2Progressive delays 3CAPTCHA/risk checks 4IP/device monitoring 5Credential breach detection
The exact strategy depends on the application.
Complete Authentication Architecture
A production-oriented architecture looks like:
1 USER 2 │ 3 ▼ 4 LOGIN FORM 5 │ 6 ▼ 7 AUTHENTICATION API 8 │ 9 ┌───────┴────────┐ 10 ▼ ▼ 11 Validate Input Rate Limit 12 │ 13 ▼ 14 Verify Password 15 │ 16 ▼ 17 Create Session 18 │ 19 ▼ 20 Secure Cookie 21 │ 22 ▼ 23 PROTECTED PAGE 24 │ 25 ▼ 26 Get Current User 27 │ 28 ▼ 29 Authentication Check 30 │ 31 ▼ 32 Authorization Check 33 │ 34 ┌─────┴─────┐ 35 ▼ ▼ 36 Allow Deny 37 │ │ 38 ▼ ▼ 39 Dashboard 403/Redirect
Authentication With a Backend API
If your Next.js frontend communicates with a Django or Node.js backend, the architecture can be:
1Next.js 2 ↓ 3Login Form 4 ↓ 5Authentication Request 6 ↓ 7Backend API 8 ↓ 9User Database 10 ↓ 11Session / Token 12 ↓ 13Next.js 14 ↓ 15Protected Application
For example:
1Next.js 2 ↓ 3POST /api/auth/login 4 ↓ 5Django / Node.js 6 ↓ 7PostgreSQL
Then authenticated requests can follow:
1Next.js 2 ↓ 3GET /api/courses 4 ↓ 5Authentication 6 ↓ 7Authorization 8 ↓ 9PostgreSQL
Authentication and API Services
This connects directly with the previous module.
Your API structure might contain:
1lib/ 2└── api/ 3 ├── client.ts 4 ├── auth.ts 5 ├── courses.ts 6 ├── users.ts 7 └── topics.ts
Authentication:
1login() 2logout() 3getCurrentUser() 4refreshSession()
Courses:
1getCourses() 2getCourse() 3createCourse() 4updateCourse() 5deleteCourse()
The service layer keeps authentication and API communication organized.
Real-World Course Platform
Imagine a learning platform.
Student
1Login 2 ↓ 3Student Dashboard 4 ↓ 5Browse Courses 6 ↓ 7Enroll 8 ↓ 9Watch Lessons 10 ↓ 11Track Progress
Instructor
1Login 2 ↓ 3Instructor Dashboard 4 ↓ 5Create Course 6 ↓ 7Edit Course 8 ↓ 9Publish Course 10 ↓ 11View Students
Admin
1Login 2 ↓ 3Admin Dashboard 4 ↓ 5Manage Users 6 ↓ 7Manage Courses 8 ↓ 9Manage Instructors 10 ↓ 11Platform Settings
All three users authenticate through the same general system, but authorization determines what each can do.
Authentication Security Checklist
For production applications, consider:
1✓ Password hashing 2✓ Secure sessions 3✓ HttpOnly cookies 4✓ Secure cookies in production 5✓ Appropriate SameSite policy 6✓ HTTPS 7✓ Server-side authentication checks 8✓ Server-side authorization checks 9✓ Input validation 10✓ CSRF protection where applicable 11✓ XSS protection 12✓ Rate limiting 13✓ Session expiration 14✓ Session rotation 15✓ Token expiration 16✓ Refresh-token security 17✓ Audit logging for sensitive operations
Common Authentication Mistakes
Mistake 1 — Only Protecting the UI
1Hide Admin Button 2 ↓ 3Assume Secure
Wrong.
The API must also enforce authorization.
Mistake 2 — Trusting a Role From the Browser
Never trust:
1{ 2 "role": "admin" 3}
just because the browser sent it.
The server must determine the user's real permissions.
Mistake 3 — Storing Passwords in Plain Text
Never store:
1password = "mypassword"
Store secure password hashes.
Mistake 4 — Exposing Private Tokens
Don't place long-lived private credentials in publicly accessible client-side JavaScript.
Mistake 5 — Forgetting API Protection
A protected page does not automatically make the underlying API secure.
Protect:
1Pages 2API routes 3Server Actions 4Database mutations
Mistake 6 — Confusing Authentication and Authorization
Remember:
1Authentication 2= Who are you? 3 4Authorization 5= What can you do?
Module 20 Learning Checklist
After completing this module, you should understand:
- Authentication
- Authorization
- Login
- Logout
- Sessions
- Cookies
HttpOnlySecureSameSite- Access tokens
- Refresh tokens
- Protected pages
- Protected APIs
- Role-based access control
- Permission-based access control
401 Unauthorized403 Forbidden- Current-user helpers
- Authentication middleware/helpers
- Authorization checks
- Session expiration
- Session rotation
- CSRF considerations
- XSS considerations
- Login rate limiting
- Secure password storage
- Authentication with backend APIs
- Protecting Server Actions
- Protecting database mutations
Final Mental Model
The most important architecture to remember is:
1 USER 2 ↓ 3 LOGIN 4 ↓ 5 AUTHENTICATION API 6 ↓ 7 Verify Credentials 8 ↓ 9 SESSION / TOKEN 10 ↓ 11 Protected Request 12 ↓ 13 Server Authentication 14 ↓ 15 Server Authorization 16 ↓ 17 ┌────────────┴────────────┐ 18 ↓ ↓ 19 ALLOWED DENIED 20 ↓ ↓ 21 Business Logic 401 / 403 22 ↓ 23 Database 24 ↓ 25 Protected Response
The key principle is:
Authentication establishes identity, while authorization establishes permission. Both must be enforced on the server. Hiding UI elements is useful for UX, but the real security boundary is the server.