Django Rest Framework JWT Auth - django-rest-framework

I have an issue posting/getting any requests after JWT login. (I am using the djangorestframework-jwt library) The user succesfully logs in, the app returns the json token but when I use this token for the next requests I get
{
"detail": "Authentication credentials were not provided."
}
settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
JWT_AUTH = {
'JWT_ALLOW_REFRESH': True,
'JWT_EXPIRATION_DELTA': datetime.timedelta(hours=1),
'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
}
Login View
def post(self, request, format=None):
if not request.data:
return Response({'Error': "Please provide username/password"}, status=status.HTTP_400_BAD_REQUEST)
username = request.data['username']
password = request.data['password']
try:
user = User.objects.get(email=username, password=password)
except User.DoesNotExist:
return Response({'Error': "Invalid username/password"}, status=status.HTTP_400_BAD_REQUEST)
if user:
payload = {
'id': user.id,
'email': user.email,
'first_name': user.first_name
}
jwt_token = jwt.encode(payload, "SECRET_KEY") # to be changed
return Response({'token': jwt_token}, status=status.HTTP_200_OK)
and then every other view contains
authentication_class = (JSONWebTokenAuthentication,)
permission_classes = (IsAuthenticated,)
I have tested both with postman and curl but I get the same error. Not sure if there is an error with the header format or if there is smth else I'm missing. Any help would be greatly appreciated!
EDIT:
I have changed my settings to
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
but now I get that
{
"detail": "Error decoding signature."
}
EDIT: I think the issue is that jwt_token = jwt.encode(payload, 'SECRET_KEY') might return a token that is not recognised...if i use the token generated by obtain_jwt_token then i can query any endpoint. Could anyone explain this?
EDIT: so I changed to jwt_token = jwt_encode_handler(payload) and the settings file contains the JWT_SECRET_KEY (when i verify the token i receive after login on jwt, it is indeed the right token with the right payload and secret) but it's still not recognised "detail": "Invalid signature."

I managed to solve my problem. When authenticating a user I was checking against a custom user table that I had created earlier, which was different from the django's auth_user table. I changed django's settings to use my custom users table and then using the token from the authentication worked for the other requests as well.
AUTH_USER_MODEL = 'main.User'

The problem is you encode the JWT by using "SECRET KEY" and decode using another key. The default secret key used by jwt is settings.SECRET_KEY. But when you create a custom jwt token you uses a string as secret "SECRET_KEY". But you use default jwt verification which automatically uses settings.SECRET_KEY.
So add 'JWT_SECRET_KEY': settings.SECRET_KEY, in to your JWT settings and use this key for encode and decode.
Ex:
jwt_token = jwt.encode(payload, settings.SECRET_KEY)
or you can override the jwt verification and create your own.

Related

How to validate & verify JWT token payload in golang

I am trying to validate my json token but i am not able to do that,
Here is my sample token
Header:
{
"alg": "HS256",
"typ": "JWT"
}
Payloads:
{
"admin": false,
"School_ID": 123,
"name": "XXXXXX",
"sub": "XXXXXXXX"
}
Singature:
Key
My problem is as soon as i am trying to manipulate JSON web token and change the value of admin 'false' to 'true', it is bypassing my API and becoming as an admin user from the normal user, to prevent that i tried using
token, err: = new(jwt.Parser).ParseWithClaims(tokenString, newClaims(), func( * jwt.Token)(interface {}, error) {
return tokenString, nil
})
but problem still there can anyone help me how to fix that issue as its critical security bug and i need to fix it.
First thing, JWT prevents the users from changing the payload because the users couldn't have key to regenerate the JWT token. If you change admin from false to true in the payload, do you regenerate the signature?
For example, you could paste the following text in jtw.io
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c. You'll see valid signature verified.
But, if you change only payload, you'll get invalid signature, like this, eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRHd3d29lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c. You also could copy it to try in jtw.io.
So when you change payload without regenerating the JWT token, you'll get invalid JWT token. When your JWT token is modified (admin: false to true) by users who don't know your key, the users basically could not get the admin permission.
Last, signature in JWT is not the key, it's just a signature to approve this JWT token is signed by your key.
It doesn't look like you're verifying the signature anywhere. You're parsing the token payload, but you don't verify the signature. When you're reading a JWT you have to verify the signature in order to check whether someone has changed the contents of the token. So to prevent exactly what you have done in your example. When you change admin claim to true then the signature will no longer match the payload and you will be able to reject such a token.

permission classes IsAuthenticated not working in DRF

I've used token authentication, and it's working fine i.e. it is authenticating a user and then the user is logged in. But in my views I've set permission classes to IsAuthenticated for one of the views, and it is not allowing to the user even if he is an authenticated user.
Below is the screenshot where it says i'm logged in (jadhav#gmail.com) :
and the very next tab to this, it says "authentication details not provided":
Can someone tell what's wrong?
ok, I'm providing details:
these are my settings:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated', )
}
This is how I authenticated:
class UserLoginAPIView(APIView):
permission_classes = [AllowAny]
serializer_class = UserLoginSerializer
def post(self, request, *args, **kwargs):
data = request.data
serializer = UserLoginSerializer(data=data)
if serializer.is_valid(raise_exception=True):
# new_data = serializer.data
if serializer.data:
user = authenticate(username=request.data['username'], password=request.data['password'])
login(request, user)
print("IsAuthenticated", user.is_authenticated)
token, _ = Token.objects.get_or_create(user=user)
return Response({'token': token.key},
status=HTTP_200_OK)
Another View where I put restrictions:
class BoardCreateAPIView(CreateAPIView):
queryset = Boards.objects.all()
serializer_class = BoardCreateSerializer
permission_classes = (IsAuthenticated,)
Just as #Reza hinted, you're missing the point of token authentication. You're trying to use the basic authentication flow instead. The flow is like this:
Client requests a token from the server using login and password
Server verifies that your credentials are correct, creates a token and returns it to the client
In subsequent requests, client adds the token to the Auth header like this:
Authorization: Token <the_client_token>
So what you should do in your login view is verify the user credentials and create a token. you shouldn't try to perform the authentication yourself. You can rename the view to obtain_token so as not to confuse its function.
Check the article #Reza linked for more info.
In django rest framework, You should provide token in your request headers. here is the sample with curl command:
curl -X POST -H "Content-Type: application/json" -H "Authorization: Token <MY_TOKEN>" http://my-api-url
Also check that in your settings.py at least you have these lines:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}
For more understanding read this doc from django rest framework

how to handle token authentication in Angular Dart?

First time dealing with a SPA. I have a back-end restful service that returns a token when a user signs in. I know I am supposed to send the token through the headers in each request so I was thinking in saving the token in a file and create a service or a class that loads the token in every component but I don't know if this is a good approach as I can't find documentation for Angular Dart about this.
I saved the Token first in localStorage as Tobe O suggested:
Future login(username, password) async {
String url = 'http://127.0.0.1:8000/auth/login/';
var response =
await _client.post(url, body: {'username': username, 'password': password});
Map mapped_response = _decoder.convert(response.body);
window.localStorage.addAll({"token": mapped_response["key"]});
}
But still I was receiving 401 responses when I tried to get user information, this was the function:
Future check_authentification () async {
String _headers_key = "Authorization";
String _headers_value = "Token "+window.localStorage["token"];
var response = await _client.get("http://127.0.0.1:8000/auth/user/", headers: {_headers_key: _headers_value});
user_data = _decoder.convert(response.body);
response_status = response.statusCode;
}
I couldn't get authorized because django-rest-auth wasn't properly configured for token authorization. The solution was to add TokenAuthentication to the default authentication classes in django settings.
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
)}

Django token authorization - customizing obtain_jwt_token by overriding ObtainAuthToken class

My goal is to override obtain_jwt_token in order to get more control over the return value of the procedure, and in the doc I found only a bizarre and sketchy info on how to do this:
Note that the default obtain_auth_token view explicitly uses JSON
requests and responses, rather than using default renderer and parser
classes in your settings. If you need a customized version of the
obtain_auth_token view, you can do so by overriding the
ObtainAuthToken view class, and using that in your url conf instead
As for now, my attempt looks like this:
urlpatterns = [
url(r'^api-token-auth/', my_customized_view),
]
class Foo(ObtainAuthToken):
def post(self):
# here goes my customized code
my_customized_view = Foo.as_view()
The odds are that my code looks quite silly, and I am just lost trying to google it. I have little experience in Djagno, so please help me with this !
I have just been going through the same journey for comprehension as I wished to return the user and also allow email or username login. The documentation is not entirely clear, but as described for auth token, you can do the same for JWT.
obtain_auth_token is to ObtainAuthToken, as obtain_jwt_token is to ObtainJSONWebToken.
This is my overwritten login method:
from rest_framework_jwt.settings import api_settings
from rest_framework_jwt.views import ObtainJSONWebToken
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
class LoginView(ObtainJSONWebToken):
def post(self, request, *args, **kwargs):
# by default attempts username / passsword combination
response = super(LoginView, self).post(request, *args, **kwargs)
# token = response.data['token'] # don't use this to prevent errors
# below will return null, but not an error, if not found :)
res = response.data
token = res.get('token')
# token ok, get user
if token:
user = jwt_decode_handler(token) # aleady json - don't serialize
else: # if none, try auth by email
req = request.data # try and find email in request
email = req.get('email')
password = req.get('password')
username = req.get('username')
if email is None or password is None:
return Response({'success': False,
'message': 'Missing or incorrect credentials',
'data': req},
status=status.HTTP_400_BAD_REQUEST)
# email exists in request, try to find user
try:
user = User.objects.get(email=email)
except:
return Response({'success': False,
'message': 'User not found',
'data': req},
status=status.HTTP_404_NOT_FOUND)
if not user.check_password(password):
return Response({'success': False,
'message': 'Incorrect password',
'data': req},
status=status.HTTP_403_FORBIDDEN)
# make token from user found by email
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
user = UserSerializer(user).data
return Response({'success': True,
'message': 'Successfully logged in',
'token': token,
'user': user},
status=status.HTTP_200_OK)
You can change the default to check by email only if you please by customising Django's auth model, but I was happy to have both options.
I started creating an api boilerplate. There is a requirements.txt file and a config.example.py file for anyone who wants to pull it down to view the rest.
https://github.com/garyburgmann/django-api-boilerplate
In views.py, file add the following code and customize as you want.
def jwt_response_payload_handler(token, user=None, request=None):
return {
'token': token,
'user': UserSerializer(user, context={'request': request}).data
}
Default is {'token': token}
Also In your settings.py file add
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER':
'api.user.views.jwt_response_payload_handler',
}
( 'api.user.views.jwt_response_payload_handler',
) is the path to your custom jwt_response_payload_handler
For more help, you can view here

Okta Authentication works but Get User by Id gives Invalid Token Provided

I have a Django app that authenticates using Okta:
headers = {
'Authorization': 'SSWS {}'.format(<okta api token>),
'Accept': 'application/json',
'Content-Type': 'application/json'
}
authentication_payload = {
'username': <username>,
'password': <password>
}
response = requests.post(
<okta auth endpoint>,
headers=headers,
data=json.dumps(authentication_payload)
)
This works successfully. From the response content I am able to get the User Id:
content = json.loads(r.content.decode('utf-8'))
okta_user_id = content['_embedded']['user']['id']
I then use the okta_user_id to create the endpoint to get the okta user by id:
okta_user_endpoint = https://<org>.okta.com/api/v1/users/<okta_user_id>
I then use the same headers from the authentication call, with the same api token, and try to get the user by id:
user_response = requests.get(
okta_user_endpoint,
headers=headers
)
But this is unsuccessful. I get a 401 error with the following content:
{
"errorCode":"E0000011",
"errorSummary":"Invalid token provided",
"errorLink":"E0000011",
"errorCauses":[]
}
Seems straight forward with an invalid token, but if the token is invalid how am I able to successfully make the authentication call? And if the token if valid for the authentication call why is it not working to get the user by id?
Okta recently changed the way that the /authn endpoint works. The /authn endpoint no longer requires an authentication token. This was done in order to support single-page applications.
It looks like your application will need to be able to fetch user information on an arbitrary user. In that case, using an Okta API token makes sense.
However, if you were making that call from a single-page application, you would want to make a request to the /users/me API endpoint.

Resources