DEV Community

loading...

JWT Authentication in Django

ninahwang profile image Nina Hwang ใƒป3 min read

1. Authentication & Authorization

Authentication

Authentication is the process of verifying the identification of a person or a device. One of the authentication processes is login process. Once you sign up to a website, your information(id, password, name, email, etc) is stored in its database. After then, you donโ€™t need to create an account to provide your information. Rather, you just give your user id and password to verify your identity and the website will automatically know it is you who is accessing.

Authorization

Authorization is a security mechanism used to determine user privileges or access levels. On many community websites, only the one who uploaded the post and admins can delete it. When someone else tries to delete a post, the website should throw an error(but in many cases, they canโ€™t even see the delete button). So for each request, users need to prove that they have permission.

2. JWT

What Is JWT?

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object(link). You can use JWT to authenticate and authorize requests.
JWT consists of three concatenated Base64url-encoded strings - header, payload, and signature - and they are separated by a dots(.). Header contains metadata about the type of token and cryptographic algorithms. Signature is used to verify the tokenโ€™s trustworthy. And payload contains all the necessary data for authentication & authorization.
Alt Text

Storing JWT

Session or Local Storage

When a user logs in, the server creates JWT and sends it to the client-side. Then the client-side stores it to the Session Storage or Local Storage. Each time the client-side sends request which needs authentication or authorization to the server-side, the JWT is sent on the Authorization Header.

Vulnerable to XSS(Cross Site Scripting) attack

Session and Local Storage are accessible through JavaScript. A malicious third party can inject its JS into your website so that it can make requests to the APIs.

Cookie

The server stores the JWT in Cookie and verifies the user with the JWT stored in Cookie.

Vulnerable to CSRF(Cross Site Request Forgery) attack

Cookies are vulnerable to CSRF because they are sent with each request. Therefore a malicious third party can easily craft unintended requests.

JWT in Django

# my_settings.py
# NEVER EVER COMMIT THIS FILE TO GIT

SECRET = {
    'SECRET_KEY': 'abcde1234',
    'JWT_ALGORITHM': 'HS256'
}
Enter fullscreen mode Exit fullscreen mode
# settings.py

import my_settings

SECRET_KEY = my_settings.SECRET['SECRET_KEY']
JWT_ALGORITHM = my_settings.SECRET['JWT_ALGORITHM']
Enter fullscreen mode Exit fullscreen mode
# user/views.py

import json
from datetime import datetime, timdelta
from django.conf import settings
from django.http import JsonResponse
from django.views import View

import bcrypt
import jwt
from .models import User
from token_utils import user_token

class UserSignInView(View):
    def post(self, request):
        try:
            data = json.loads(request.body)
            username = data['username']
            pw_input = data['password']
            user = User.objects.filter(username=username).first()

            if user is None:
                return JsonResponse({"message": "INVALID_USERNAME"}, status=401)
            if bcrypt.checkpw(pw_input.encode('utf-8'),
                              user.password.encode('utf-8')):
                key = settings.SECRET_KEY
                algorithm = settings.JWT_ALGORITHM
                token = jwt.encode(
                    {
                        'iss': 'me',
                        'id': user.id,
                        'exp': datetime.utcnow() + timedelta(days=14)
                    }, key, algorithm=algorithm).decode('utf-8')

                response = JsonResponse(
                    {
                        'message': 'SUCCESS'
                    }, status=200
                )

                # when using Local/Session Storage instead of Cookie, just send token in JsonResponse
                if data.get('remember_me') is not None:
                    max_age = 14*24*60*60 # 14 days
                    expires = datetime.strftime(
                        datetime.utcnow() + timedelta(seconds=max_age),
                        "%Y-%m-%d %H:%M:%S"
                    )
                    response.set_cookie(
                        'token',
                        token,
                        max_age=max_age,
                        expires=expires,
                        httponly=True
                    )
                    return response

                response.set_cookie(
                    'token',
                    token,
                    max_age=None,
                    expires=None,
                    httponly=True
                )
                return response

            return JsonResponse({"message": "WRONG_PASSWORD"}, status=401)

        except KeyError as e:
            return JsonResponse({'message': f'KEY_ERROR: {e}'}, status=400)
        except ValueError as e:
            return JsonResponse({'message': f'VALUE_ERROR: {e}'}, status=400)

Enter fullscreen mode Exit fullscreen mode
# token_utils.py

import json
from django.conf import settings
from django.http import JsonResponse

import jwt
from user.models import User

def user_token(func):
    def wrapper(self, request, *args, **kwargs):
        try:
            token = request.COOKIES.get('token')
            # token = request.headers.get('token')

            key = settings.SECRET_KEY
            algorithm = settings.JWT_ALGORITHM

            if token is None:
                return JsonResponse({"message": "INVALID_TOKEN"}, status=401)

            decode = jwt.decode(token, key, algorithm=algorithm)
            request.user = User.objects.get(id=decode['id'])

        except jwt.ExpiredSignatureError:
            return JsonResponse({"message": "EXPIRED_TOKEN"}, status=400)
        return func(self, request, *args, **kwargs)
    return wrapper
Enter fullscreen mode Exit fullscreen mode

Discussion (0)

pic
Editor guide