Django Session Auth#

Docs: https://docs.djangoproject.com/en/dev/topics/auth

For this auth to work, you will need to:

  1. Properly add and configure django.contrib.sessions.middleware.SessionMiddleware middleware

  2. Properly add and configure django.contrib.auth.middleware.AuthenticationMiddleware middleware

  3. Set correct auth settings, like enabling django.contrib.auth app

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:

Example, how to use the auth class and how to get self.request.user:

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:

Pass the serializer to as_view in your urls, there is no view code at all:

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:

  1. DjangoSessionSyncController for sync controllers

  2. DjangoSessionAsyncController for async controllers

To use them, you will need to:

  1. Provide actual types for serializer, request model, and response body

  2. Redefine convert_auth_payload() to convert your request model into the kwargs of django.contrib.auth.authenticate() to authenticate your request

  3. Redefine make_api_response() to return the response in the format of your choice

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.

property www_authenticate_challenge: str | None#

This auth has no challenge to advertise, so this returns None.

A challenge asks the client for the Authorization header, while this auth reads credentials that need CSRF protection instead.

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.

property www_authenticate_challenge: str | None#

This auth has no challenge to advertise, so this returns None.

A challenge asks the client for the Authorization header, while this auth reads credentials that need CSRF protection instead.