Authentication
Intro
Django Ninja provides several tools to help you deal with authentication and authorization easily, rapidly, in a standard way, and without having to study and learn all the security specifications.
The core concept is that when you describe an API operation, you can define an authentication object.
from typing import Annotated
from hattori import NinjaAPI, Response
from hattori.security import django_auth
api = NinjaAPI()
@api.get("/pets", auth=django_auth)
def pets(request) -> Annotated[Response[str], 200]:
return Response(200, f"Authenticated user {request.auth}")
In this example, the client will only be able to call the pets method if it uses Django session authentication (the default is cookie based), otherwise an HTTP-401 error will be returned.
If you need to authorize only a superuser, you can use from hattori.security import django_auth_superuser instead.
Automatic OpenAPI schema
Here's an example where the client, in order to authenticate, needs to pass a header:
Authorization: Bearer supersecret
from typing import Annotated
from hattori import Response, Schema
from hattori.security import HttpBearer
class TokenResponse(Schema):
token: str
class AuthBearer(HttpBearer):
def authenticate(self, request, token):
if token == "supersecret":
return token
@api.get("/bearer", auth=AuthBearer())
def bearer(request) -> Annotated[Response[TokenResponse], 200]:
return Response(200, {"token": request.auth})
Now go to the docs at http://localhost:8000/api/docs.

Now, when you click the Authorize button, you will get a prompt to input your authentication token.

When you do test calls, the Authorization header will be passed for every request.
Global authentication
In case you need to secure all methods of your API, you can pass the auth argument to the NinjaAPI constructor:
from hattori import NinjaAPI, Form
from hattori.security import HttpBearer
class GlobalAuth(HttpBearer):
def authenticate(self, request, token):
if token == "supersecret":
return token
api = NinjaAPI(auth=GlobalAuth())
# @api.get(...)
# def ...
# @api.post(...)
# def ...
And, if you need to overrule some of those methods, you can do that on the operation level again by passing the auth argument. In this example, authentication will be disabled for the /token operation:
from typing import Annotated
from hattori import NinjaAPI, Form, Response, Schema
from hattori.security import HttpBearer
class GlobalAuth(HttpBearer):
def authenticate(self, request, token):
if token == "supersecret":
return token
class TokenResponse(Schema):
token: str
api = NinjaAPI(auth=GlobalAuth())
# @api.get(...)
# def ...
# @api.post(...)
# def ...
@api.post("/token", auth=None) # < overriding global auth
def get_token(
request, username: str = Form(...), password: str = Form(...)
) -> Annotated[Response[TokenResponse], 200]:
if username == "admin" and password == "giraffethinnknslong":
return Response(200, {"token": "supersecret"})
Available auth options
Custom function
The "auth=" argument accepts any Callable object. Authentication is treated as successful whenever the callable returns any value other than None. That return value will be assigned to request.auth.
Use None to indicate that authentication failed or that the next authenticator in a list should be tried. Falsy values such as 0, False, or "" are still considered successful authentication results and will be stored on request.auth.
from typing import Annotated
from hattori import Response
def ip_whitelist(request):
if request.META["REMOTE_ADDR"] == "8.8.8.8":
return "8.8.8.8"
@api.get("/ipwhitelist", auth=ip_whitelist)
def ipwhitelist(request) -> Annotated[Response[str], 200]:
return Response(200, f"Authenticated client, IP = {request.auth}")
API Key
Some API's use API keys for authorization. An API key is a token that a client provides when making API calls to identify itself. The key can be sent in the query string:
GET /something?api_key=abcdef12345
or as a request header:
GET /something HTTP/1.1
X-API-Key: abcdef12345
or as a cookie:
GET /something HTTP/1.1
Cookie: X-API-KEY=abcdef12345
Django Ninja comes with built-in classes to help you handle these cases.
in Query
from typing import Annotated
from hattori import Response
from hattori.security import APIKeyQuery
from someapp.models import Client
class ApiKey(APIKeyQuery):
param_name = "api_key"
def authenticate(self, request, key):
try:
return Client.objects.get(key=key)
except Client.DoesNotExist:
pass
api_key = ApiKey()
@api.get("/apikey", auth=api_key, url_name="apikey_query")
def apikey(request) -> Annotated[Response[str], 200]:
assert isinstance(request.auth, Client)
return Response(200, f"Hello {request.auth}")
In this example we take a token from GET['api_key'] and find a Client in the database that corresponds to this key. The Client instance will be set to the request.auth attribute.
Note: param_name is the name of the GET parameter that will be checked for. If not set, the default of "key" will be used.
in Header
from typing import Annotated
from hattori import Response
from hattori.security import APIKeyHeader
class ApiKey(APIKeyHeader):
param_name = "X-API-Key"
def authenticate(self, request, key):
if key == "supersecret":
return key
header_key = ApiKey()
@api.get("/headerkey", auth=header_key, url_name="apikey_header")
def apikey(request) -> Annotated[Response[str], 200]:
return Response(200, f"Token = {request.auth}")
in Cookie
from typing import Annotated
from hattori import Response
from hattori.security import APIKeyCookie
class CookieKey(APIKeyCookie):
def authenticate(self, request, key):
if key == "supersecret":
return key
cookie_key = CookieKey()
@api.get("/cookiekey", auth=cookie_key, url_name="apikey_cookie")
def apikey(request) -> Annotated[Response[str], 200]:
return Response(200, f"Token = {request.auth}")
Django Session Authentication
Django Ninja provides built-in session authentication classes that leverage Django's existing session framework:
SessionAuth
Uses Django's default session authentication - authenticates any logged-in user:
from hattori.security import SessionAuth
@api.get("/protected", auth=SessionAuth())
def protected_view(request):
return {"user": request.auth.username}
SessionAuthSuperUser
Authenticates only users with superuser privileges:
from hattori.security import SessionAuthSuperUser
@api.get("/admin-only", auth=SessionAuthSuperUser())
def admin_view(request):
return {"message": "Hello superuser!"}
SessionAuthIsStaff
Authenticates users who are either superusers or staff members:
from hattori.security import SessionAuthIsStaff
@api.get("/staff-area", auth=SessionAuthIsStaff())
def staff_view(request):
return {"message": "Hello staff member!"}
These authentication classes automatically use Django's SESSION_COOKIE_NAME setting and check the user's authentication status through the standard Django session framework.
HTTP Bearer
from typing import Annotated
from hattori import Response, Schema
from hattori.security import HttpBearer
class TokenResponse(Schema):
token: str
class AuthBearer(HttpBearer):
def authenticate(self, request, token):
if token == "supersecret":
return token
@api.get("/bearer", auth=AuthBearer())
def bearer(request) -> Annotated[Response[TokenResponse], 200]:
return Response(200, {"token": request.auth})
HTTP Basic Auth
from typing import Annotated
from hattori import Response, Schema
from hattori.security import HttpBasicAuth
class AuthUser(Schema):
httpuser: str
class BasicAuth(HttpBasicAuth):
def authenticate(self, request, username, password):
if username == "admin" and password == "secret":
return username
@api.get("/basic", auth=BasicAuth())
def basic(request) -> Annotated[Response[AuthUser], 200]:
return Response(200, {"httpuser": request.auth})
Multiple authenticators
The auth argument also allows you to pass multiple authenticators:
from typing import Annotated
from hattori import Response
from hattori.security import APIKeyQuery, APIKeyHeader
class AuthCheck:
def authenticate(self, request, key):
if key == "supersecret":
return key
class QueryKey(AuthCheck, APIKeyQuery):
pass
class HeaderKey(AuthCheck, APIKeyHeader):
pass
@api.get("/multiple", auth=[QueryKey(), HeaderKey()])
def multiple(request) -> Annotated[Response[str], 200]:
return Response(200, f"Token = {request.auth}")
In this case Django Ninja will first check the API key GET, and if not set or invalid will check the header key.
If both are invalid, it will raise an authentication error to the response.
Router authentication
Use auth argument on Router to apply authenticator to all operations declared in it:
api.add_router("/events/", events_router, auth=BasicAuth())
or using router constructor
router = Router(auth=BasicAuth())
This overrides any API level authentication. To allow router operations to not use the API-level authentication by default, you can explicitly set the router's auth=None.
Error responses in OpenAPI docs
When an operation uses authentication, a 401 Unauthorized response is automatically added to the OpenAPI schema using the default AuthErrorResponse model ({"detail": "string"}).
You can customize the error responses for your auth class by setting the openapi_responses class attribute:
import pydantic
from hattori.security import HttpBearer
class MyErrorResponse(pydantic.BaseModel):
detail: str
code: str
class ForbiddenResponse(pydantic.BaseModel):
detail: str
class MyAuth(HttpBearer):
openapi_responses = {401: MyErrorResponse, 403: ForbiddenResponse}
def authenticate(self, request, token):
...
This will document both 401 and 403 responses on every operation that uses MyAuth.
To disable automatic error responses for an auth class, set openapi_responses to an empty dict:
class MyAuth(HttpBearer):
openapi_responses = {}
def authenticate(self, request, token):
...
Note
When multiple auth classes on the same operation define different models for the same status code, or when an operation's response also defines that status code, all schemas are merged using oneOf in the OpenAPI spec.
Custom exceptions
Raising an exception that has an exception handler will return the response from that handler in the same way an operation would:
from typing import Annotated
from hattori import NinjaAPI, Response, Schema
from hattori.security import HttpBearer
class TokenResponse(Schema):
token: str
api = NinjaAPI()
class InvalidToken(Exception):
pass
@api.exception_handler(InvalidToken)
def on_invalid_token(request, exc):
return api.create_response(
request, {"detail": "Invalid token supplied"}, status=401
)
class AuthBearer(HttpBearer):
def authenticate(self, request, token):
if token == "supersecret":
return token
raise InvalidToken
@api.get("/bearer", auth=AuthBearer())
def bearer(request) -> Annotated[Response[TokenResponse], 200]:
return Response(200, {"token": request.auth})
Async authentication
Django Ninja has basic support for asynchronous authentication. While the default authentication classes are not async-compatible, you can still define your custom asynchronous authentication callables and pass them in using auth.
async def async_auth(request):
...
@api.get("/pets", auth=async_auth)
def pets(request):
...
See Handling errors for more information.