Build a Startup Cheatsheet
Auth and OAuth
Use this Build a Startup reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Why Auth Is Harder Than It Looks
Authentication is one of the most deceptively complex parts of any product. Getting it wrong means security vulnerabilities, user lockouts, or credential leaks. Getting it right from scratch takes weeks and requires deep knowledge of JWTs, session management, CSRF protection, OAuth flows, and token refresh logic.
The practical recommendation: use an auth library or service unless auth is your competitive differentiation (which it almost never is).
Core Concepts
Before choosing a tool, understand the building blocks:
Authentication — proving who you are ("I am Alice").
Authorization — what you are allowed to do ("Alice can edit this post").
Session — a server-side record that a user is logged in. Stored on the server, referenced via a cookie.
JWT (JSON Web Token) — a signed, self-contained token that encodes identity claims. The server signs it; clients present it with every request. No server-side state required, but harder to invalidate.
OAuth 2.0 — a protocol that lets users authorize your app to act on their behalf at a third-party service (Google, GitHub, etc.). Commonly used for "Sign in with Google".
OIDC (OpenID Connect) — an identity layer on top of OAuth 2.0. When you "Sign in with Google", OIDC is what actually gives you the user's profile information.
Cookie vs. Authorization header
httpOnlycookies — browser sends them automatically; JavaScript cannot read them; best for web apps; CSRF-safe when combined withSameSite=LaxAuthorization: Bearer <token>header — JavaScript sets it manually; required for mobile/API clients; no CSRF risk but exposed to XSS
Option 1: Clerk (Recommended for Most)
Clerk is a complete auth platform. It provides hosted sign-in/sign-up UI, session management, OAuth, MFA, and user management — embedded via a React component or a hosted page.
Free tier: 10,000 monthly active users.
Pricing: $25/month after the free tier.
Setup time: Under an hour.
Pros:
- Best developer experience in the category — UI components included
- Handles every OAuth provider (Google, GitHub, Apple, etc.)
- Built-in user dashboard (manage users, ban accounts, view sessions)
- Pre-built React components: <SignIn />, <UserButton />, <SignedIn />
- Webhooks for user creation/deletion events
- Works seamlessly with Next.js middleware
Cons: - Vendor lock-in; migrating away is painful (user passwords are stored with Clerk) - $25/month can feel steep for early-stage products - Less control over the auth flow UI if you want full custom design
// middleware.ts import { clerkMiddleware } from "@clerk/nextjs/server"; export default clerkMiddleware(); // Protected route import { auth } from "@clerk/nextjs/server"; const { userId } = await auth(); if (!userId) redirect("/sign-in");
Option 2: Supabase Auth
Supabase includes a full auth system. If you are already using Supabase for your database, Supabase Auth is the natural choice.
Free tier: 50,000 monthly active users.
Pros: - Included with Supabase at no extra cost - Email/password, magic links (passwordless), OAuth, phone/OTP - JWTs that integrate directly with Row-Level Security policies - User table is in your Postgres database — queryable with standard SQL - Social providers: Google, GitHub, Apple, Discord, Twitter, and more
Cons: - UI components are more basic than Clerk's — you build your own sign-in form - Session refresh logic requires setup - Less granular user management dashboard than Clerk
import { createClient } from "@supabase/supabase-js"; const supabase = createClient(url, key); // Sign up await supabase.auth.signUp({ email, password }); // Sign in with Google OAuth await supabase.auth.signInWithOAuth({ provider: "google" }); // Get current user const { data: { user } } = await supabase.auth.getUser();
Option 3: Auth.js (formerly NextAuth)
Auth.js is an open-source auth library for Next.js (and other frameworks). You install it, configure providers, and it handles OAuth flows, session cookies, and callbacks.
Cost: Free (open-source).
Pros: - Free and open-source; no vendor lock-in - Works with any Postgres, MySQL, or MongoDB adapter - 50+ OAuth providers pre-configured (Google, GitHub, Spotify, etc.) - Full control over session behavior
Cons: - More configuration required than Clerk or Supabase Auth - No hosted UI — you build your own sign-in page - Documentation and API changed significantly between v4 and v5
// auth.ts import NextAuth from "next-auth"; import Google from "next-auth/providers/google"; export const { handlers, auth, signIn, signOut } = NextAuth({ providers: [Google], callbacks: { session({ session, token }) { session.user.id = token.sub!; return session; }, }, });
Option 4: Hand-Rolled Auth (When It Makes Sense)
Building auth yourself is the right choice when:
- You have very specific requirements (e.g., invite-only sign-up, custom application flow)
- Your team has security expertise and time to invest
- You want zero external auth dependencies
A minimal secure implementation requires:
- Password hashing with
bcryptorargon2— never store plaintext passwords httpOnly,Secure,SameSite=Laxsession cookies- Crypto-random session tokens (not predictable IDs)
- Rate limiting on login endpoints
- CSRF protection
- Secure password reset flow (time-limited, single-use tokens)
import bcrypt from "bcryptjs"; import { randomBytes } from "crypto"; // Hash on sign-up const hash = await bcrypt.hash(password, 12); // Verify on login const valid = await bcrypt.compare(password, storedHash); // Generate a session token const token = randomBytes(32).toString("hex");
OAuth: "Sign in with Google" Flow
OAuth is a delegation protocol, not an authentication protocol — but OIDC layers identity on top. Here is what happens when a user clicks "Sign in with Google":
- Your app redirects the user to Google's authorization server with your
client_id, requestedscopes, and aredirect_uri - The user approves at Google
- Google redirects back to your
redirect_uriwith an authorizationcode - Your server exchanges the
codefor anaccess_tokenandid_token(server-to-server, never exposed to the browser) - You verify the
id_token, extract the user's email and Google ID, and create or look up their account
Auth.js, Clerk, and Supabase Auth handle all of this for you. You only need the client_id and client_secret from the provider's developer console.
Getting Google OAuth credentials:
1. Go to console.cloud.google.com
2. Create a project → APIs & Services → Credentials → OAuth 2.0 Client ID
3. Set Authorized redirect URIs to http://localhost:3000/api/auth/callback/google (dev) and your production URL
Comparing Auth Options
| Clerk | Supabase Auth | Auth.js | Hand-rolled | |
|---|---|---|---|---|
| Setup time | 30 min | 1 hour | 2–3 hours | Days |
| Cost | $25/mo after 10K MAU | Free (bundled) | Free | Free |
| UI components | Built-in | Basic | None | Build yourself |
| OAuth providers | 30+ | 20+ | 50+ | Manual |
| User dashboard | Excellent | Good | None | Build yourself |
| Vendor lock-in | High | Medium | Low | None |
| Flexibility | Low | Medium | High | Full |
| Best for | Speed, great UX | Supabase users | Custom needs | Unique requirements |
Security Checklist for Auth
- Passwords hashed with bcrypt/argon2 (cost factor ≥10)
- Session cookies:
httpOnly,Secure,SameSite=Lax, short max-age (7–30 days) - Rate limit login attempts (5–10 per IP per 15 minutes)
- Email verification before account activation
- Secure password reset: time-limited (15–60 min), single-use, invalidated on use
- Logout invalidates the session server-side
- No sensitive user data in JWT payloads (JWTs are base64, not encrypted)
- HTTPS everywhere — never send credentials over HTTP