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
- disable doc url by env ```py
if app_env == 'staging':
app = FastAPI(docs_url=None, openapi_url=None, redoc_url=None)
2. create a middleware for http basic authN
```py
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)
replace YOUR_USERNAME and YOUR_PASSWORD
- enable document paths and document html ```py
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')
That is, you will see the login modal when entering doc path
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7hspuantobkn6qn82fvm.png)
## Put it all together
```py
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')
Top comments (0)