Skip to content

Add AuthStaticFiles for auth on static file serving#1

Open
yashwant86 wants to merge 3 commits into
masterfrom
pr-15295
Open

Add AuthStaticFiles for auth on static file serving#1
yashwant86 wants to merge 3 commits into
masterfrom
pr-15295

Conversation

@yashwant86
Copy link
Copy Markdown

@yashwant86 yashwant86 commented Apr 7, 2026

Mirror of fastapi#15295


Summary by MergeMonkey

  • Paper Trail:
    • Added tutorial example for authenticated static file serving with bearer tokens
  • Enhancements:
    • Authenticated static file serving via AuthStaticFiles with pluggable auth and custom error handling

Faisalsaificode and others added 3 commits April 4, 2026 01:04
Adds a new AuthStaticFiles class that extends StaticFiles with an `auth`
parameter, allowing users to protect static files behind authentication.
This addresses a long-standing feature request (issue fastapi#858) where mounted
static files bypass FastAPI's dependency injection and serve files without
any access control.

Closes fastapi#858

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rf docs

- Changed default error response from JSON to plain text (browser-friendly)
- Added optional `on_error` callback for custom error responses (redirect
  to login page, HTML error pages, etc.)
- Added performance note about keeping auth checks lightweight
- Added tests for redirect and HTML custom error responses

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@mergemonkeyhq
Copy link
Copy Markdown

mergemonkeyhq Bot commented Apr 14, 2026

Walkthrough

A developer mounts AuthStaticFiles on a FastAPI app with an async auth callback. On every HTTP request to that mount, the auth callback is invoked; if it raises an HTTPException, the response is either a plain-text error or a custom response from on_error. If auth succeeds, the request proceeds to Starlette's normal static file serving.

Changes

Files Summary
AuthStaticFiles Implementation
fastapi/staticfiles.py
Introduces AuthStaticFiles class extending Starlette's StaticFiles with an auth callback invoked before every request, catching HTTPException to produce error responses or delegating to an optional on_error handler.
Tutorial Example
docs_src/static_files/tutorial002_auth_py310.py
Provides a minimal example demonstrating bearer-token authentication on mounted static files using AuthStaticFiles.
Test Suite for AuthStaticFiles
tests/test_auth_static_files.py
Comprehensive tests covering unauthenticated denial, valid token access, 404 with auth, cookie-based auth, custom error headers, redirect-based on_error, and HTML error page on_error.

Sequence Diagram

sequenceDiagram
    participant Client
    participant AuthStatic as AuthStaticFiles
    participant AuthFn as auth callback
    participant ErrorHandler as on_error callback
    participant StarletteStatic as StaticFiles (super)

    Client->>AuthStatic: HTTP request (scope, receive, send)
    AuthStatic->>AuthFn: await auth(request)
    alt Auth raises HTTPException
        AuthFn-->>AuthStatic: raises HTTPException
        alt on_error provided
            AuthStatic->>ErrorHandler: await on_error(request, exc)
            ErrorHandler-->>AuthStatic: Response
        else no on_error
            AuthStatic->>AuthStatic: PlainTextResponse(exc.detail)
        end
        AuthStatic->>Client: error response
    else Auth raises other exception
        AuthFn-->>AuthStatic: raises Exception
        AuthStatic->>Client: re-raise (500)
    else Auth succeeds
        AuthFn-->>AuthStatic: returns None
        AuthStatic->>StarletteStatic: await super().__call__(scope, receive, send)
        StarletteStatic->>Client: serve file or 404
    end
Loading

Dig Deeper With Commands

  • /review <file-path> <function-optional>
  • /chat <file-path> "<question>"
  • /roast <file-path>

Runs only when explicitly triggered.

Comment thread fastapi/staticfiles.py
except Exception as exc:
from fastapi.exceptions import HTTPException

if isinstance(exc, HTTPException):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isinstance only catches fastapi.HTTPException, misses starlette.HTTPException

The except block imports HTTPException from fastapi.exceptions and checks isinstance(exc, HTTPException). Since fastapi.HTTPException is a subclass of starlette.HTTPException, a plain starlette.HTTPException raised by the auth callback won't match — it gets re-raised unhandled instead of going through on_error. This matters because FastAPI's own security modules (APIKeyHeader, OpenIdConnect, etc.) import and raise starlette.exceptions.HTTPException directly. Consider importing from starlette.exceptions instead, or checking for both.

Import from starlette instead: from starlette.exceptions import HTTPException — this will catch both starlette and fastapi HTTPExceptions since fastapi's is a subclass.

@mergemonkeyhq
Copy link
Copy Markdown

mergemonkeyhq Bot commented Apr 14, 2026

Actionable Comments Posted: 1

🧹 Nitpick comments (1)
Deferred import of HTTPException inside except block on every auth failure - fastapi/staticfiles.py (94, 95)
The `from fastapi.exceptions import HTTPException` runs inside the `except` block, meaning Python re-executes the import statement on every auth failure. While Python caches modules so this isn't catastrophic, it's unconventional — moving it to the module level would be cleaner and marginally faster.

Move `from fastapi.exceptions import HTTPException` to the top of the file with the other imports.
🧾 Coverage Summary
✔️ Covered (3 files)
- docs_src/static_files/tutorial002_auth_py310.py
- fastapi/staticfiles.py
- tests/test_auth_static_files.py

Comment thread fastapi/staticfiles.py
html: bool = False,
check_dir: bool = True,
follow_symlink: bool = False,
auth: Callable[[Request], Awaitable[Any]],
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sync auth callables will crash at runtime despite no enforcement

The auth parameter is typed as Callable[[Request], Awaitable[Any]] but Python doesn't enforce this at runtime. If a user passes a sync function (returning None or a value), await self.auth(request) at line 93 will raise TypeError: object ... can't be used in 'await' expression. Consider wrapping with asyncio.iscoroutinefunction() check or using run_in_threadpool for sync callables, similar to how FastAPI handles sync dependencies.

Either detect sync callables and wrap them with run_in_threadpool, or add a runtime check in __init__ that raises a clear error if the callable isn't async.

@mergemonkeyhq
Copy link
Copy Markdown

mergemonkeyhq Bot commented Apr 14, 2026

Actionable Comments Posted: 1

♻️ Duplicate comments (1)
isinstance check won't catch Starlette's HTTPException from auth callbacks - fastapi/staticfiles.py (94, 97)
The isinstance check at line 97 imports FastAPI's `HTTPException` (which is a subclass of Starlette's). If a user's auth callback raises `starlette.exceptions.HTTPException` directly — which is common in FastAPI apps — it won't match this check and will be re-raised as an unhandled exception (500 error) instead of being converted to a proper error response. Consider checking against `StarletteHTTPException` instead.

Import StarletteHTTPException and use it in the isinstance check: `from starlette.exceptions import HTTPException as StarletteHTTPException` then `isinstance(exc, StarletteHTTPException)`. This catches both Starlette and FastAPI HTTPExceptions.
🧹 Nitpick comments (1)
on_error type annotation says `Any` but always receives HTTPException - fastapi/staticfiles.py (77)
The `on_error` parameter is typed as `Callable[[Request, Any], Awaitable[Response]]` but the docstring at line 48-49 correctly says it receives an `HTTPException`. The `Any` type annotation is misleading — users won't get IDE autocompletion for HTTPException attributes like `status_code` and `detail`.

Change the type annotation to `Callable[[Request, HTTPException], Awaitable[Response]]` to match the documented and actual behavior.
🧾 Coverage Summary
✔️ Covered (3 files)
- docs_src/static_files/tutorial002_auth_py310.py
- fastapi/staticfiles.py
- tests/test_auth_static_files.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants