Full Stack Development
by Tomas Trescak
t.trescak@westernsydney.edu.au
Authentication
/
Introduction
- Authentication = User identity verification
- Enables secure, personalised experiences
- Approaches: Cookies, Tokens, OAuthm SAML
// app/api/login/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { hash, compare } from 'bcryptjs';
type User = { email: string; password: string };
const users: User[] = []; // Fake DB
export async function POST(req: NextRequest) {
const { email, password } = await req.json() as User;
const user = users.find(u => u.email === email);
if (!user || !(await compare(password, user.password))) {
return NextResponse.json(
{ message: 'Wrong email or password' },
{ status: 401 });
}
return NextResponse.json({ message: 'Logged in!' });
}
Authentication
/
Cookies
- Cookies holds ID
- Sent to the server on every request
- Secure + HTTP Only
Authentication
/
Cookies
import { NextRequest, NextResponse } from 'next/server';
import { hash, compare } from 'bcryptjs';
type User = { id: number; email: string; password: string };
const users: User[] = [
{
id: 1,
email: 'alice@example.com', password: await hash('supersecret', 10)
sessionIds: []
}
];
export async function POST(req: NextRequest) {
const { email, password } = await req.json();
const user = users.find(u => u.email === email);
if (!user || !(await compare(password, user.password))) {
return NextResponse.json(
{ message: 'Wrong credentials' },
{ status: 401 }
);
}
const sessionId = Math.random().toString(36).slice(2).toString();
user.sessionIds.push(sessionId);
const response = NextResponse.json({
message: 'Logged in',
userId: user.id
});
response.cookies.set('session', sessionId, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 24 * 60 * 60 // 1 day
});
return response;
}
/app/api/login/route.tsx
Authentication
/
Cookies
import { NextRequest, NextResponse } from 'next/server';
import { users, drafts } from '@app/db';
import { cookies } from 'next/headers'
type Draft = { id: number; title: string; userId: number };
export async function GET(req: NextRequest) {
const cookieStore = await cookies()
const sessionId = cookieStore.get('session')?.value;
const user = users.find(u => u.sessionId.includes(sessionId));
if (!sessionId || !user) {
return NextResponse.json(
{ message: 'Not authenticated' },
{ status: 401 }
);
}
// Authenticated
const userDrafts = drafts.filter(d => d.userId === user.id);
return NextResponse.json(userDrafts);
}
/app/api/drafts/route.tsx
Authentication
/
JWT
- Session Token
-
Authorization: Bearer mytoken123
- Refresh token
Authentication
/
JWT | Login
import { NextRequest, NextResponse } from 'next/server';
import jwt from 'jsonwebtoken';
type User = { id: number; email: string; role: 'user' | 'admin' };
const SECRET = 'my-secret-key';
const users: User[] = [{ id: 1, email: 'alice@example.com', role: 'user' }];
const refreshTokens: Map<string, number> = new Map();
export async function POST(req: NextRequest) {
const { email, password } = await req.json();
const user = users.find(u => u.email === email && u.password == password);
if (!user) {
return NextResponse.json({ message: 'User not found' }, { status: 401 })
};
const sessionToken = jwt.sign(
{ userId: user.id, role: user.role },
SECRET,
{ expiresIn: '15m' }
);
const refreshToken = `rf_${Math.random().toString(36).slice(2)}`;
refreshTokens.set(refreshToken, user.id);
const response = NextResponse.json({ sessionToken });
response.cookies.set(
'refresh',
refreshToken,
{ httpOnly: true, secure, maxAge: 7 * 24 * 60 * 60 }
);
return response;
}
/app/api/login/route.tsx
Authentication
/
JWT | Refresh
import { NextRequest, NextResponse } from 'next/server';
import jwt from 'jsonwebtoken';
import { refreshTokens, users } from '@app/db'
import { cookies } from 'next/headers';
export async function POST(req: NextRequest) {
const refreshToken = (await cookies()).get('refresh')?.value;
const userId = refreshTokens.get(refreshToken!);
if (!userId) return NextResponse.json(
{ message: 'Invalid refresh token' },
{ status: 401 }
);
const user = users.find({ id: userId });
const newSessionToken = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
return NextResponse.json({ sessionToken: newSessionToken });
}
/app/api/refresh/route.tsx
Authentication
/
JWT | Usage
import { NextRequest, NextResponse } from 'next/server';
import jwt from 'jsonwebtoken';
import { posts } from '@app/db';
import { cookies } from 'next/headers';
export async function GET(req: NextRequest) {
const token = (await headers()).get('authorization')?.split(' ')[1];
if (!token) return NextResponse.json(
{ message: 'No token' },
{ status: 401 }
);
try {
const { userId } = jwt.verify(
token,
process.env.SECRET
) as { userId: number };
const tag = req.nextUrl.searchParams.get('tag');
const filteredPosts = posts.filter(
p => p.userId === userId && (!tag || p.tags.includes(tag))
);
return NextResponse.json(filteredPosts);
} catch {
return NextResponse.json({ message: 'Invalid token' }, { status: 401 });
}
}
/app/api/refresh/route.tsx
Authentication
/
JWT
Authentication
/
OAUTH | Login
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import { NextAuthOptions } from 'next-auth';
export const authOptions: NextAuthOptions = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
],
callbacks: {
async jwt({ token, account }) {
if (account) {
token.accessToken = account.access_token;
token.role = account.email === 'alice@gmail.com' ? 'admin' : 'user';
}
return token;
},
async session({ session, token }) {
session.user.role = token.role as 'user' | 'admin';
return session;
},
},
};
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
/app/api/auth/[...nextauth]/route.ts
Authentication
/
OAUTH | Uasge
// app/api/admin/posts/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from '/app/api/auth/[...nextauth]/route';
type Post = { id: number; title: string; userId: number };
const posts: Post[] = [{ id: 1, title: 'Admin Post', userId: 1 }];
export async function GET(req: NextRequest) {
const session = await getServerSession(authOptions);
if (!session || session.user.role !== 'admin') {
return NextResponse.json(
{ message: 'Not authorized' },
{ status: 403 }
);
}
return NextResponse.json(posts);
}
/app/api/posts/route.ts
Authentication
/
SAML
- Single Sign-On
-
Enterprise-Based
-
XML Protocol
-
Complicated
Authentication
/
Wrap Up
Method
|
Security
|
Ease of Use
|
Setup Effort
|
Best For
|
Cookies
|
Good (HTTPS + flags needed) |
Super easy (login once) |
Low (built-in stuff) |
Small apps, simple logins |
JWT + Refresh
|
Strong (short tokens limit damage) |
Okay (tokens to manage) |
Medium (code tokens) |
Apps on phones, scaling up |
OAuth (Google)
|
Very strong (Google’s 2FA) |
Awesome (no passwords) |
Medium (Google setup) |
User-friendly, third-party |
SAML
|
Top-tier (enterprise-grade) |
Good (SSO is smooth) |
High (XML and IdP) |
Company apps, single sign-on |