Full Stack Development

Authentication



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