'"Expected a string value" on login using Django REST and pyjwt

So, I'm trying to build simple register and login functionalities into my API. I can register just fine, but when I insert the user details on the login API view page, it gives me the "Expected a string value" error. I think this is probably because one of the credentials being passed somehow doesn't get stringified? I'm really not sure and I can't find much about it. Here's my code!

views.py

from django.shortcuts import render
from rest_framework.generics import GenericAPIView
from .serializers import UserSerializer, LoginSerializer
from rest_framework import status
from rest_framework.response import Response
from django.conf import settings
from django.contrib import auth
import jwt

class RegisterView(GenericAPIView):
serializer_class = UserSerializer
    def post(self, request):
    serializer = UserSerializer(data=request.data)
    if serializer.is_valid():
        serializer.save()
        return Response(serializer.data, status=status.HTTP_201_CREATED)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class LoginView(GenericAPIView):
serializer_class = LoginSerializer

    def post(self, request):
    data = request.data
    username = data.get('username', '')
    password = data.get('password', '')
    user = auth.authenticate(username=username, password=password)

    if user:
        auth_token = jwt.encode({'username': user.username}, settings.JWT_SECRET_KEY, algorithm="HS256")
        serializer = UserSerializer(user)
        data = {"user": serializer.data, "token": auth_token}
        print(auth_token)
        return Response(data, status=status.HTTP_200_OK)

 ### SEND RES
    return Response({'detail':'Invalid Credentials'}, status=status.HTTP_401_UNAUTHORIZED)

serializers.py

from rest_framework import serializers
from django.contrib.auth.models import User

class UserSerializer(serializers.ModelSerializer):
    password = serializers.CharField(max_length=65, min_length=8, write_only=True)
    email = serializers.EmailField(max_length=220)
    username = serializers.CharField(max_length=50)

    class Meta:
        model = User
        fields = ['username', 'email', 'password']

    def validate(self, attrs):
        if User.objects.filter(email = attrs['email']).exists():
        raise serializer.ValidationError({'email', ('email is already in use')})
        return super().validate(attrs)

    def create(self, validated_data):
            return User.objects.create_user(**validated_data)

class LoginSerializer(serializers.ModelSerializer):
        password = serializers.CharField(max_length=65, min_length=8, write_only=True)
        username = serializers.CharField(max_length=220)

    class Meta:
        model = User
        fields = ['username', 'password']

backends.py

import jwt
from django.contrib.auth.models import User
from rest_framework import authentication, exceptions
from django.conf import settings

class JWTAuth(authentication.BaseAuthentication):
    def authenticate(self, request):
    auth_data = authentication.get_authorization_header(request)
    print(auth_data)

    if not auth_data:
        return None

    prefix, token = auth_data.decode('utf-8').split('')

    try:
        payload = jwt.decode(token, settings.JWT_SECRET_KEY, algorithms='HS256')
        user = User.objects.get(username = payload['username'])
        return (user, token)

    except jwt.DecodeError as identifier:
        raise exceptions.AuthenticationFailed('your token is invalid.')

    except jwt.DecodeError as identifier:
        raise exceptions.AuthenticationFailed('your token is expired.')

    return super().authenticate(request

settings.py

#JWT
JWT_SECRET_KEY = os.environ.get('JWT_SECRET_KEY')


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'api',
    'rest_framework'
]

REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'api.backends.JWTAuth',
        )
    }


Solution 1:[1]

This issue happens because os.env os.environ.get is return None

I too faced the same issue, make sure your .env file looks like this

export JWT_SECRET_KEY='your secret key here'

and .env file should be next to or in same level to settings.py

please refer to images below [![enter image description here][1]][1]

and if you want some more fun to play with multiple environment variable check this resources

https://www.youtube.com/watch?v=ecshCQU6X2U

https://pypi.org/project/python-dotenv/

Solution 2:[2]

On your LoginView, the auth_token cannot be encoded because you have not defined the payload parameter as string. You also need to define your secret key. Change your code to look like this:

if user:
        key='JWT_SECRET_KEY'
        auth_token = jwt.encode ({'user':"payload"}, key, algorithm="HS256")
        serializer = UserSerializer(user)
        data={'user':serializer.data, 'token':auth_token}
        return Response(data, status=status.HTTP_200_OK)

This should work.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Drice