Authentication Basics: Login and Signup
Understand authentication concepts, build login and signup flows, manage sessions, and protect routes — all with AI-assisted implementation.
How authentication works behind the scenes
Authentication answers a simple question: who are you? When a user logs in with an email and password, the server checks the credentials and issues a token — a digital proof of identity that the browser sends with every subsequent request. There are two main approaches. Session-based authentication stores the user's identity on the server. After login, the server creates a session (a record in a database or memory), gives the browser a session ID as a cookie, and on every request, looks up the session ID to identify the user. Token-based authentication (JWT) encodes the user's identity in a signed token. After login, the server creates a JWT containing the user ID and role, sends it to the browser, and on every request, the browser sends the token back. The server verifies the signature without needing to look anything up. Ask Claude Code: Create a diagram as comments in a new file src/auth/README.md showing the login flow for both session-based and JWT-based authentication. Include every step from the user clicking Login to the server responding with protected data. For most web applications, session-based auth with HTTP-only cookies is the safer choice. JWTs are useful for APIs consumed by mobile apps or third-party clients. The critical security rule: never store passwords in plain text. When a user signs up, hash the password with bcrypt before storing it. When they log in, hash the submitted password and compare it to the stored hash. Ask Claude Code: Explain why bcrypt is preferred over SHA-256 for password hashing and demonstrate the difference.
Building the signup flow
A signup flow collects credentials, validates them, creates a user record, and starts a session. Ask Claude Code: Create a signup system with these files. src/auth/signup.ts for the server-side logic, and a React component for the signup form. The form needs: email (validated format and uniqueness), password (minimum 8 characters, at least one number and one letter), confirm password (must match password), and a submit button. The server-side signup function should: validate the email format, check that no existing user has this email (return a specific error if duplicate), validate password strength requirements, hash the password with bcrypt (cost factor 12), create the user record in the database, create a session for the new user, and return the session token. Install bcrypt: npm install bcrypt @types/bcrypt. The hashing code: import bcrypt from 'bcrypt'; const hashedPassword = await bcrypt.hash(password, 12). The number 12 is the cost factor — higher means slower hashing, which makes brute-force attacks harder. 12 is a good balance between security and performance (about 250ms per hash). Ask Claude Code: Add these signup validations — reject passwords that contain the user's email, reject commonly breached passwords (check against a list of the top 1000), and rate-limit signup attempts to 5 per IP per hour. Common error: if bcrypt.hash hangs or crashes, you may have installed the wrong bcrypt package. Use bcrypt (native, faster) on servers or bcryptjs (pure JavaScript, works everywhere) in serverless environments. Ask Claude Code: Which bcrypt package should I use for my deployment target? Switch if needed.
Building the login flow
Login verifies credentials and creates a session. Ask Claude Code: Create a login system. The login form has email and password fields. The server-side logic should: find the user by email, compare the submitted password against the stored hash using bcrypt.compare, create a new session if valid, return an appropriate error if invalid, and never reveal whether the email exists (say 'Invalid email or password' for both wrong email and wrong password — this prevents attackers from discovering which emails are registered). The bcrypt comparison: const isValid = await bcrypt.compare(submittedPassword, storedHash). This returns true or false. Never compare hashes as strings — bcrypt includes a random salt, so the same password produces different hashes each time. Add rate limiting to prevent brute-force attacks. After 5 failed login attempts for the same email, lock the account for 15 minutes. After 10 failed attempts from the same IP, block that IP for an hour. Ask Claude Code: Implement login rate limiting with an in-memory store. Track failed attempts by email and by IP address. Return appropriate error messages telling the user how long until they can try again. Add a "forgot password" flow: the user enters their email, the server sends a password reset link with a time-limited token (expires in 1 hour), the user clicks the link and enters a new password, and the server validates the token, hashes the new password, and invalidates all existing sessions. Ask Claude Code: Build the forgot password flow. Generate a cryptographically secure reset token, store it with an expiry timestamp, and create the reset form page. Common error: sending a reset email should always return success even if the email does not exist in your database. Otherwise, attackers can probe for registered emails by checking which addresses trigger a 'no account found' response.
Session management and cookies
After login, you need to maintain the user's authenticated state across page loads and requests. The most secure approach for web apps is HTTP-only cookies. Ask Claude Code: Implement session management using HTTP-only cookies. After successful login, set a cookie with these attributes: httpOnly (JavaScript cannot read it — prevents XSS theft), secure (only sent over HTTPS), sameSite set to lax (prevents CSRF for most cases), path set to / (available on all pages), maxAge set to 7 days in seconds (automatic expiry). The session ID in the cookie maps to a session record stored server-side. The session record contains: the user ID, creation timestamp, last activity timestamp, IP address, and user agent. Ask Claude Code: Create a session store using a database table. Include functions to create a session, validate a session (check it exists and has not expired), refresh a session (update last activity), and destroy a session (logout). For every authenticated request, middleware checks the session. Ask Claude Code: Create authentication middleware that reads the session cookie, validates the session, attaches the user object to the request, and returns 401 if the session is invalid or expired. Add the middleware to all protected routes. Implement session cleanup: a scheduled task that deletes expired sessions from the database. Sessions older than 30 days should be removed even if the maxAge has not technically expired — this limits the damage if a session is compromised. Ask Claude Code: Create a session cleanup script that runs daily and deletes stale sessions. Add it as an npm script. Common error: if your cookies are not being set, check that you are on HTTPS (required for secure cookies) and that your domain matches the cookie domain. In development, use localhost without the secure flag.
Protecting routes and role-based access
With authentication in place, you need to protect routes that require login and restrict certain routes to specific user roles. Ask Claude Code: Create a route protection system. Build a middleware function called requireAuth that checks for a valid session and redirects to the login page if not authenticated. Build another middleware called requireRole that takes a role parameter and checks that the authenticated user has that role. Apply requireAuth to routes like /dashboard, /settings, and /profile. Apply requireRole('admin') to routes like /admin, /users/manage, and /settings/system. For Next.js with the App Router, create a middleware.ts file in the project root. Ask Claude Code: Create a Next.js middleware that checks authentication for protected routes. Define a list of public routes (login, signup, homepage, public API endpoints) and redirect everything else to login if the user is not authenticated. On the client side, create a React context that provides the current user's authentication state to all components. Ask Claude Code: Build an AuthContext provider that checks the session on page load, provides the current user object to child components, exposes login, logout, and signup functions, and handles loading states (show a spinner while checking the session, not a flash of the login page). Implement role-based UI: certain buttons, links, and sections should only appear for users with the right role. Ask Claude Code: Create a PermissionGate component that takes a required role and only renders its children if the current user has that role. Use it to conditionally show admin features. Common error: never rely on client-side checks alone. A user can modify React state or local storage to bypass client-side role checks. Always validate permissions on the server for every request that modifies data.
OAuth and third-party login providers
Many users prefer logging in with Google, GitHub, or other existing accounts rather than creating yet another password. OAuth enables this. Instead of handling passwords yourself, you delegate authentication to a trusted provider. The flow: user clicks Sign in with Google, your app redirects to Google's login page, the user authenticates with Google, Google redirects back to your app with an authorization code, your server exchanges the code for user information (email, name, profile picture), and you create or update the user in your database and start a session. Ask Claude Code: Set up NextAuth.js (or Auth.js) with Google and GitHub providers. Create the auth configuration, the API route handler, and a sign-in page with provider buttons. NextAuth handles the OAuth complexity — token exchange, session management, and callback URLs. You configure the providers and it handles the rest. Install: npm install next-auth. Create src/app/api/auth/[...nextauth]/route.ts with your provider configuration. You need client IDs and secrets from each provider — get them from the Google Cloud Console and GitHub Developer Settings. Ask Claude Code: Walk me through getting OAuth credentials from Google and GitHub. Create .env.example entries for the required secrets. After setup, test the complete flow: click Sign in with Google, authenticate, and verify you are redirected back with a valid session. Check that the user was created in your database with the correct email and name. Add account linking: if a user signs up with email/password and later tries to sign in with Google using the same email, link the accounts instead of creating a duplicate. Ask Claude Code: Implement account linking in my auth system. When an OAuth login matches an existing email, link the provider to the existing account instead of creating a new one. Common error: OAuth redirect URI mismatch. The redirect URI in your provider configuration must exactly match what you registered in the Google/GitHub console — including the protocol (http vs https) and port number.
Security hardening and production checklist
Before deploying authentication to production, run through this security checklist. Ask Claude Code: Audit my authentication system against the OWASP top 10 and create a security report listing any vulnerabilities with fixes. Password storage: verify passwords are hashed with bcrypt (cost factor 12 or higher). Never log passwords, even in error handlers. Do not store the plain password anywhere, not even temporarily. HTTPS: all authentication requests must use HTTPS in production. Set the HSTS header to tell browsers to always use HTTPS. Ask Claude Code: Add security headers to my application — HSTS, Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, and Referrer-Policy. CSRF protection: if using cookie-based sessions, add CSRF tokens to all forms. A CSRF token is a random value that the server generates and the form includes as a hidden field. On submission, the server verifies the token matches — this prevents attackers from crafting malicious forms on other sites that submit to your server. Ask Claude Code: Implement CSRF protection for all my forms. Generate a token per session, include it in forms, and validate it on the server. Input sanitisation: all user input (email, name, password) must be sanitised before storage or display. This prevents stored XSS where an attacker registers with a name like <script>alert('hacked')</script> and that script executes when their name is displayed to other users. Logging and monitoring: log all authentication events (signup, login, logout, failed attempts, password changes) with timestamps and IP addresses. Do not log passwords or session tokens. Set up alerts for unusual patterns: many failed logins from one IP, successful login from a new country, or multiple password reset requests. Ask Claude Code: Add authentication event logging to my system. Log to a structured format that can be searched and alerted on. Run a final security check: npm audit to find vulnerable dependencies. Ask Claude Code: Are there any security issues in my authentication implementation that I have missed?
Security and Authentication
This guide is hands-on and practical. The full curriculum covers the conceptual foundations in depth with structured lessons and quizzes.
Go to lesson