Everyone Can Learn Program
中文 Start learning
Specialty · BackendLesson 4 of 6Free to read

Sessions, 401 and 403 · Backend Development

How does a server know who you are, and what you're allowed to do?

Course contents

Why you need it

"Broken access control" is number one in the OWASP Top 10 (2021), the best-known list of web security risks: users reading or changing things that belong to someone else. It usually isn't clever hacking, just a missing check in one handler. Learning to ask two questions on every request, who is this? and may they do this?, prevents most of it.

The intuition

Two different questions, two different status codes:

  • Authentication: who are you? No valid login → 401 Unauthorized.
  • Authorization: you're logged in, but may you do this? No → 403 Forbidden.

A common way to remember a login is a session:

  1. The user logs in with a password. The server checks it and creates a random session id, stored server-side: SESSIONS["s-8f3a…"] = "ada".
  2. The response sets a cookie: Set-Cookie: session=s-8f3a…; HttpOnly; Secure.
  3. The browser sends it back on every request: Cookie: session=s-8f3a…. The server looks it up to find the user.

Then each handler checks the rules: an admin may delete any post; a member only their own.

Never take the user's identity from the request body or URL ({"user": "ada"}): anyone can type that. Only trust what the server itself stored.

Definition

Authentication

Finding out who is making the request. Failing it means 401.

Authorization

Deciding whether that user may do this action on this thing. Failing it means 403.

Session

A random id the server stores next to a user, sent to the browser in a cookie and returned on every request.

Examples

Who is this?

Try it yourself

Read the session cookie and look it up.

SESSIONS = {"s-ada": "ada", "s-bo": "bo"}
ROLES = {"ada": "admin", "bo": "member"}
POSTS = {1: {"author": "bo", "text": "Hello"}, 2: {"author": "ada", "text": "Welcome"}}

def current_user(headers):
    cookie = headers.get("Cookie", "")
    for part in cookie.split(";"):
        name, _, value = part.strip().partition("=")
        if name == "session" and value in SESSIONS:
            return SESSIONS[value]
    return None

print(current_user({"Cookie": "theme=dark; session=s-bo"}))
print(current_user({"Cookie": "session=s-fake"}))
print(current_user({}))
Output
bo
None
None
Step by step
  1. L6No Cookie header means an empty string, not an error.
  2. L8"session=s-bo" splits into name "session" and value "s-bo".
  3. L9Only ids the server created count.
  4. L14An unknown id: nobody.None

401 versus 403

Try it yourself

Check who, then check what.

SESSIONS = {"s-ada": "ada", "s-bo": "bo"}
ROLES = {"ada": "admin", "bo": "member"}
POSTS = {1: {"author": "bo", "text": "Hello"}, 2: {"author": "ada", "text": "Welcome"}}

def current_user(headers):
    cookie = headers.get("Cookie", "")
    for part in cookie.split(";"):
        name, _, value = part.strip().partition("=")
        if name == "session" and value in SESSIONS:
            return SESSIONS[value]
    return None

def edit_post(headers, post_id, text):
    user = current_user(headers)
    if user is None:
        return 401, {"error": "log in first"}
    post = POSTS[post_id]
    if user != post["author"] and ROLES[user] != "admin":
        return 403, {"error": "not your post"}
    post["text"] = text
    return 200, post

print(edit_post({}, 1, "Hi"))
print(edit_post({"Cookie": "session=s-bo"}, 2, "Hi"))
print(edit_post({"Cookie": "session=s-bo"}, 1, "Hi"))
print(edit_post({"Cookie": "session=s-ada"}, 1, "Edited"))
Output
(401, {'error': 'log in first'})
(403, {'error': 'not your post'})
(200, {'author': 'bo', 'text': 'Hi'})
(200, {'author': 'bo', 'text': 'Edited'})
Step by step
  1. L16No login: 401.
  2. L19Logged in as bo, but post 2 is ada's and bo isn't an admin: 403.
  3. L26ada is an admin, so she may edit anyone's post.

Common mistakes

Trusting the client about identity

A user id in the body, URL or a plain cookie like user=ada can be changed by anyone. Look the user up from a session (or a signed token) on the server.

Checking only that someone is logged in

Being logged in doesn't mean every post is yours. Check ownership or role in every handler that changes data.

Mixing up 401 and 403

401 means "log in" (the client can fix it by authenticating). 403 means "no, even though we know who you are".

Quick quiz

1. Bo is logged in and tries to delete Ada's account. Bo isn't an admin. Which status?

  1. A403 Forbidden
  2. B401 Unauthorized
  3. C404 Not Found
Show the answer

Answer: A

We know who Bo is (authenticated), but he isn't allowed (not authorized).

2. Where should a handler get the current user from?

  1. AA server-side session found via the cookie
  2. BA "user" field in the JSON body
  3. CA ?user= parameter in the URL
Show the answer

Answer: A

Only data the server stored itself can be trusted; the rest is typed by the client.

Practice

3 exercises in three steps: predict what a program prints, change a program to meet a new requirement, then write one from scratch. Your code runs in the browser and is checked the moment you submit.

  1. PredictFour requests

    Type exactly what this prints.

  2. ModifyOnly your own posts

    Add the missing authorization check to delete_post: the author or an admin may delete a post; anyone else gets 403 {"error": "not your post"}. Keep 401 and 404 as they are.

  3. WriteAn access policy

    Write check(user, action, doc) that returns the status code for a request. user is None or a dict like {"name": "bo", "role": "member"}; doc is like {"owner": "ada", "public": True}; action is "read", "edit" or "delete".

    Rules, in order:

    1. reading a public doc is allowed for everyone, even without login → 200
    2. no user → 401
    3. admins may do anything → 200
    4. the owner may read, edit and delete → 200
    5. other members may read (it's not public, so no) → 403 for everything

Practise this lesson

Open the interactive lesson to edit and run these examples and solve the exercises with instant feedback. Free to start, no sign-up.

Interactive lessons open in order. If you have not done the earlier ones yet, you start at the course map.

Terms in this lesson

TermChineseDefinition
Authentication认证Who are you? Failing it: 401.
Authorization授权May you do this? Failing it: 403.
Session会话A server-side record of a login, found through a cookie.
CookieCookieA small value the browser stores and sends back to the same site.