SoFunction
Updated on 2024-10-29

Introduction to user authentication and JWT extensions in the django drf framework

Login registration is almost all websites need to do the interface , and when it comes to login , naturally involves authentication and user login status preservation , recently used DRF in an online shopping mall project , the introduction of an expansion of DRF JWT, specifically used to do the authentication and user status preservation . This extension is more secure than the traditional CSRF. First of all, let's introduce the JWT authentication mechanism!

Json web token (JWT), is an open standard based on JSON implemented for passing statements between web application environments ( (RFC 7519 ). The token is designed to be compact and secure, especially for single sign-on (SSO) scenarios in distributed sites.JWT declarations are generally used to pass authenticated user identity information between identity providers and service providers for accessing resources from resource servers, as well as to add additional declarations that are necessary for other business logic, and the token can also be used directly for The token can also be used directly for authentication, or it can be encrypted.

Token-based authentication mechanism

The token-based authentication mechanism is similar to the http protocol in that it is stateless and does not need to retain user authentication or session information on the server side. This means that applications based on token-based authentication mechanisms do not need to consider which server the user is logged in, which provides a convenient way to extend the application.

The process works like this:

  • The user requests the server with a username and password
  • The server performs authentication of the user's information
  • The server sends a token to the user through authentication
  • The client stores the token and attaches it to each request.
  • The server verifies the token value and returns the data

This token must be passed to the server on every request, it should be stored in the request header. In addition, the server should support CORS (Cross-Control Resource Sharing) policy, generally we can do this on the server side Access-Control-Allow-Origin: *.

So let's get back to JWT now.

Composition of JWT

The first part we call the header, the second part we call the payload, similar to the items carried on an airplane, and the third part is the signature.

The header of a jwt carries two pieces of information: 1, a declaration of the type, in this case jwt, and 2, a declaration of the algorithm used to encrypt it, usually HMAC SHA256 is used directly. the complete header looks like the following JSON:

{
 'typ': 'JWT',
 'alg': 'HS256'
}

The header is then base64 encrypted (the encryption is symmetrically decryptable), which forms the first part.

As eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.

The payload is the place where the valid information is stored. The name refers specifically to the cargo carried on an airplane, and the valid information consists of three parts: 1 declarations registered in the standard, 2 public declarations, and 3 private declarations. Declarations registered in the standard (recommended but not mandatory): 1 iss: the issuer of the jwt, 2 sub: the user for whom the jwt is intended, 3 aud: the party that receives the jwt, 4 exp: the expiration time of the jwt, which must be greater than the time of issuance, 5 nbf: defines the time before which the jwt is unavailable, 6 iat: the time when the jwt is issued, 7 jti: the time of issuance of the jwt, and 8 jti: the time of issuance of the jwt, and 9 jti: the time of issuance of the jwt. 7 jti: the unique identity of the jwt, mainly used as a one-time token, thus avoiding replay attacks. Public Declaration: The public declaration can add any information, usually the user's information or other necessary information for business needs. However, it is not recommended to add sensitive information, because this part can be decrypted on the client side. Private declarations: Private declarations are declarations defined by both the provider and the consumer, and are generally not recommended for storing sensitive information because base64 is symmetric decryption, which means that this part of the information can be categorized as plaintext. Define a payload.

{
 "sub": "1234567890",
 "name": "John Doe",
 "admin": true
}

It is then base64 encrypted to get the second part of the JWT.

As eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.

The third part of JWT is a visa message, this visa message consists of three parts: 1 header (after base64), 2 payload (after base64), 3 secret. this part needs base64 encrypted header and base64 encrypted payload using . This part requires the header after base64 encryption and the payload after base64 encryption using the . string, and then through the header declared in the encryption method for the salt secret combination of encryption, and then it constitutes the third part of the jwt.

Note: the secret is stored on the server side, jwt issuance generation is also on the server side, the secret is used for jwt issuance and jwt authentication, so it is your server's private key, in any scenario should not be exposed out. Once the client knows the secret, it means the client can issue jwt by itself.

First you need to install the extension pip install djangorestframework-jwt, and then configure it in django. JWT_EXPIRATION_DELTA specifies the validity period of the token.

REST_FRAMEWORK = {
 'DEFAULT_AUTHENTICATION_CLASSES': (
  'rest_framework_jwt.',
  'rest_framework.',
  'rest_framework.',
 ),
}

JWT_AUTH = {
 'JWT_EXPIRATION_DELTA': (days=1),
}

The documentation for the Django REST framework JWT extension provides methods for manually issuing JWTs

from rest_framework_jwt.settings import api_settings

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)

For registration, just introduce the above code and issue JWT. For login, the JWT extension provides a built-in view, just add it to urls for routing.

from rest_framework_jwt.views import obtain_jwt_token

urlpatterns = [
 url(r'^authorizations/$', obtain_jwt_token),
]

Although it's simple to write, it actually does a lot of things internally, so today we're going to examine in detail what the source code does internally.

When the user logs in, it will send a request to the backend in the form of a post, which will access the post method in obtain_jwt_token. In the source code, you can see obtain_jwt_token = ObtainJSONWebToken.as_view(), which is very similar to the class view that we have written, and this is a class that has already been written inside the as_view(), very similar to the class view we wrote, which is an internally written class view.

class ObtainJSONWebToken(JSONWebTokenAPIView):
 """
 API View that receives a POST with a user's username and password.

 Returns a JSON Web Token that can be used for authenticated requests.
 """
 serializer_class = JSONWebTokenSerializer

This class does not define any methods, so the corresponding post method should be written in the parent class, the following is the post method in the parent class

def post(self, request, *args, **kwargs):
  serializer = self.get_serializer(data=)

  if serializer.is_valid():
   user = ('user') or 
   token = ('token')
   response_data = jwt_response_payload_handler(token, user, request)
   response = Response(response_data)
   if api_settings.JWT_AUTH_COOKIE:
    expiration = (() +
        api_settings.JWT_EXPIRATION_DELTA)
    response.set_cookie(api_settings.JWT_AUTH_COOKIE,
         token,
         expires=expiration,
         httponly=True)
   return response

  return Response(, status=status.HTTP_400_BAD_REQUEST)

The above method returns a Response object, which is returned to the front-end after a series of operations, and the access ends.

And in the DRF framework, the corresponding validation operation is performed before the view is called. To understand the whole process, we need to explore it step by step from the source code. When the front-end initiates a request to the back-end, it will access the as_view() method of the object's view class according to the route, which will then call the dispatch() method. APIView is the parent of all view classes in DRF, so you can take a look at his dispatch method.

def dispatch(self, request, *args, **kwargs):
  """
  `.dispatch()` is pretty much the same as Django's regular dispatch,
  but with extra hooks for startup, finalize, and exception handling.
  """
   = args
   = kwargs
  request = self.initialize_request(request, *args, **kwargs)
   = request
   = self.default_response_headers # deprecate?

  try:
   (request, *args, **kwargs)

   # Get the appropriate handler method
   if () in self.http_method_names:
    handler = getattr(self, (),
         self.http_method_not_allowed)
   else:
    handler = self.http_method_not_allowed

   response = handler(request, *args, **kwargs)

  except Exception as exc:
   response = self.handle_exception(exc)

   = self.finalize_response(request, response, *args, **kwargs)
  return 

As you can see, by the time the request comes in, the self.initalize_request() method is called to process the request.

def initialize_request(self, request, *args, **kwargs):
  """
  Returns the initial request object.
  """
  parser_context = self.get_parser_context(request)

  return Request(
   request,
   parsers=self.get_parsers(),
   authenticators=self.get_authenticators(),
   negotiator=self.get_content_negotiator(),
   parser_context=parser_context
  )

The only thing we need to focus on is the sentence authenticators=self.get_authenticators(), the

def get_authenticators(self):
  """
  Instantiates and returns the list of authenticators that this view can use.
  """
  return [auth() for auth in self.authentication_classes]

Moving on to the top photo, you can see the class attribute permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES, which is what is going to what we are going to add to the django configuration with the DEFAULT_PERMISSION_CLASSES configuration, and the above method will traverse the the list we wrote to in the configuration, get the validation class inside and instantiate it and load the generated object in a new list and save it in the newly generated Request object. We then move on to the dispatch method, which also calls the () method for some initialization operations before actually calling the corresponding method in the view class.

def initial(self, request, *args, **kwargs):
  """
  Runs anything that needs to occur prior to calling the method handler.
  """
  self.format_kwarg = self.get_format_suffix(**kwargs)

  # Perform content negotiation and store the accepted info on the request
  neg = self.perform_content_negotiation(request)
  request.accepted_renderer, request.accepted_media_type = neg

  # Determine the API version, if versioning is in use.
  version, scheme = self.determine_version(request, *args, **kwargs)
  , request.versioning_scheme = version, scheme

  # Ensure that the incoming request is permitted
  self.perform_authentication(request)
  self.check_permissions(request)
  self.check_throttles(request)

We need to pay attention to the last three rows, respectively, call three methods, authentication, permission authentication and triage operations, here we only pay attention to the authentication method, self.perform_authentication(), the method within only one line of code, it looks like the user property of the request object is called, but in fact, it is not, we can to the DRF framework's Request object to find the following method.

@property
 def user(self):
  """
  Returns the user associated with the current request, as authenticated
  by the authentication classes provided to the request.
  """
  if not hasattr(self, '_user'):
   with wrap_attributeerrors():
    self._authenticate()
  return self._user

user method after property decorator decoration, you can call the method like a property, the method in the Request object there is a corresponding user will be returned directly, if the user login, the Request object does not correspond to the user, so the code will go if the judgment inside, we just need to focus on the method self._ authenticate() call can be.

def _authenticate(self):
  """
  Attempt to authenticate the request using each authentication instance
  in turn.
  """
  for authenticator in :
   try:
    user_auth_tuple = (self)
   except :
    self._not_authenticated()
    raise

   if user_auth_tuple is not None:
    self._authenticator = authenticator
    ,  = user_auth_tuple
    return

  self._not_authenticated()

you can see, the method will traverse the Request object we passed in the previous processing loaded with a list of objects of the authentication class, and call the authentication class authenticate() method, if the authentication is successful to generate the corresponding and directly return, up will be generated directly to return, if the authentication of the internal error, it will call self._not_ authenticated(), and throw an error, up to see, in the dispatch method, if the initialization method error, then capture, and call self.handle_exception() method to generate a Response object to return, will not perform the corresponding method in the view class, then call for the self._not_authenticated() method, then call for the self._not_authenticated() method, then call for the self._not_authenticated() method, then call for the self._not_authenticated() method. not_authenticated().

def _not_authenticated(self):
  """
  Set authenticator, user & authtoken representing an unauthenticated request.

  Defaults are None, AnonymousUser & None.
  """
  self._authenticator = None

  if api_settings.UNAUTHENTICATED_USER:
    = api_settings.UNAUTHENTICATED_USER()
  else:
    = None

  if api_settings.UNAUTHENTICATED_TOKEN:
    = api_settings.UNAUTHENTICATED_TOKEN()
  else:
    = None

UNAUTHENTICATED_USER is an anonymous user class in the default django configuration, and UNAUTHENTICATED_TOKEN is set to None by default. If all authentications are passed or if there is an error in one of the authentication processes, an anonymous user is generated and will be set to None.

To summarize, before the request is executed, the DRF framework will authenticate the user according to the authentication class we configured in the configuration file, if it fails to pass the authentication, an anonymous user will be generated, and if the authentication passes, the corresponding user will be generated.

This is the whole content of this article.