Django Session Auth#
Docs: https://docs.djangoproject.com/en/dev/topics/auth
For this auth to work, you will need to:
Properly add and configure
django.contrib.sessions.middleware.SessionMiddlewaremiddlewareProperly add and configure
django.contrib.auth.middleware.AuthenticationMiddlewaremiddlewareSet correct auth settings, like enabling
django.contrib.authapp
Make sure to follow the default Django’s guide on setting up the regular Django auth before going further.
Requiring auth#
Note
Current user will always be accessible as self.request.user.
Read more: https://docs.djangoproject.com/en/stable/topics/auth/default/
We provide two classes to require Django session auth in your API:
DjangoSessionSyncAuthfor sync viewsDjangoSessionAsyncAuthfor async views
Example, how to use the auth class and how to get self.request.user:
1from django.contrib.auth.models import User
2
3from dmr import Controller
4from dmr.plugins.pydantic import PydanticFastSerializer
5from dmr.security import AuthenticatedHttpRequest
6from dmr.security.django_session import DjangoSessionSyncAuth
7
8
9class APIController(Controller[PydanticFastSerializer]):
10 request: AuthenticatedHttpRequest[User]
11 auth = (DjangoSessionSyncAuth(),)
12
13 def get(self) -> str:
14 # Let's test that `User` has the correct type:
15 assert self.request.user.is_authenticated
16 return 'get'
17
18 def post(self) -> str:
19 # POST will have `csrf` security requirement.
20 assert self.request.user.is_authenticated
21 return 'post'
22
OpenAPI Schema
Preview openapi.json
{
"components": {
"schemas": {
"ErrorDetail": {
"description": "Base schema for error details description.",
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
]
},
"title": "Loc",
"type": "array"
},
"msg": {
"title": "Msg",
"type": "string"
},
"type": {
"title": "Type",
"type": "string"
}
},
"required": [
"msg"
],
"title": "ErrorDetail",
"type": "object"
},
"ErrorModel": {
"description": "Default error response schema.\n\nCan be customized.\nSee :ref:`customizing-error-messages` for more details.",
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/ErrorDetail"
},
"title": "Detail",
"type": "array"
}
},
"required": [
"detail"
],
"title": "ErrorModel",
"type": "object"
}
},
"securitySchemes": {
"csrf": {
"description": "CSRF protection",
"in": "cookie",
"name": "csrftoken",
"type": "apiKey"
},
"django_session": {
"description": "Reusing standard Django auth flow for API",
"in": "cookie",
"name": "sessionid",
"type": "apiKey"
}
}
},
"info": {
"title": "Django Modern Rest",
"version": "0.1.0"
},
"openapi": "3.2.0",
"paths": {
"/api/apicontroller/": {
"get": {
"operationId": "getAPIControllerApiApicontroller",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
},
"description": "OK"
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when auth was not successful"
},
"406": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when provided `Accept` header cannot be satisfied"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when returned response does not match the response schema"
}
},
"security": [
{
"django_session": []
}
]
},
"post": {
"operationId": "postAPIControllerApiApicontroller",
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
},
"description": "Created"
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when auth was not successful"
},
"403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when CSRF check failed"
},
"406": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when provided `Accept` header cannot be satisfied"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when returned response does not match the response schema"
}
},
"security": [
{
"csrf": [],
"django_session": []
}
]
}
}
}
}
Note
Using any of these classes would also automatically enable CSRF checks
for this view. Because it is not secure to use Django
session auth without CSRF checks.
We automatically enforce CSRF checks before any other actions are taken.
Custom user models are automatically supported.
CSRF in auth#
We additionally require CSRF checks to pass on non-safe HTTP methods like
POST, PUT, DELETE, etc.
Security requirement generated for these methods will reflect that:
they will need both django_session and csrf security requirements
to be present for successful login.
Ready-to-use views#
dmr.security.django_session.concrete_views has login controllers
that only need a serializer:
DjangoSessionSyncControllerfor sync viewsDjangoSessionAsyncControllerfor async views
Pass the serializer to as_view in your urls,
there is no view code at all:
1from dmr.openapi import build_schema
2from dmr.openapi.views import OpenAPIJsonView
3from dmr.plugins.pydantic import PydanticFastSerializer
4from dmr.routing import Router, path
5from dmr.security.django_session import concrete_views
6
7router = Router(
8 'api/',
9 [
10 # You can also use `DjangoSessionAsyncController` if needed:
11 path(
12 'auth/',
13 concrete_views.DjangoSessionSyncController.as_view(
14 serializer=PydanticFastSerializer,
15 ),
16 name='session_login',
17 ),
18 ],
19)
20
21urlpatterns = [
22 router.to_urlpatterns(namespace='api'),
23 path(
24 'docs/openapi.json/',
25 OpenAPIJsonView.as_view(build_schema(router)),
26 name='openapi_json',
27 ),
28]
Run result
$ curl http://127.0.0.1:8000/api/auth/ -D - -X POST -d '{"username": "test_user", "password": "password"}' -H 'Content-Type: application/json'
HTTP/1.1 200 OK
date: Mon, 05 Oct 2026 11:16:10 GMT
server: uvicorn
Cache-Control: no-store
Content-Type: application/json
X-Frame-Options: DENY
Vary: Accept-Language, Cookie
Content-Language: en
Content-Length: 15
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin
Set-Cookie: csrftoken=uM5xDlJjm8CnybcO8mLjd07weQmDTSwe; expires=Mon, 04 Oct 2027 11:16:10 GMT; Max-Age=31449600; Path=/; SameSite=Lax
Set-Cookie: sessionid=555yw9yjtrit0h2diww8wl95n4qdg9ru; expires=Mon, 19 Oct 2026 11:16:10 GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=Lax
{"user_id":"1"}
OpenAPI Schema
Preview openapi.json
{
"components": {
"schemas": {
"DjangoSessionPayload": {
"description": "Payload for default version of a django session request body.\n\nIs also used as kwargs for :func:`django.contrib.auth.authenticate`.",
"properties": {
"password": {
"title": "Password",
"type": "string"
},
"username": {
"title": "Username",
"type": "string"
}
},
"required": [
"username",
"password"
],
"title": "DjangoSessionPayload",
"type": "object"
},
"DjangoSessionResponse": {
"description": "Default response type for django session endpoint.",
"properties": {
"user_id": {
"title": "User Id",
"type": "string"
}
},
"required": [
"user_id"
],
"title": "DjangoSessionResponse",
"type": "object"
},
"ErrorDetail": {
"description": "Base schema for error details description.",
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
]
},
"title": "Loc",
"type": "array"
},
"msg": {
"title": "Msg",
"type": "string"
},
"type": {
"title": "Type",
"type": "string"
}
},
"required": [
"msg"
],
"title": "ErrorDetail",
"type": "object"
},
"ErrorModel": {
"description": "Default error response schema.\n\nCan be customized.\nSee :ref:`customizing-error-messages` for more details.",
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/ErrorDetail"
},
"title": "Detail",
"type": "array"
}
},
"required": [
"detail"
],
"title": "ErrorModel",
"type": "object"
}
}
},
"info": {
"title": "Django Modern Rest",
"version": "0.1.0"
},
"openapi": "3.2.0",
"paths": {
"/api/auth/": {
"post": {
"operationId": "postDjangoSessionSyncControllerApiAuth",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DjangoSessionPayload"
}
}
},
"description": "Payload for default version of a django session request body.\n\nIs also used as kwargs for :func:`django.contrib.auth.authenticate`.",
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DjangoSessionResponse"
}
}
},
"description": "OK",
"headers": {
"Cache-Control": {
"description": "Credentials must not be stored in any cache.",
"required": true,
"schema": {
"type": "string"
}
},
"Set-Cookie: csrftoken": {
"description": "CSRF protection.",
"required": true,
"schema": {
"type": "string"
}
},
"Set-Cookie: sessionid": {
"required": true,
"schema": {
"type": "string"
}
}
}
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when request components cannot be parsed"
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Unauthorized"
},
"406": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when provided `Accept` header cannot be satisfied"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when returned response does not match the response schema"
}
},
"summary": "By default cookies are acquired on post."
}
}
}
}
They take a username and a password
as DjangoSessionPayload,
start a session, and return the id of the user
as DjangoSessionResponse.
as_view takes the serializer as a typed keyword argument
and passes everything else to django as usual, see
as_view().
They all set auth = None: they are the very endpoints
that check credentials, so auth from the settings
must never be required to reach them.
Tip
These should be your default for the common cases. They are final, so any custom logic belongs to the reusable controllers below, which leave the bodies and the hooks open for you.
Customizing pre-existing views#
We provide several pre-existing views to get Django session cookie. So, users won’t have to write tons of boilerplate code.
Reach for them when the bodies of Ready-to-use views do not match your API.
We provide two Reusable controllers to obtain Django session cookie:
DjangoSessionSyncControllerfor sync controllersDjangoSessionAsyncControllerfor async controllers
To use them, you will need to:
Provide actual types for serializer, request model, and response body
Redefine
convert_auth_payload()to convert your request model into the kwargs ofdjango.contrib.auth.authenticate()to authenticate your requestRedefine
make_api_response()to return the response in the format of your choice
1from typing_extensions import override
2
3from dmr.plugins.pydantic import PydanticFastSerializer
4from dmr.security.django_session.views import (
5 DjangoSessionPayload,
6 DjangoSessionResponse,
7 DjangoSessionSyncController,
8)
9
10
11class SessionSyncController(
12 DjangoSessionSyncController[
13 PydanticFastSerializer,
14 DjangoSessionPayload,
15 DjangoSessionResponse,
16 ],
17):
18 @override
19 def convert_auth_payload(
20 self,
21 payload: DjangoSessionPayload,
22 ) -> DjangoSessionPayload:
23 return payload
24
25 @override
26 def make_api_response(self) -> DjangoSessionResponse:
27 return {'user_id': str(self.request.user.pk)}
28
Run result
$ curl http://127.0.0.1:8000/api/auth/ -D - -X POST -d '{"username": "test_user", "password": "password"}' -H 'Content-Type: application/json'
HTTP/1.1 200 OK
date: Mon, 05 Oct 2026 11:16:11 GMT
server: uvicorn
Cache-Control: no-store
Content-Type: application/json
X-Frame-Options: DENY
Vary: Accept-Language, Cookie
Content-Language: en
Content-Length: 15
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin
Set-Cookie: csrftoken=sh6dKCLb6yN07ayZJqaCj3MI5R82A109; expires=Mon, 04 Oct 2027 11:16:11 GMT; Max-Age=31449600; Path=/; SameSite=Lax
Set-Cookie: sessionid=6vzyrad0d2kxiww0ai2sxh835er0883x; expires=Mon, 19 Oct 2026 11:16:11 GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=Lax
{"user_id":"1"}
OpenAPI Schema
Preview openapi.json
{
"components": {
"schemas": {
"DjangoSessionPayload": {
"description": "Payload for default version of a django session request body.\n\nIs also used as kwargs for :func:`django.contrib.auth.authenticate`.",
"properties": {
"password": {
"title": "Password",
"type": "string"
},
"username": {
"title": "Username",
"type": "string"
}
},
"required": [
"username",
"password"
],
"title": "DjangoSessionPayload",
"type": "object"
},
"DjangoSessionResponse": {
"description": "Default response type for django session endpoint.",
"properties": {
"user_id": {
"title": "User Id",
"type": "string"
}
},
"required": [
"user_id"
],
"title": "DjangoSessionResponse",
"type": "object"
},
"ErrorDetail": {
"description": "Base schema for error details description.",
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
]
},
"title": "Loc",
"type": "array"
},
"msg": {
"title": "Msg",
"type": "string"
},
"type": {
"title": "Type",
"type": "string"
}
},
"required": [
"msg"
],
"title": "ErrorDetail",
"type": "object"
},
"ErrorModel": {
"description": "Default error response schema.\n\nCan be customized.\nSee :ref:`customizing-error-messages` for more details.",
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/ErrorDetail"
},
"title": "Detail",
"type": "array"
}
},
"required": [
"detail"
],
"title": "ErrorModel",
"type": "object"
}
}
},
"info": {
"title": "Django Modern Rest",
"version": "0.1.0"
},
"openapi": "3.2.0",
"paths": {
"/api/sessionsynccontroller/": {
"post": {
"operationId": "postSessionSyncControllerApiSessionsynccontroller",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DjangoSessionPayload"
}
}
},
"description": "Payload for default version of a django session request body.\n\nIs also used as kwargs for :func:`django.contrib.auth.authenticate`.",
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DjangoSessionResponse"
}
}
},
"description": "OK",
"headers": {
"Cache-Control": {
"description": "Credentials must not be stored in any cache.",
"required": true,
"schema": {
"type": "string"
}
},
"Set-Cookie: csrftoken": {
"description": "CSRF protection.",
"required": true,
"schema": {
"type": "string"
}
},
"Set-Cookie: sessionid": {
"required": true,
"schema": {
"type": "string"
}
}
}
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when request components cannot be parsed"
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Unauthorized"
},
"406": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when provided `Accept` header cannot be satisfied"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when returned response does not match the response schema"
}
},
"summary": "By default cookies are acquired on post."
}
}
}
}
Any further customizations are also possible.
CSRF in views#
When a user logs in through these controllers, Django automatically rotates the CSRF token and includes a CSRF cookie in the login response (alongside the session cookie).
Note
When CSRF_USE_SESSIONS is True, Django stores the CSRF token
in the session instead of a cookie. In that case no csrftoken cookie
appears in the login response — the token is already embedded in the
session used for authentication.
The OpenAPI schema reflects this as well: with a CSRF cookie
the csrf security scheme is an apiKey in cookie,
with CSRF_USE_SESSIONS it is an apiKey in header
named after CSRF_HEADER_NAME, like X-Csrftoken.
The session cookie itself is described only once,
by the django_session security scheme.
See csrf_security_scheme().
API Reference#
- class dmr.security.django_session.auth.DjangoSessionSyncAuth(security_scheme_name: str = 'django_session', csrf_scheme_name: str = 'csrf')[source]#
Reuses Django’s regular session auth for the API.
This class is used for sync endpoints. CSRF is automatically enforced before any other actions.
Changed in version 0.16.0: Fixed how CSRF schema is generated.
- __call__(endpoint: Endpoint, controller: Controller[BaseSerializer]) Self | None[source]#
Does check for the existing request user.
- auth_security_scheme() SecurityScheme#
Provides a security schema definition.
- authenticate(endpoint: Endpoint, controller: Controller[BaseSerializer]) Self | None[source]#
Override this method to provide other authentication logic.
For example: checking that user is staff / superuser.
- csrf_security_scheme() SecurityScheme#
Provides the security scheme of the CSRF.
- provide_response_specs(metadata: EndpointMetadata, controller_cls: type[Controller[BaseSerializer]], existing_responses: Mapping[HTTPStatus, ResponseSpec]) list[ResponseSpec]#
Declares extra responses for failed auth and failed CSRF checks.
- security_requirements(metadata: EndpointMetadata, controller_cls: type[Controller[BaseSerializer]]) list[dict[str, list[str]]]#
Requires the auth scheme and CSRF for unsafe HTTP methods.
- security_schemes(metadata: EndpointMetadata, controller_cls: type[Controller[BaseSerializer]]) dict[str, SecurityScheme | Reference]#
Provides the auth security scheme together with the CSRF one.
- validate(controller_cls: type[Controller[BaseSerializer]], metadata: EndpointMetadata) None#
Validate auth configuration at import time.
Added in version 0.16.0.
- class dmr.security.django_session.auth.DjangoSessionAsyncAuth(security_scheme_name: str = 'django_session', csrf_scheme_name: str = 'csrf')[source]#
Reuses Django’s regular session auth for the API.
This class is used for async endpoints. CSRF is automatically enforced before any other actions.
Changed in version 0.16.0: Fixed how CSRF schema is generated.
- async __call__(endpoint: Endpoint, controller: Controller[BaseSerializer]) Self | None[source]#
Does check for the existing request user.
- auth_security_scheme() SecurityScheme#
Provides a security schema definition.
- async authenticate(endpoint: Endpoint, controller: Controller[BaseSerializer]) Self | None[source]#
Override this method to provide other authentication logic.
For example: checking that user is staff / superuser.
- csrf_security_scheme() SecurityScheme#
Provides the security scheme of the CSRF.
- provide_response_specs(metadata: EndpointMetadata, controller_cls: type[Controller[BaseSerializer]], existing_responses: Mapping[HTTPStatus, ResponseSpec]) list[ResponseSpec]#
Declares extra responses for failed auth and failed CSRF checks.
- security_requirements(metadata: EndpointMetadata, controller_cls: type[Controller[BaseSerializer]]) list[dict[str, list[str]]]#
Requires the auth scheme and CSRF for unsafe HTTP methods.
- security_schemes(metadata: EndpointMetadata, controller_cls: type[Controller[BaseSerializer]]) dict[str, SecurityScheme | Reference]#
Provides the auth security scheme together with the CSRF one.
- validate(controller_cls: type[Controller[BaseSerializer]], metadata: EndpointMetadata) None#
Validate auth configuration at import time.
Added in version 0.16.0.