liveBook · Manning

403 Forbidden

As explained in Secure APIs

The HTTP 403 status code indicates that the server understood a request but refuses to authorize it, typically because the requester lacks sufficient permissions.

HTTP 403 Forbidden means the server understood a request but refuses it because the requester lacks sufficient permission. A validly authenticated user may receive 403 when accessing another user’s registration or an administrator-only endpoint. This differs from 401 Unauthorized, which indicates missing or invalid authentication credentials. Framework behavior can blur the distinction: an identified FastAPI bug caused some unauthenticated requests to return 403 instead of 401. section 12.4 section 4.8

Use 403 as an authorization decision, then verify that the decision matches the request context:

  • For a protected resource, validate the access token and confirm that its claims identify a permitted user. section 4.8
  • For role-restricted functionality, check the permissions claim for the required role, such as admin, and return 403 when it is absent. section 4.8
  • Test unauthorized access to course lessons, another student’s registration, and administrative routes; assert the expected response code. section 12.4 section 4.8

Authorization test

A test creates a token for one student, requests another student’s registration, and expects 403. A separate role-based test requests /admin/students with a non-admin token and also expects 403. These checks expose broken object-level or function-level authorization. section 12.4 section 12.4

Listing 12.2 Running a broken authentication test
# file: ch12/tests.py
from sqlalchemy import select
from starlette_testclient import TestClient

from db_session import session_maker
from models import Course
from server import server


client = TestClient(server)     #1


def test_broken_authentication():
    with session_maker() as session:     #2
        course = session.scalar(select(Course))     #3
        lessons = course.lessons
    response = client.get(
        f"/courses/{course.id}/lessons/{lessons[0].id}"
    )     #4
    assert response.status_code == 403    #5

Inspect the complete pytest example for creating test data, calling a lesson endpoint without authentication, and asserting its response status. section 12.4

How Secure APIs covers it

How deep it goes
Defines the status distinction, demonstrates permission checks, and applies it in pytest security tests; it defers deeper RBAC treatment.
Examples
Uses course lessons, another student’s registration, and administrative endpoints as authorization-test targets.
What it assumes
Assumes HTTP requests, access tokens, and endpoint testing, while clarifying the 401-versus-403 distinction.
What you can do after
Diagnose authorization flaws by writing pytest requests that assert 403 for unauthorized resources and role-restricted operations.
How it connects
Connects 403 with authentication, authorization, BOLA, BFLA, RBAC, access tokens, and HTTP status codes.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Secure APIs. The section links point to the parts of the book it draws on.