Reputation: 37
I have a Django application with external database, which means that for every user request I'm sending SQL queries to external DB server. No local DB (like sqllite or such) exists. Also, JWT should be used to authenticate users.
To do so, I've overwritten ObtainJSONWebToken
view:
class ObtainJWT(ObtainJSONWebToken):
def post(self, request, *args, **kwargs):
username = request.data.get('username')
password = request.data.get('password')
# verify that user with given credentials exist in db
resp = requests.post(settings.SERVER_HOST+"/auth/",
json={"username":username, "password":password})
if resp.status_code == status.HTTP_401_UNAUTHORIZED:
return Response({'error':'Invalid credentials'},
status=status.HTTP_401_UNAUTHORIZED)
# create token
payload = jwtutils.jwt_payload_handler(username, password, api_settings.JWT_EXPIRATION_DELTA)
token = jwt_encode_handler(payload)
return Response({'token': token},
status=status.HTTP_200_OK)
And jwt_payload_handler
in jwtutils
:
def jwt_payload_handler(username, password, delta):
# custom payload handler
payload = {
'username': username,
'password': password,
'exp': datetime.utcnow() + delta
}
return payload
Now I can successfully obtain token without using any User
objects. But when obtained token is used (a user tries to access protected routes with the token), {"detail":"Invalid signature."}
is returned. I think it's because DRF's JSONWebTokenAuthentication
class that I'm using has authenticate_credentials
method that checks if a user with given credentials exists in local DB ( https://github.com/GetBlimp/django-rest-framework-jwt/blob/master/rest_framework_jwt/authentication.py#L59 ), hence the error. So I decided to create custom authentication class.
There is what I wrote:
class JSONWebTokenAuthentication(BaseAuthentication):
"""
Token based authentication using the JSON Web Token standard.
"""
def get_jwt_value(self, request):
auth = get_authorization_header(request).split()
auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()
if not auth:
if api_settings.JWT_AUTH_COOKIE:
return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)
return None
if smart_text(auth[0].lower()) != auth_header_prefix:
return None
if len(auth) == 1:
msg = _('Invalid Authorization header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid Authorization header. Credentials string '
'should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
return auth[1]
def authenticate(self, request):
"""
Returns a two-tuple of `User` and token if a valid signature has been
supplied using JWT-based authentication. Otherwise returns `None`.
"""
jwt_value = self.get_jwt_value(request)
if jwt_value is None:
return None
try:
payload = jwt_decode_handler(jwt_value)
except jwt.ExpiredSignature:
msg = ('Signature has expired.')
raise exceptions.AuthenticationFailed(msg)
except jwt.DecodeError:
msg = _('Error decoding signature.')
raise exceptions.AuthenticationFailed(msg)
except jwt.InvalidTokenError:
raise exceptions.AuthenticationFailed()
return (None, payload)
However, that doesn't work. I'm returning None
instead of a valid User
object. Somewhere later in Django's authentication process that value is read and tested if it's is_authenticated()
returns True
. Returning None
would obviously result in {"detail":"You do not have permission to perform this action."}
.
I'm relatively new to Django and JWT, what is the best way to rewrite the authentication class so I'd be able not to have any DjangoUser
s saved locally and not break anything in Django authentication process? Or maybe I need to rewrite some permissions classes? Thanks in advance.
Upvotes: 1
Views: 2797
Reputation: 1376
Not sure if you solved this but did you add your custom authentication class in rest framework settings? something like this:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'myapp.location.JSONWebTokenAuthentication' #location of your custom authentication class.
),
}
Upvotes: 2