Django DRF: custom Permission: tokenauthentication: Why to use isAuthenticated for permission - django

I have the following
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
)
}
How does DRF get the request.user
def hello_world(request):
print(request.user)
return Response()
I am trying
curl --location --request GET "http://127.0.0.1:8000/hello-world/" \
--header "Authorization:Token ssssss28acbd550a9806c6ac9ce13f1bbc73137" \
--header 'Content-Type: application/json'
so in the output i see the request.user is printed as per the token supplied i.e eg: test
Then what is the use of using isAuthenticated
It only checks whether Authentication header is provided or not
Why cant that be checked by tokenauthentication itself

To my understanding, token authentication is not a custom permission. Authentication and permission are 2 different things.
If you check out the DRF site https://www.django-rest-framework.org/api-guide/authentication/, they say:
Authentication is the mechanism of associating an incoming request with a set of identifying credentials, such as the user the request came from, or the token that it was signed with. The permission and throttling policies can then use those credentials to determine if the request should be permitted.
So authentication is the underlying mechanism of how you authenticate users, e.g. token authentication, session authentication etc ... Permission regulates whether a request should be granted based on the identifying credentials which were retrieved from the authentication mechanism.
So in your example, the default authentication mechanism uses tokens. This applies to all your views (unless you override this explicitly in a particular view). You then send a http request to the endpoint handled by the hello_world view. In this http request you supply a token in the header. The authentication mechanism will attempt to match this token with a unique django user model instance in the database. This happens through DRF's Request class which extends the standard django HttpRequest, an instance of which is passed to the hello_world view. If you delve into the source code, you can see that user in request.user has a property decorator:
#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
so that request.user triggers the authentication mechanism in the background if self._user hasn't been previously set and returns a django user model instance. I think this is roughly how DRF gets request.user.
To answer the second part of your question, again, token authentication only refers to the mechanism, it doesn't make any checks. So the use of isAuthenticated permission is to ensure only authenticated users, who have been authenticated by token authentication, are granted a successful response. Removing isAuthenticated would allow a user to receive a successful response without providing a token.

Related

How to get and save token using Django Web service

I'm not sure I'm right on track. Please give me a hint or direction.
I set up my Web service using Django and also made mobile app with React Native using Django REST framwork. Django uses the basic session authentication, but Django REST API uses token authentication to process the request from mobile app.
I want to implement small ReactJS app into my existing Django web. At this stage, I think my small react app will need auth token to communicate with REST api for itself.
So, my idea is that when user logs in web login page, user's API token needs to be received from API and save into cookie or localStorage while normal log in process is processing in Django Web service. Because I don't want to let users log in again to run react app on my web page to get auth token.
Am I right on track? if so, how can I make it works? Please refer to my code in Django login view.py Do i need to some code in order to get API auth token and save it into client side?
def Login(request):
if not request.user.is_authenticated:
if request.method == "POST":
email = request.POST['email']
password = request.POST['password']
user = authenticate(email = email, password = password)
if user is not None:
login(request, user)
messages.add_message(request, messages.SUCCESS, request.user.nickname + ' Welcome!')
return redirect('Search')
else:
messages.add_message(request, messages.WARNING, 'Please check Email / Password again')
return redirect('login')
else:
form = LoginForm()
return render(request, 'login.html', {'form': form })
else:
return redirect('main')
You have done some useless in your login function. you can use jwt. it has some good function for supporting login. In its login function, when send username and password with post, it return token to client.
http://getblimp.github.io/django-rest-framework-jwt/
You just need set urlpattern
from rest_framework_jwt.views import obtain_jwt_token
#...
urlpatterns = [
'',
# ...
url(r'^api-token-auth/', obtain_jwt_token),
]
It return token
$ curl -X POST -d "username=admin&password=password123" http://localhost:8000/api-token-auth/
In other request, if you need authentication, use following request
$ curl -H "Authorization: JWT <your_token>" http://localhost:8000/protected-url/
They both carrying out similar tasks with few differences.
Token
DRF's builtin Token Authentication
One Token for all sessions
No time stamp on the token
DRF JWT Token Authentication
One Token per session
Expiry timestamp on each token
Database access
DRF's builtin Token Authentication
Database access to fetch the user associated with the token
Verify user's status
Authenticate the user
DRF JWT Token Authentication
Decode token (get payload)
Verify token timestamp (expiry)
Database access to fetch user associated with the id in the payload
Verify user's status
Authenticate the user
Pros
DRF's builtin Token Authentication
Allows forced-logout by replacing the token in the database (ex: password change)
DRF JWT Token Authentication
Token with an expiration time
No database hit unless the token is valid
Cons
DRF's builtin Token Authentication
Database hit on all requests
Single token for all sessions
DRF JWT Token Authentication
Unable to recall the token without tracking it in the database
Once the token is issued, anyone with the token can make requests
Specs are open to interpretations, no consensus on how to do refresh
Reference: Django : DRF Token based Authentication VS JSON Web Token
I'd like to leave my answer after I solved in my way through my long research and study. My solution is quite simple.1. set DRF session authentication enable. Adding some code in setting.py
REST_FRAMEWORK = {
# ...
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
}
2. add 'credentials: "include"' into fetch code to use already logged in session cookie for authentication.
await fetch(API_URL, {
credentials: "include"
})
this solution solved my case.

CSRF is only checked when authenticated in DRF?

TLDR; It seems that my POSTs (to DRF endpoints) are only CSRF protected, if the client has an authenticated session. This is wrong, and leaves the application option to login CSRF attacks. How can I fix this?
I'm starting to build a django rest framework API for a ReactJS frontend, and we want everything, including the authentication, to be handled via API. We are using SessionAuthentication.
If I have an authenticated session, then CSRF works entirely as expected (when auth'd the client should have a CSRF cookie set, and this needs to be paired with the csrfmiddlewaretoken in the POST data).
However, when not authenticated, no POSTs seem to be subject to CSRF checks. Including the (basic) login APIView that has been created. This leaves the site vulnerable to login CSRF exploits.
Does anyone know how to enforce CSRF checks even on unathenticated sessions? and/or how DRF seems to bypass CSRF checks for login?
Below is my rough setup ...
settings.py:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
views.py:
class Login(APIView):
permission_classes = (permissions.AllowAny,)
#method_decorator(csrf_protect) # shouldn't be needed
def post(self, request, format=None):
user = authenticate(
request,
username=request.POST['username'],
password=request.POST['password']
)
# ... perform login logic ...
def get(self, request, format=None):
"""
Client must GET the login to obtain CSRF token
"""
# Force generation of CSRF token so that it's set in the client
get_token(request)
return Response(None)
urls.py:
urlpatterns = [
url(r'^login/$', views.Login.as_view(), name='login'),
]
expected behaviour:
login_url = reverse('login')
login_details = {
'username': self.user.email,
'password': self.password,
}
client = APIClient(enforce_csrf_checks=True)
# Try to just POST to a CSRF protected view with no CSRF
response = client.post(reverse('login'), login_details)
# response status should be 403 Missing or incorrect CSRF
# GET the login API first to obtain CSRF
client.get(reverse('login'))
login_details['csrfmiddlewaretoken'] = client.cookies.get('csrftoken').value
# Now POST to the login API with the CSRF cookie and CSRF token in the POST data
response = client.post(reverse('login'), login_details)
# response status should now be 200 (and a newly rotated CSRF token delivered)
actual behaviour:
client = APIClient(enforce_csrf_checks=True)
# Try to just to a CSRF protected view with no CSRF
response = client.post(reverse('login'), login_details)
# BROKEN: response status is 200, client is now logged in
# Post to the exact same view again, still with no CSRF
response = client.post(reverse('login'), login_details)
# response status is now 403
# BROKEN: This prooves that this view is protected against CSRF, but ONLY for authenticated sessions.
Django REST Framework is disabling CSRF token requirement when using SessionAuthentication and user is not authenticated. This is by design to not mess up other authentication method that don't require CSRF authentication (because they're not based on cookies) and you should ensure by yourself that CSRF is validated on login request and it is mentioned in last paragraph of SessionAuthentication documentation. It is advised to either use non-API login process or ensure that API-based login process is fully protected.
You can check how DRFs SessionAuthentication is enforcing CSRF validation when you are logged in and base your view on that.
You can create a child class of APIView that forces CSRF.
from rest_framework import views
class ForceCRSFAPIView(views.APIView):
#classmethod
def as_view(cls, **initkwargs):
# Force enables CSRF protection. This is needed for unauthenticated API endpoints
# because DjangoRestFramework relies on SessionAuthentication for CSRF validation
view = super().as_view(**initkwargs)
view.csrf_exempt = False
return view
Then all you need to do is change your login view to descend from this
class Login(ForceCRSFAPIView)
# ...

Django-rest-auth use cookie instead of Authorization header

I want to build the SPA application using Django Rest Framework as a back-end. The application will use Token authentication.
For maximum security, I want to store the authentication token inside of httpOnly cookie, so it will not be accessible from javascript. However, because the cookie is not accessible from the javascript, I am not able to set the 'Authorization: Token ...' header.
So, my question is, can I make the DRF auth system (or Django-Rest-Knox/Django-Rest-JWT) to read the authentication token from the cookie instead of reading it from the "Authorization" header? Or the "Authorization" header is the only and correct way to authenticate in DRF?
I would override the authenticate method of TokenAuthentication, assuming the token is in auth_token cookie:
class TokenAuthSupportCookie(TokenAuthentication):
"""
Extend the TokenAuthentication class to support cookie based authentication
"""
def authenticate(self, request):
# Check if 'auth_token' is in the request cookies.
# Give precedence to 'Authorization' header.
if 'auth_token' in request.COOKIES and \
'HTTP_AUTHORIZATION' not in request.META:
return self.authenticate_credentials(
request.COOKIES.get('auth_token')
)
return super().authenticate(request)
Then set django-rest-framework to use that class in settings:
REST_FRAMEWORK = {
# other settings...
'DEFAULT_AUTHENTICATION_CLASSES': (
'<path>.TokenAuthSupportCookie',
),
}

How to include user specific access to Django REST API in this example?

My understanding of authentication via an API is that the HTTP request sent by the client needs to include credentials, whether that be just a raw username and password (probably bad practice) or a hashed password, token, etc.
Normally in my Django views, I just use:
request.user.is_authenticated():
If I want my API to be used with an iOS app, this line of code cannot be used because it relies on sessions/cookies?
I would like to edit the following function, to allow it access to a specific user:
api_view(['GET'])
#csrf_exempt
def UserInfoAPI(request):
###if HTTP header includes name and password:###
private_info = Entry.objects.filter(user=request.user)
serializer = EntrySerializer(private_info, many=True)
return Response(serializer.data)
Is there a simple way to manually check for a username/pass in the HTTP header? I don't actually plan to use this in a production environment, but for the sake of understanding, I would like to understand how to have this function verify a username/pass from the http header.
Django REST Framework tries to determine the user that sends the request looking into the Authorization HTTP header. What you should send inside this header depends on the authentication scheme you choose. For example, if you choose BasicAuthentication, your header would be:
Authorization: Basic <"user:password" encoded in base64>
or, if you choose TokenAuthentication:
Authorization: Token <your token>
I would recommend the TokenAuthentication scheme. More schemes are listed in the docs.
To make sure only authenticated users have access to that API's endpoint, use the IsAuthenticated permission. This will check your user's credentials in the request, and if they are not correct, it will raise a HTTP 401 Unauthorized error.
Your Django REST Framework view would look something like this:
from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
class UserInfo(generics.ListAPIView):
model = Entry
serializer_class = EntrySerializer
permission_classes = (IsAuthenticated,)
def get_queryset(self):
queryset = super(UserInfo, self).get_queryset()
user = self.request.user
return queryset.filter(user=user)
As for the code in your iOS app, this post may be helpful.

How do I authenticate API requests (by anonymous user) in Django rest framework?

The API requests will be sent by anonymous users. No Login/register functionality is present.
I need to authenticate the API requests, one primitive way I tried was to send an auth key in each request. This auth key, I is saved in the Angular frontend as a constant.
There must be a better and more sophisticated way, kindly help!
Django REST framework largely assumes that requests are authenticated based on a user, but they do provide support for authentication anonymous requests. While this largely breaks from the assumption that "authentication" means "verifying a (Django) user is genuine", Django REST framework does allow it to happen and just substitutes the AnonymousUser instead.
Authentication in DRF can define both the request.user (the authenticated user) and request.auth (generally the token used, if applicable) properties on the request. So for your authentication, you would be holding on to tokens you have created (in a model or somewhere else) and those would be validated instead of the user credentials, and you would just end up not setting the user.
from django.contrib.auth.models import AnonymousUser
from rest_framework import authentication
from rest_framework import exceptions
class ExampleAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
auth = authentication.get_authorization_header(request)
if not auth or auth[0].lower() != b'token':
return None
if len(auth) == 1:
msg = _('Invalid token header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid token header. Credentials string should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
try:
token = Token.objects.get(token=auth[1])
except Token.DoesNotExist:
raise exceptions.AuthenticationFailed('No such token')
return (AnonymousUser(), token)
This example assumes that you have a Token model which stores the tokens that will be authenticated. The token objects will be set to request.auth if the request was authenticated properly.
Read the rest api docs on authentication and their tutorial - they offer a solid intro to the options.