DEV Community

whchi
whchi

Posted on • Edited on

1

Protect your FastAPI document with HTTP basic authN

When working in enterprise with frontends as a backend engineer, it common to deploy a staging(test) environment to expedite development.

One of the advantages of using FastAPI is it built-in API documentation. However, we don't want to expose it in the staging environment to the public, unless you can control the networking aspect.

In this article, I'll demonstrate how to secure it using HTTP basic authentication.

Code

  1. disable doc url by env
if app_env == 'staging':
    app = FastAPI(docs_url=None, openapi_url=None, redoc_url=None)
Enter fullscreen mode Exit fullscreen mode
  1. create a middleware for http basic authN
import base64
import secrets

from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.requests import Request
from starlette.responses import Response


class ApidocBasicAuthMiddleware(BaseHTTPMiddleware):

    async def dispatch(  # type: ignore
            self, request: Request, call_next: RequestResponseEndpoint):
        if request.url.path in ['/docs', '/openapi.json', '/redoc']:
            auth_header = request.headers.get('Authorization')
            if auth_header:
                try:
                    scheme, credentials = auth_header.split()
                    if scheme.lower() == 'basic':
                        decoded = base64.b64decode(credentials).decode('ascii')
                        username, password = decoded.split(':')
                        correct_username = secrets.compare_digest(
                            username, 'YOUR_USERNAME')
                        correct_password = secrets.compare_digest(
                            password, 'YOUR_PASSWORD')
                        if correct_username and correct_password:
                            return await call_next(request)
                except Exception:
                    ...
            response = Response(content='Unauthorized', status_code=401)
            response.headers['WWW-Authenticate'] = 'Basic'
            return response
        return await call_next(request)

app.add_middleware(ApidocBasicAuthMiddleware)
Enter fullscreen mode Exit fullscreen mode

replace YOUR_USERNAME and YOUR_PASSWORD

  1. enable document paths and document html
from typing import Any, Dict

from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
from fastapi.openapi.utils import get_openapi

@app.get(
    '/docs',
    tags=['documentation'],
    include_in_schema=False,
)
async def get_swagger_documentation() -> HTMLResponse:
    return get_swagger_ui_html(openapi_url='/openapi.json', title='docs')


@app.get(
    '/openapi.json',
    tags=['documentation'],
    include_in_schema=False,
)
async def openapi() -> Dict[str, Any]:
    return get_openapi(title='FastAPI', version='0.1.0', routes=app.routes)


@app.get(
    '/redoc',
    tags=['documentation'],
    include_in_schema=False,
)
async def get_redoc() -> HTMLResponse:
    return get_redoc_html(openapi_url='/openapi.json', title='docs')
Enter fullscreen mode Exit fullscreen mode

That is, you will see the login modal when entering doc path

Image description

Put it all together

import base64
import secrets

from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.requests import Request
from starlette.responses import Response
from typing import Any, Dict
from fastapi import FastAPI
from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
from fastapi.openapi.utils import get_openapi

app = FastAPI(docs_url=None, openapi_url=None, redoc_url=None)
app.add_middleware(ApidocBasicAuthMiddleware)

@app.get(
    '/docs',
    tags=['documentation'],
    include_in_schema=False,
)
async def get_swagger_documentation() -> HTMLResponse:
    return get_swagger_ui_html(openapi_url='/openapi.json', title='docs')


@app.get(
    '/openapi.json',
    tags=['documentation'],
    include_in_schema=False,
)
async def openapi() -> Dict[str, Any]:
    return get_openapi(title='FastAPI', version='0.1.0', routes=app.routes)


@app.get(
    '/redoc',
    tags=['documentation'],
    include_in_schema=False,
)
async def get_redoc() -> HTMLResponse:
    return get_redoc_html(openapi_url='/openapi.json', title='docs')
Enter fullscreen mode Exit fullscreen mode

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay