JWT Auth

Docs: https://jwt.io

Important

To use jwt you must install 'django-modern-rest[jwt]' extra.

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 JWT 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": {
      "jwt": {
        "bearerFormat": "JWT",
        "description": "JWT token auth",
        "scheme": "Bearer",
        "type": "http"
      }
    }
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/apicontroller/": {
      "get": {
        "deprecated": false,
        "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": [
          {
            "jwt": []
          }
        ]
      }
    }
  }
}

Reusing pre-existing views

We provide several pre-existing views to get auth tokens. So, users won’t have to write tons of boilerplate code.

JWT with access and refresh tokens

We provide two Reusable controllers to obtain pairs of access and refresh tokens:

  1. ObtainTokensSyncController for sync controllers

  2. ObtainTokensAsyncController 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/ -X POST -d '{"username": "test_user", "password": "password"}' -H 'Content-Type: application/json'
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoxNzc4MjQ1MDM5LCJpYXQiOjE3NzgxNTg2MzksImp0aSI6IjEyNzgwMGZhNjRmNzQzOWU5NTU5NDFiOGZkYWY4NzIyIiwiZXh0cmFzIjp7InR5cGUiOiJhY2Nlc3MifX0._z4LjN9QR1oyiQeMHnFVdeR8Fcj1FdfhKYQXHSxe3LI","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoxNzc5MDIyNjM5LCJpYXQiOjE3NzgxNTg2MzksImp0aSI6Ijc4MTdjY2Y5MGI2ZjQ2ZDU4OGI4MGExMjQ0NWFmMTdhIiwiZXh0cmFzIjp7InR5cGUiOiJyZWZyZXNoIn19.oTv1oJIlPB0erNkXFIJI7pSiz99dlndi1sCPb5lU_i4"}

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"
      },
      "ObtainTokensPayload": {
        "description": "Payload for default version of a jwt 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": "ObtainTokensPayload",
        "type": "object"
      },
      "ObtainTokensResponse": {
        "description": "Default response type for refresh token endpoint.",
        "properties": {
          "access_token": {
            "title": "Access Token",
            "type": "string"
          },
          "refresh_token": {
            "title": "Refresh Token",
            "type": "string"
          }
        },
        "required": [
          "access_token",
          "refresh_token"
        ],
        "title": "ObtainTokensResponse",
        "type": "object"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/obtainaccessandrefreshsynccontroller/": {
      "post": {
        "deprecated": false,
        "operationId": "postObtainaccessandrefreshsynccontrollerApiObtainaccessandrefreshsynccontroller",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ObtainTokensPayload"
              }
            }
          },
          "description": "Payload for default version of a jwt 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/ObtainTokensResponse"
                }
              }
            },
            "description": "OK"
          },
          "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 tokens are acquired on post."
      }
    }
  }
}

In this example we utilize pre-defined types of request model and response body, only doing the bare minimum with no customizations.

Things that you can customize:

  • Request body format

  • Response body format

  • JWT settings

  • JWT token class to be JWToken subclass with custom logic

  • Error messages, see Customizing error messages

  • Error handling, see Error handling

  • Response status code and any other regular controller or endpoint features

Here’s an example with a lot more customizations:

Run result

$ curl http://127.0.0.1:8000/api/auth/ -X POST -d '{"email": "test_user", "password": "password"}' -H 'Content-Type: application/json'
{"auth":{"access":"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoxNzc4MjQ1MDQwLCJpYXQiOjE3NzgxNTg2NDAsImlzcyI6Im15LWF3ZXNvbWUtY29tcGFueSIsImF1ZCI6WyJkZXYiLCJxYSJdLCJqdGkiOiJlOWQ3YjBkZjg2ZDA0OWY4YTk0Yjk4YWZiMTJmYmI2MiIsImV4dHJhcyI6eyJ0eXBlIjoiYWNjZXNzIn19.lcveBSyiyJnc_QaYm-WmcJFvyJaAsBh0rQpw_u82wMSCm2fbZ3N2qhUWUGPoZlac6-CF7OHBJ7-9C_kU4-5c_w","refresh":"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoxNzc5MDIyNjQwLCJpYXQiOjE3NzgxNTg2NDAsImlzcyI6Im15LWF3ZXNvbWUtY29tcGFueSIsImF1ZCI6WyJkZXYiLCJxYSJdLCJqdGkiOiJjZGFjYWM4Nzk4OWQ0ZTg3OGExMWMxNTNmODFmMjBlNiIsImV4dHJhcyI6eyJ0eXBlIjoicmVmcmVzaCJ9fQ.DRBjkCwgqhpfO-W0WBLv54EKt59GvwlHL4IwlfjHFOHT-RzI6se7EWrO0rITol9BNczVE3sHJsgRRgsSy-pDNw"}}

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"
      },
      "_RequestModel": {
        "properties": {
          "email": {
            "title": "Email",
            "type": "string"
          },
          "password": {
            "title": "Password",
            "type": "string"
          }
        },
        "required": [
          "email",
          "password"
        ],
        "title": "_RequestModel",
        "type": "object"
      },
      "_ResponseModel": {
        "properties": {
          "auth": {
            "$ref": "#/components/schemas/_TokensModel"
          }
        },
        "required": [
          "auth"
        ],
        "title": "_ResponseModel",
        "type": "object"
      },
      "_TokensModel": {
        "properties": {
          "access": {
            "title": "Access",
            "type": "string"
          },
          "refresh": {
            "title": "Refresh",
            "type": "string"
          }
        },
        "required": [
          "access",
          "refresh"
        ],
        "title": "_TokensModel",
        "type": "object"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/obtainaccessandrefreshsynccontroller/": {
      "post": {
        "deprecated": false,
        "operationId": "postObtainaccessandrefreshsynccontrollerApiObtainaccessandrefreshsynccontroller",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/_RequestModel"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/_ResponseModel"
                }
              }
            },
            "description": "OK"
          },
          "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 tokens are acquired on post."
      }
    }
  }
}

This example also provides issuer and audience in the token, so it can be used together with accepted_issuers and accepted_audiences configurations of dmr.security.jwt.auth.JWTSyncAuth to additionally validate aud and iss JWT token claims.

We want to be sure that this class is at the same time:

  1. Easy enough to not write a lot of boilerplate code by default

  2. Customizable enough to be able to change a lot of stuff that can be affected by existing business rules

  3. Always type safe

Refreshing tokens

Once a user has a refresh token, they can use it to obtain a new pair of access and refresh tokens without re-authenticating. We provide two Reusable controllers for this:

  1. RefreshTokenSyncController for sync controllers

  2. RefreshTokenAsyncController for async controllers

To use them, you only need to:

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

  2. Redefine convert_refresh_payload() to extract the refresh token string from your request payload

  3. Redefine make_api_response() to return the new token pair in the format of your choice

The controller validates that the submitted token:

  • Is a valid, non-expired JWT signed with the configured secret

  • Has extras.type == 'refresh' (i.e. it is a refresh token, not an access token)

  • Belongs to an existing, active 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"
      },
      "ObtainTokensResponse": {
        "description": "Default response type for refresh token endpoint.",
        "properties": {
          "access_token": {
            "title": "Access Token",
            "type": "string"
          },
          "refresh_token": {
            "title": "Refresh Token",
            "type": "string"
          }
        },
        "required": [
          "access_token",
          "refresh_token"
        ],
        "title": "ObtainTokensResponse",
        "type": "object"
      },
      "RefreshTokenPayload": {
        "description": "Default request body type for the refresh token endpoint.",
        "properties": {
          "refresh_token": {
            "title": "Refresh Token",
            "type": "string"
          }
        },
        "required": [
          "refresh_token"
        ],
        "title": "RefreshTokenPayload",
        "type": "object"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/refreshsynccontroller/": {
      "post": {
        "deprecated": false,
        "operationId": "postRefreshsynccontrollerApiRefreshsynccontroller",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RefreshTokenPayload"
              }
            }
          },
          "description": "Default request body type for the refresh token endpoint.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ObtainTokensResponse"
                }
              }
            },
            "description": "OK"
          },
          "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": "Refresh tokens on POST."
      }
    }
  }
}

Blocklisting tokens

Note

Add 'dmr.security.jwt.blocklist' to the INSTALLED_APPS if you want to use tokens blocklist.

JWT tokens might be leaked / outdated / etc. There must be a way to make a valid, non-expired token blocked from auth.

To do so, we provide a default Django app to do so. We store blocked tokens in the database and provide an API to add tokens to the blocklist.

Here’s an example:

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": {
      "jwt": {
        "bearerFormat": "JWT",
        "description": "JWT token auth",
        "scheme": "Bearer",
        "type": "http"
      }
    }
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/apicontroller/": {
      "get": {
        "deprecated": false,
        "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": [
          {
            "jwt": []
          }
        ]
      }
    }
  }
}

We provide two mixin types:

API Reference

class dmr.security.jwt.token.JWToken(sub: str, exp: datetime, iat: datetime = <factory>, iss: str | None = None, aud: str | Sequence[str] | None = None, jti: str | None = None, extras: dict[str, ~typing.Any]=<factory>, leeway: dataclasses.InitVar[int] = 0)[source]

JWT Token DTO.

sub

Subject - usually a unique identifier of the user or equivalent entity.

Type:

str

exp

Expiration - datetime for token expiration.

Type:

datetime.datetime

iat

Issued at - should always be current now.

Type:

datetime.datetime

iss

Issuer - optional unique identifier for the issuer.

Type:

str | None

aud

Audience - intended audience(s).

Type:

str | collections.abc.Sequence[str] | None

jti

JWT ID - a unique identifier of the JWT between different issuers.

Type:

str | None

extras

Extra fields that were found on the JWT token.

Type:

dict[str, Any]

__post_init__(leeway: int) None[source]

Runs extra validation.

classmethod decode(encoded_token: str, secret: str, algorithm: str, *, leeway: int = 0, accepted_audiences: str | Sequence[str] | None = None, accepted_issuers: str | Sequence[str] | None = None, require_claims: Sequence[str] | None = None, verify_exp: bool = True, verify_iat: bool = True, verify_jti: bool = True, verify_nbf: bool = True, verify_sub: bool = True, strict_audience: bool = False, enforce_minimum_key_length: bool = True) Self[source]

Decode a passed in token string and return a Token instance.

Parameters:
  • encoded_token – A base64 string containing an encoded JWT.

  • secret – The secret with which the JWT is encoded.

  • algorithm – The algorithm used to encode the JWT.

  • leeway – Number of potential seconds as a clock error for expired tokens.

  • accepted_audiences – Verify the audience when decoding the token.

  • accepted_issuers – Verify the issuer when decoding the token.

  • require_claims – Verify that the given claims are present in the token.

  • verify_exp – Verify that the value of the exp (expiration) claim is in the future.

  • verify_iat – Verify that iat (issued at) claim value is an integer.

  • verify_jti – Check that jti (JWT ID) claim is a string.

  • verify_nbf – Verify that the value of the nbf (not before) claim is in the past.

  • verify_sub – Check that sub (subject) claim is a string.

  • strict_audience – Verify that the value of the aud (audience) claim is a single value, and not a list of values, and matches audience exactly. Requires the value passed to the audience to be a sequence of length 1.

  • enforce_minimum_key_length – Raise an auth error when keys are below minimum recommended length.

Returns:

A decoded Token instance.

Raises:

NotAuthenticatedError – If the token is invalid.

classmethod decode_payload(encoded_token: str, secret: str, algorithms: list[str], *, leeway: int, issuer: str | Sequence[str] | None, audience: str | Sequence[str] | None, options: Options | None) dict[str, Any][source]

Decode and verify the JWT and return its payload.

encode(secret: str | bytes, algorithm: str, headers: dict[str, Any] | None = None) str[source]

Encode the token instance into a string.

Parameters:
  • secret – The secret with which the JWT is encoded.

  • algorithm – The algorithm used to encode the JWT.

  • headers – Optional headers to include in the JWT (e.g., {“kid”: “…”}).

Returns:

An encoded token string.

Raises:

InternalServerError – If encoding fails.

class dmr.security.jwt.auth.JWTSyncAuth(*, user_id_field: str = 'pk', algorithm: str = 'HS256', security_scheme_name: str = 'jwt', auth_header: str = 'Authorization', auth_scheme: str = 'Bearer', secret: str | None = None, token_cls: type[JWToken] = <class 'dmr.security.jwt.token.JWToken'>, leeway: int = 0, accepted_audiences: str | Sequence[str] | None = None, accepted_issuers: str | Sequence[str] | None = None, require_claims: Sequence[str] | None = None, verify_expiry: bool = True, verify_issued_at: bool = True, verify_jwt_id: bool = True, verify_not_before: bool = True, verify_subject: bool = True, strict_audience: bool = False, enforce_minimum_key_length: bool = True)[source]

Sync jwt auth.

__call__(endpoint: Endpoint, controller: Controller[BaseSerializer]) Self | None[source]

Does check for the correct jwt token.

authenticate(request: HttpRequest, token: JWToken) AbstractBaseUser[source]

Run all auth pipeline.

check_auth(user: AbstractBaseUser, token: JWToken) None[source]

Run extra auth checks, raise if something is wrong.

claim_from_token(token: JWToken) str

Return claim value from the token object.

Override this method if you want to change how claim is extracted from token. For example, if you create email claim, it will be stored in .extras.

So, you would need to use: token.extras['email'].

decode_token(encoded_token: str) JWToken

Decodes token object from the encoded string.

get_token_from_request(request: HttpRequest) str | None

Gets the jwt token from the request.

By default, it is in headers. Customize this method to get the token from cookies or body.

get_user(token: JWToken) AbstractBaseUser[source]

Get application user from token.

prepare_token(request: HttpRequest) JWToken | None

Fetches JWToken instance from the auth header.

provide_response_specs(metadata: EndpointMetadata, controller_cls: type[Controller[BaseSerializer]], existing_responses: Mapping[HTTPStatus, ResponseSpec]) list[ResponseSpec]

Provides responses that can happen when user is not authed.

property security_requirement: dict[str, list[str]]

Provides a security schema usage requirement.

property security_schemes: dict[str, SecurityScheme | Reference]

Provides a security schema definition.

set_request_attrs(request: HttpRequest, user: AbstractBaseUser, token: JWToken) None[source]

Set current user as authed for this request.

split_encoded_token(header: str) str | None

Splits string like ‘Bearer token’ and returns ‘token’ part.

class dmr.security.jwt.auth.JWTAsyncAuth(*, user_id_field: str = 'pk', algorithm: str = 'HS256', security_scheme_name: str = 'jwt', auth_header: str = 'Authorization', auth_scheme: str = 'Bearer', secret: str | None = None, token_cls: type[JWToken] = <class 'dmr.security.jwt.token.JWToken'>, leeway: int = 0, accepted_audiences: str | Sequence[str] | None = None, accepted_issuers: str | Sequence[str] | None = None, require_claims: Sequence[str] | None = None, verify_expiry: bool = True, verify_issued_at: bool = True, verify_jwt_id: bool = True, verify_not_before: bool = True, verify_subject: bool = True, strict_audience: bool = False, enforce_minimum_key_length: bool = True)[source]

Async jwt auth.

async __call__(endpoint: Endpoint, controller: Controller[BaseSerializer]) Self | None[source]

Does check for the correct jwt token.

async authenticate(request: HttpRequest, token: JWToken) AbstractBaseUser[source]

Run all auth pipeline.

async check_auth(user: AbstractBaseUser, token: JWToken) None[source]

Run extra auth checks, raise if something is wrong.

claim_from_token(token: JWToken) str

Return claim value from the token object.

Override this method if you want to change how claim is extracted from token. For example, if you create email claim, it will be stored in .extras.

So, you would need to use: token.extras['email'].

decode_token(encoded_token: str) JWToken

Decodes token object from the encoded string.

get_token_from_request(request: HttpRequest) str | None

Gets the jwt token from the request.

By default, it is in headers. Customize this method to get the token from cookies or body.

async get_user(token: JWToken) AbstractBaseUser[source]

Get application user from token.

prepare_token(request: HttpRequest) JWToken | None

Fetches JWToken instance from the auth header.

provide_response_specs(metadata: EndpointMetadata, controller_cls: type[Controller[BaseSerializer]], existing_responses: Mapping[HTTPStatus, ResponseSpec]) list[ResponseSpec]

Provides responses that can happen when user is not authed.

property security_requirement: dict[str, list[str]]

Provides a security schema usage requirement.

property security_schemes: dict[str, SecurityScheme | Reference]

Provides a security schema definition.

async set_request_attrs(request: HttpRequest, user: AbstractBaseUser, token: JWToken) None[source]

Set current user as authed for this request.

split_encoded_token(header: str) str | None

Splits string like ‘Bearer token’ and returns ‘token’ part.

dmr.security.jwt.auth.request_jwt(request: HttpRequest, *, strict: Literal[True]) JWToken[source]
dmr.security.jwt.auth.request_jwt(request: HttpRequest, *, strict: bool = False) JWToken | None

Returns a JWToken from request, if it was authed with it.

When strict is passed and request has no jwt token, we raise AttributeError.

Pre-defined views to fetch JWT tokens

class dmr.security.jwt.views.ObtainTokensSyncController(**kwargs)[source]

Sync controller to get access and refresh tokens.

jwt_audiences

String or sequence of string of audiences for JWT token.

Type:

ClassVar[str | collections.abc.Sequence[str] | None]

jwt_issuer

String of who issued this JWT token.

Type:

ClassVar[str | None]

jwt_algorithm

Default algorithm to use for token signing.

Type:

ClassVar[str]

jwt_expiration

Default access token expiration timedelta.

Type:

ClassVar[datetime.timedelta]

jwt_refresh_expiration

Default refresh token expiration timedelta.

Type:

ClassVar[datetime.timedelta]

jwt_secret

Alternative token secret for signing. By default uses secret.SECRET_KEY

Type:

ClassVar[str | None]

jwt_token_cls

Possible custom JWT token class.

Type:

ClassVar[type[dmr.security.jwt.token.JWToken]]

See also

https://pyjwt.readthedocs.io/en/stable for all the JWT terms and options explanation.

abstractmethod convert_auth_payload(payload: _ObtainTokensT) ObtainTokensPayload[source]

Convert your custom payload to kwargs that django supports.

See django.contrib.auth.authenticate() docs on which kwargs it supports.

Basically it needs username and password strings.

create_jwt_token(*, expiration: datetime | None = None, token_type: Literal['access', 'refresh'] | None = None, subject: str | None = None, issuer: str | None = None, audiences: str | Sequence[str] | None = None, jwt_id: str | None = None, secret: str | None = None, algorithm: str | None = None, token_headers: dict[str, Any] | None = None) str

Create correct jwt token of a given expiration and token_type.

login(parsed_body: _ObtainTokensT) _TokensResponseT[source]

Perform the sync login routine for user.

abstractmethod make_api_response() _TokensResponseT[source]

Abstract method to create a response payload.

make_jwt_id() str | None

Create unique token’s jwt id.

post(parsed_body: ~typing.Annotated[~dmr.security.jwt.views._ObtainTokensT, <dmr.components.BodyComponent object at 0x70cd63556de0>]) _TokensResponseT[source]

By default tokens are acquired on post.

class dmr.security.jwt.views.ObtainTokensAsyncController(**kwargs)[source]

Async controller to get access and refresh tokens.

jwt_audiences

String or sequence of string of audiences for JWT token.

Type:

ClassVar[str | collections.abc.Sequence[str] | None]

jwt_issuer

String of who issued this JWT token.

Type:

ClassVar[str | None]

jwt_algorithm

Default algorithm to use for token signing.

Type:

ClassVar[str]

jwt_expiration

Default token expiration timedelta.

Type:

ClassVar[datetime.timedelta]

jwt_refresh_expiration

Default refresh token expiration timedelta.

Type:

ClassVar[datetime.timedelta]

jwt_secret

Alternative token secret for signing. By default uses secret.SECRET_KEY

Type:

ClassVar[str | None]

jwt_token_cls

Possible custom JWT token class.

Type:

ClassVar[type[dmr.security.jwt.token.JWToken]]

See also

https://pyjwt.readthedocs.io/en/stable for all the JWT terms and options explanation.

abstractmethod async convert_auth_payload(payload: _ObtainTokensT) ObtainTokensPayload[source]

Convert your custom payload to kwargs that django supports.

See django.contrib.auth.authenticate() docs on which kwargs it supports.

Basically it needs username and password strings.

create_jwt_token(*, expiration: datetime | None = None, token_type: Literal['access', 'refresh'] | None = None, subject: str | None = None, issuer: str | None = None, audiences: str | Sequence[str] | None = None, jwt_id: str | None = None, secret: str | None = None, algorithm: str | None = None, token_headers: dict[str, Any] | None = None) str

Create correct jwt token of a given expiration and token_type.

async login(parsed_body: _ObtainTokensT) _TokensResponseT[source]

Perform the async login routine for user.

abstractmethod async make_api_response() _TokensResponseT[source]

Abstract method to create a response payload.

make_jwt_id() str | None

Create unique token’s jwt id.

async post(parsed_body: ~typing.Annotated[~dmr.security.jwt.views._ObtainTokensT, <dmr.components.BodyComponent object at 0x70cd63556de0>]) _TokensResponseT[source]

By default tokens are acquired on post.

class dmr.security.jwt.views.ObtainTokensPayload[source]

Bases: TypedDict

Payload for default version of a jwt request body.

Is also used as kwargs for django.contrib.auth.authenticate().

class dmr.security.jwt.views.ObtainTokensResponse[source]

Bases: TypedDict

Default response type for refresh token endpoint.

class dmr.security.jwt.views.RefreshTokenSyncController(**kwargs)[source]

Sync controller to refresh access and refresh tokens.

Accepts a refresh token in the request body, validates it, loads the user, and calls make_api_response() to build the response.

jwt_user_id_field

User model field matched against token.sub. Defaults to 'pk'.

Type:

ClassVar[str]

jwt_audiences

String or sequence of string of audiences for JWT token.

Type:

ClassVar[str | collections.abc.Sequence[str] | None]

jwt_issuer

String of who issued this JWT token.

Type:

ClassVar[str | None]

jwt_algorithm

Default algorithm to use for token signing.

Type:

ClassVar[str]

jwt_expiration

Default token expiration timedelta.

Type:

ClassVar[datetime.timedelta]

jwt_refresh_expiration

Default refresh token expiration timedelta.

Type:

ClassVar[datetime.timedelta]

jwt_secret

Alternative token secret for signing. By default uses secret.SECRET_KEY

Type:

ClassVar[str | None]

jwt_token_cls

Possible custom JWT token class.

Type:

ClassVar[type[dmr.security.jwt.token.JWToken]]

check_auth(user: Any) None[source]

Run extra auth checks, raise if something is wrong.

abstractmethod convert_refresh_payload(payload: _RefreshTokensT) str[source]

Extract the refresh token string from the request payload.

create_jwt_token(*, expiration: datetime | None = None, token_type: Literal['access', 'refresh'] | None = None, subject: str | None = None, issuer: str | None = None, audiences: str | Sequence[str] | None = None, jwt_id: str | None = None, secret: str | None = None, algorithm: str | None = None, token_headers: dict[str, Any] | None = None) str

Create correct jwt token of a given expiration and token_type.

abstractmethod make_api_response() _TokensResponseT[source]

Build the token pair response after a successful refresh.

make_jwt_id() str | None

Create unique token’s jwt id.

post(parsed_body: ~typing.Annotated[~dmr.security.jwt.views._RefreshTokensT, <dmr.components.BodyComponent object at 0x70cd63556de0>]) _TokensResponseT[source]

Refresh tokens on POST.

refresh(parsed_body: _RefreshTokensT) _TokensResponseT[source]

Validate the refresh token, load user, and return new tokens.

class dmr.security.jwt.views.RefreshTokenAsyncController(**kwargs)[source]

Async controller to refresh access and refresh tokens.

Accepts a refresh token in the request body, validates it, loads the user, and calls make_api_response() to build the response.

jwt_user_id_field

User model field matched against token.sub. Defaults to 'pk'.

Type:

ClassVar[str]

jwt_audiences

String or sequence of string of audiences for JWT token.

Type:

ClassVar[str | collections.abc.Sequence[str] | None]

jwt_issuer

String of who issued this JWT token.

Type:

ClassVar[str | None]

jwt_algorithm

Default algorithm to use for token signing.

Type:

ClassVar[str]

jwt_expiration

Default token expiration timedelta.

Type:

ClassVar[datetime.timedelta]

jwt_refresh_expiration

Default refresh token expiration timedelta.

Type:

ClassVar[datetime.timedelta]

jwt_secret

Alternative token secret for signing. By default uses secret.SECRET_KEY

Type:

ClassVar[str | None]

jwt_token_cls

Possible custom JWT token class.

Type:

ClassVar[type[dmr.security.jwt.token.JWToken]]

async check_auth(user: Any) None[source]

Run extra auth checks, raise if something is wrong.

abstractmethod async convert_refresh_payload(payload: _RefreshTokensT) str[source]

Extract the refresh token string from the request payload.

create_jwt_token(*, expiration: datetime | None = None, token_type: Literal['access', 'refresh'] | None = None, subject: str | None = None, issuer: str | None = None, audiences: str | Sequence[str] | None = None, jwt_id: str | None = None, secret: str | None = None, algorithm: str | None = None, token_headers: dict[str, Any] | None = None) str

Create correct jwt token of a given expiration and token_type.

abstractmethod async make_api_response() _TokensResponseT[source]

Build the token pair response after a successful refresh.

make_jwt_id() str | None

Create unique token’s jwt id.

async post(parsed_body: ~typing.Annotated[~dmr.security.jwt.views._RefreshTokensT, <dmr.components.BodyComponent object at 0x70cd63556de0>]) _TokensResponseT[source]

Refresh tokens on POST.

async refresh(parsed_body: _RefreshTokensT) _TokensResponseT[source]

Validate the refresh token, load user, and return new tokens.

class dmr.security.jwt.views.RefreshTokenPayload[source]

Bases: TypedDict

Default request body type for the refresh token endpoint.

Blocklist app

class dmr.security.jwt.blocklist.auth.JWTokenBlocklistSyncMixin[source]

Sync mixin for working with tokens blocklist.

blocklist(token: JWToken) tuple[BlocklistedJWToken, bool][source]

Add token to the blocklist.

check_auth(user: AbstractBaseUser, token: JWToken) None[source]

Check if the token is in the black list, if so raise the error.

class dmr.security.jwt.blocklist.auth.JWTokenBlocklistAsyncMixin[source]

Async mixin for working with tokens blocklist.

async blocklist(token: JWToken) tuple[BlocklistedJWToken, bool][source]

Add token to the blocklist.

async check_auth(user: AbstractBaseUser, token: JWToken) None[source]

Check if the token is in the black list, if so raise the error.