Lesson Progress
0% Complete

Authentication and authorisation are closely related, but they solve different problems:

  • Authentication (AuthN) answers: Who are you?
    Example: logging in with an email and password.
  • Authorisation (AuthZ) answers: What are you allowed to do?
    Example: an “Editor” can publish posts, but a “Viewer” cannot.

A secure website needs both. If you only authenticate users but do not control what they can access, users may be able to view or change data they should not.


Key concepts and common terms

  • User identity: the user record in your database (usually an id).
  • Credentials: proof of identity (password, OTP, passkey, etc.).
  • Session / token: something the server and client use to remember that a user is logged in.
  • Role: a broad category of access (e.g. admin, staff, customer).
  • Permission: a specific action (e.g. posts:edit, users:delete).
  • Least privilege: give users the minimum access needed to do their job.

Sessions vs JWTs (JSON Web Tokens)

Both sessions and JWTs help your site remember a logged-in user across requests (HTTP is stateless, so the server needs a way to “remember”).

Option A: Session-based authentication (server-stored)

How it works

  1. User logs in with credentials.
  2. Server verifies the credentials.
  3. Server creates a session record (stored on the server, often in memory store like Redis or in a database).
  4. Server sends a session cookie to the browser.
  5. Browser sends that cookie with each request.
  6. Server looks up the session and knows which user is logged in.

Pros

  • Easy to revoke: delete/expire the session server-side.
  • Token contents are not exposed to the client (only a session id).
  • Good fit for traditional websites where the backend renders pages.

Cons

  • Requires server-side storage and scaling strategy (shared session store if multiple servers).
  • You must handle cookie security carefully.

Typical use

  • Web apps with browser clients using cookies (HttpOnly, Secure).

Option B: JWT-based authentication (token-stored)

A JWT is a signed token containing “claims” (information), like userId, role, and an expiry time.

How it works

  1. User logs in.
  2. Server verifies credentials.
  3. Server issues a JWT (signed) and returns it to the client.
  4. Client sends JWT on each request (often in Authorization: Bearer <token> header).
  5. Server verifies the signature and reads the claims.

Pros

  • No session store needed (stateless on the server).
  • Works well for APIs used by mobile apps and multiple front ends.

Cons

  • Revocation is harder (if stolen, it works until expiry unless you add extra controls).
  • If you put too much information in the token, you risk leaking data.
  • Storing JWTs in the browser can be risky if done incorrectly.

Typical use

  • Public APIs, mobile clients, microservices.

Practical guidance: choosing between them

  • If you are building a browser-based website with your own backend, sessions with secure cookies are often simpler and safer.
  • If you are building an API for multiple clients (mobile + web + third-party), JWTs can work well, but you must design for token expiry, refresh, and possible revocation.

Password hashing (never store passwords in plain text)

What to do

  • Store a hash of the password, not the password itself.
  • Use a password hashing algorithm designed for passwords:
    • Argon2id (recommended where available)
    • bcrypt (still widely used)
  • Use a unique salt per user (modern password hashing libraries handle this).
  • Use a work factor (cost) that is slow enough to resist brute force, but still acceptable for user experience.

What not to do

  • Do not “encrypt” passwords and store the encrypted value.
  • Do not use fast hashing algorithms like MD5, SHA-1, or plain SHA-256 for passwords (they are too fast and easier to brute force).
  • Do not log passwords (even in development logs).

Password policy (balanced approach)

Encourage strong passwords without making it impossible to comply:

  • Minimum length (e.g. 12+ characters).
  • Allow passphrases (spaces allowed).
  • Block known breached passwords if possible.
  • Consider multi-factor authentication for sensitive systems.

Safe login flows (end-to-end)

A secure login flow (typical)

  1. User submits login form (over HTTPS).
  2. Server:
    • Finds the user by email/username.
    • Verifies password using the hashing function.
    • Applies rate limits and brute-force protections.
  3. On success:
    • Creates a session or issues a token.
    • Returns it securely (cookie or header).
  4. User is now authenticated for future requests.

Don’t leak account information

Error messages should not reveal whether an account exists:

  • Avoid: “Email not found”
  • Prefer: “Invalid email or password”

Brute-force and credential stuffing protection

Add multiple layers:

  • Rate limiting on login endpoint (by IP and/or username).
  • Account lockout carefully (temporary lockouts can be abused to block real users).
  • CAPTCHA only when suspicious behaviour is detected (avoid for every login if possible).
  • Monitor login attempts and alert on anomalies.

Password reset flow (must be secure)

A safe reset process:

  • User requests password reset (enter email).
  • Always respond with the same message: “If that email exists, we have sent instructions.”
  • Generate a single-use, short-lived token.
  • Send reset link via email.
  • When the user sets a new password:
    • Validate token
    • Hash and store new password
    • Invalidate existing sessions/tokens

Session security (if using cookies)

If using cookies for sessions, set the right cookie flags:

  • HttpOnly: JavaScript cannot read the cookie (reduces XSS impact).
  • Secure: cookie only sent over HTTPS.
  • SameSite:
    • Lax often works well for many sites.
    • Strict is more restrictive.
    • None is required for some cross-site scenarios but must be paired with Secure.

Also:

  • Regenerate the session id after login (prevents session fixation).
  • Expire sessions (idle timeout + absolute timeout).
  • Store only a session identifier in the cookie, not sensitive data.

JWT security (if using tokens)

Important JWT rules

  • Keep JWTs short-lived (e.g. 5–15 minutes for access tokens).
  • Use refresh tokens if you need long sessions:
    • Store refresh tokens securely (often in an HttpOnly cookie).
    • Rotate refresh tokens (issue a new one each time, invalidate the old one).
  • Use strong signing algorithms (commonly RS256 or HS256 with strong secrets).
  • Validate:
    • Signature
    • Expiry (exp)
    • Issuer (iss) and audience (aud) if you use them

Storage considerations (browser apps)

  • Avoid storing JWTs in localStorage if you can, because XSS can steal them.
  • A safer pattern for browser-based apps is:
    • Access token in memory (short-lived)
    • Refresh token in HttpOnly cookie (server-controlled)

Access control (Authorisation)

Two key checks to apply

  1. Authentication check: Is the user logged in?
  2. Authorisation check: Is the user allowed to perform this action on this resource?

A common mistake is to only protect the front end (hiding buttons). You must enforce access control on the server.


Roles and permissions

Role-Based Access Control (RBAC)

You assign users to roles, and roles grant access.

Example roles:

  • admin: manage users, settings
  • editor: create/edit/publish content
  • viewer: read-only access

Pros

  • Simple to understand and manage.
  • Works well for many business apps.

Cons

  • Can become too broad: roles grow messy when you need fine-grained rules.

Permission-based (fine-grained)

Instead of (or as well as) roles, you assign permissions like:

  • posts:create
  • posts:publish
  • users:read
  • users:delete

Pros

  • Clear and flexible.
  • Works well when different teams need different actions.

Cons

  • More work to design and maintain.

Resource-based checks (ownership)

Even with roles/permissions, you often need ownership logic:

  • A user can edit their own profile
  • But cannot edit another user’s profile unless they are an admin

This usually looks like:

  • if (user.id === resource.ownerId) allow
  • else require elevated permission (e.g. admin)

Where to enforce authorisation

Apply checks at multiple layers, but treat the server as the source of truth:

  • Route-level checks: protect entire endpoints (e.g. /admin/*)
  • Action-level checks: protect specific actions (e.g. publish, delete)
  • Data-level checks: restrict database queries so users only see allowed records
    Example: SELECT ... WHERE owner_id = currentUserId

A strong pattern is: deny by default, then explicitly allow.


Common security risks and how to avoid them

Broken Access Control

Risk: Users access other users’ data by changing an id in a URL.
Example: /orders/1234/orders/1235

Fix:

  • Always check ownership/permissions for the specific resource id.
  • Filter queries by the current user where appropriate.

Insecure Direct Object References (IDOR)

This is a common form of broken access control where predictable IDs are used without permission checks.

Fix:

  • Permission checks (primary fix)
  • Consider using non-guessable IDs (like UUIDs) where suitable (secondary measure)

Cross-Site Request Forgery (CSRF) (mostly for cookie-based sessions)

Risk: Attacker tricks a logged-in user’s browser to submit a request.

Fix:

  • Use SameSite cookies (Lax or Strict)
  • Use CSRF tokens for state-changing requests (POST/PUT/PATCH/DELETE) where needed

Cross-Site Scripting (XSS)

Risk: Attacker runs JavaScript in your site and steals tokens or performs actions.

Fix:

  • Escape output and sanitise untrusted HTML
  • Use Content Security Policy (CSP)
  • Prefer HttpOnly cookies for session/refresh tokens

Implementation checklist (practical)

Use this checklist when building authentication and authorisation:

  • [ ] Use HTTPS everywhere (especially login and cookies).
  • [ ] Hash passwords with Argon2id or bcrypt (with appropriate cost).
  • [ ] Provide generic login and reset responses (avoid account enumeration).
  • [ ] Add rate limiting and monitoring on auth endpoints.
  • [ ] Choose session or JWT approach based on your app architecture.
  • [ ] Secure cookies (HttpOnly, Secure, SameSite) if using sessions.
  • [ ] Keep tokens short-lived and validate JWT claims if using JWTs.
  • [ ] Enforce authorisation on the server for every protected action.
  • [ ] Implement RBAC/permissions + ownership checks for real-world access control.
  • [ ] Log security events safely (no passwords/tokens), and review regularly.

Mini practice task

Design access rules for a simple blogging system:

  1. Define at least three roles (e.g. Admin, Editor, Viewer).
  2. List five permissions (e.g. create post, publish post, delete comment).
  3. Write down ownership rules (e.g. Editors can edit their own posts, Admin can edit any).
  4. Identify two endpoints and describe what checks happen server-side:
    • POST /posts
    • DELETE /posts/:id

This exercise helps you think like a real developer: authentication tells you who the user is, and authorisation decides what they can do—every time.