Djoser user activation email POST example - python

I am using the Django rest framework and Djoser for Authentication and User Registration.
When a new user registers, Djoser sends an activation email with a link that does a GET request. In order to activate, I need to extract the uid and token from the activation URL and make a POST request for Djoser to be able to activate the user.
My environment is Python 3 and Django 1.11, Djoser 1.0.1.
What I would like to do is to handle the get request in Django, extract the uid and token, and then make a POST request. I have extracted the uid and token and would like to make a POST (within this GET request).
I do not know how to make this POST request in the background.
My URL is like this:
http://127.0.0.1:8000/auth/users/activate/MQ/4qu-584cc6772dd62a3757ee
When I click on this in an email it does a GET request.
I handle this in a Django view.
The view needs to make a POST request like this:
http://127.0.0.1:8000/auth/users/activate/
data= [(‘uid’=‘MQ’), (‘token’=‘4qu-584cc6772dd62a3757ee’),]
My view to handle GET is:
from rest_framework.views import APIView
from rest_framework.response import Response
import os.path, urllib
class UserActivationView(APIView):
def get (self, request):
urlpathrelative=request.get_full_path()
ABSOLUTE_ROOT= request.build_absolute_uri('/')[:-1].strip("/")
spliturl=os.path.split(urlpathrelative)
relpath=os.path.split(spliturl[0])
uid=spliturl[0]
uid=os.path.split(uid)[1]
token=spliturl[1]
postpath=ABSOLUTE_ROOT+relpath[0]+'/'
post_data = [('uid', uid), ('token', token),]
result = urllib.request.urlopen(postpath, urllib.parse.urlencode(post_data).encode("utf-8"))
content = result.read()
return Response(content)

views.py
from rest_framework.views import APIView
from rest_framework.response import Response
import requests
class UserActivationView(APIView):
def get (self, request, uid, token):
protocol = 'https://' if request.is_secure() else 'http://'
web_url = protocol + request.get_host()
post_url = web_url + "/auth/users/activate/"
post_data = {'uid': uid, 'token': token}
result = requests.post(post_url, data = post_data)
content = result.text
return Response(content)
urls.py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^auth/users/activate/(?P<uid>[\w-]+)/(?P<token>[\w-]+)/$', UserActivationView.as_view()),
]

Related

How can I pass in url arguments to an APIRequestFactory put request?

I have been trying for hours but cannot figure out how to pass a url argument through an APIRequestFactory put request. I have tried it through Postman when running my server and the url variable is passed just fine, but when I run it in my tests it stops working.
What I mean is that when I send a Postman PUT request to '/litter/1/' it will successfully take in the 1 as the variable litterId since my url is setup like this
path('litter/', include('apps.litter.urls')),
and
path('<int:litterId>/', LitterView.as_view(), name='litter-with-id')
But when I try and send an APIRequestFactory put request to that same url, for some reason the 1 will not go through as the litterId anymore.
Some relevant pieces of code...
My top level url.py
from rest_framework.authtoken import views
from apps.litter.views import LitterView
urlpatterns = [
path('admin/', admin.site.urls),
path('auth/', include('apps.my_auth.urls')),
path('litter/', include('apps.litter.urls')),
]
This is my app specific urls.py
from .views import LitterView
urlpatterns = [
path('', LitterView.as_view(), name='standard-litter'),
path('<int:litterId>/', LitterView.as_view(), name='litter-with-id'),
]
Here is my views.py
import json
from django.contrib.auth.models import User
from django.db import IntegrityError
from django.views.decorators.csrf import csrf_exempt
from rest_framework import authentication, permissions
from rest_framework.parsers import JSONParser
from rest_framework.permissions import IsAuthenticated
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
from rest_framework.views import APIView
from django.db import models
from .models import Litter
from .serializers import LitterSerializer
##csrf_exempt
class LitterView(APIView):
"""
View for litter related requests
* Requres token auth
"""
permission_classes = (IsAuthenticated,)
authentication_classes = [authentication.TokenAuthentication]
renderer_classes = [JSONRenderer]
def put(self, request, litterId=0):
"""
Updates an old litter
"""
try:
litterModel = Litter.objects.get(user=request.user, id=litterId)
except Litter.DoesNotExist:
returnData = {'status': 'fail',
'error': 'Could not find object with that id.'}
return Response(returnData)
serializer_class = LitterSerializer
serialized = LitterSerializer(litterModel, data=request.data)
if serialized.is_valid():
litterModel = serialized.save()
returnData = {'status': 'okay',
'litter': [serialized.data]}
return Response(returnData)
else:
return Response(serialized.errors, status=400)
And here is the relevant test.
def test_easy_successful_put_type(self):
"""
Testing a simple put
"""
user = UserFactory()
amount = 40
amountChange = 20
litter = LitterFactory(user=user, amount=amount)
data = {'typeOfLitter': litter.typeOfLitter,
'amount': litter.amount + amountChange,
'timeCollected': litter.timeCollected}
url = '/litter/' + str(litter.id) + '/'
request = self.factory.put(url, data, format='json')
force_authenticate(request, user=user)
view = LitterView.as_view()
response = view(request).render()
responseData = json.loads(response.content)
No matter what I do, I cannot get the int:litterId to get passed in, the put function always has the default value of 0. Any help would be greatly appreciated.
Your problem is here:
response = view(request).render()
You are manually passing the request to the view, also not passing the kwarg litterId, instead use APIClient and make a put request to the url. First import the required modules:
from django.urls import reverse
from rest_framework.test import APIClient
then:
user = UserFactory()
amount = 40
amountChange = 20
litter = LitterFactory(user=user, amount=amount)
data = {
'typeOfLitter': litter.typeOfLitter,
'amount': litter.amount + amountChange,
'timeCollected': litter.timeCollected
}
url = reverse('litter-with-id', kwargs={'litterId': litter.id})
client = APIClient()
client.force_authenticate(user=user)
response = client.put(url, data, format='json')

Unauthorized response to POST request in Django Rest Framework with JWT Token

I am building a REST API with Django Rest Framework. I currently have an issue where some of my endpoints return HTTP 401 Unauthorized, whereas the vast majority of my endpoints return correct responses. For authentication I am using JWT tokens with djangorestframework-simplejwt.
I've configured Django to use token auth with djangorestframework-simplejwt.
# rest framework config settings
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
# 'rest_framework.permissions.AllowAny',
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
The vast majority of my endpoints return valid data when I pass a valid access token in the request. If I do not send a valid token, I receive a HTTP 403.
On the other hand, I have some custom API views which return a HTTP 401 regardless of whether I pass a valid token or not.
I have included the code to one of my problematic views below.
class CheckDifferentialView(generics.GenericAPIView):
permission_classes = [IsAuthenticated]
authentication_classes = [TokenAuthentication]
serializer_class = QuizDifferentialSerializer
def post(self, request, *args, **kwargs):
"""
A function to check a quiz question and to update a user's record of questions answered
"""
print(request.META)
if 'answer' not in request.data:
return JsonResponse({'Error': 'answer not found in request'}, status=status.HTTP_400_BAD_REQUEST)
answer = get_object_or_404(Differential, pk=request.data['answer'])
serializer = QuizDifferentialSerializer(answer)
if answer.is_correct:
pass
# record correct results
else:
pass
# record failed result
return Response(serializer.data, status=status.HTTP_200_OK)
And here is my script which I am using to test my API
import requests
import json
POST_LOGIN_URL = 'http://localhost:8000/api/v1/token/'
POST_URL= 'http://localhost:8000/api/v1/check_differential'
REQUEST_URL = 'http://localhost:8000/api/v1/users'
with requests.Session() as session:
post = session.post(POST_LOGIN_URL, json={"username": "j", "monkey": "aphextwin21"})
token = json.loads(post.text)['access']
headers = {'Authorization': 'Bearer ' + token}
r = session.post(POST_URL, headers=headers, json={"answer": "2"})
# r = session.get(REQUEST_URL, headers=headers)
print(token)
print(r.text, r.status_code)
The desired behaviour is that if I send a POST request with a valid token to this endpoint that is would authorise me and carry on with its day. If no Authorization header with a valid access token is given, then I expect it to deny the request.
Update
Enthusiastic Martin kindly point out that
authentication_classes = [TokenAuthentication]
Was overriding the defaults found in my settings file. I was not aware that as far as Django is concerned TokenAuthentication and JWTAuthentication are treated differently. Now I know.
After removing the authentication_classess = [TokenAuthentication] from my views, the views are working as they should.
The view's authentication class is explicitly set to TokenAuthentication only. It wont work with JWT token.
authentication_classes = [TokenAuthentication]
You either remove this to let the default classes handle it or change it to accept JWTAuthentication.

Token Authentication Implementation in Django Rest Framework

I'm developing an API backend using the Django Rest Framework. I had initially developed it using Session Authentication, unaware that it could not be used for sending to a mobile application. I encountered trouble with respect to CSRF protection while trying user login in Postman.
Now, since I have to shift to Token-based Authentication to make it work, how do I go about doing so? I would like to how to implement it quickly. I have browsed through tutorials and answers on stackoverflow, but am unable to implement this in practise
Also, is Token Authentication the most suitable method for authentication? Should I use the default provided DRF module or JWT or some other implementation? Could I use token authentication simply for user login, and session authentication for the other 3 APIs?
class UserLogin(APIView):
queryset = User.objects.all()
serializer_class = UserSerializer
def post(self, request, format='json'):
username = request.POST.get('username')
email = request.POST.get('email')
password = request.POST.get('password')
user = EmailBackend.authenticate(self,username = email, password = password)
if user:
id = user.id
return Response(id, status=status.HTTP_201_CREATED)
else:
return Response("Failure", status=HTTP_400_BAD_REQUEST)
class UserRegistration(APIView):
queryset = User.objects.all()
serializer_class = UserSerializer
def post(self, request, format='json'):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
if user:
return Response('Success', status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
class RecommendationQuestions(generics.ListCreateAPIView):
def post(self, request, format = 'json'):
"""Save the post data when logging in."""
uid = request.data['user_id']
resp_list = MovieSerializer.provide_movie_choices(uid)
return Response(resp_list, status=status.HTTP_400_BAD_REQUEST)
class RecommendationGenerator(generics.ListCreateAPIView):
queryset = Ratings.objects.all()#.filter(id__in=(1,2))
serializer_class= RatingsSerializer#(queryset,many=True)
def post(self, request, format='json'):
many = isinstance(request.data, list)
serializer = RatingsSerializer(data = request.data, many = many)
x = 0
if serializer.is_valid():
uid = [d['userId'] for d in serializer.data]
resp_list = RatingsSerializer.generate_recommendations(self, uid[0])
return Response(resp_list, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors,status=status.HTTP_400_BAD_REQUEST)
This is the views.py for the APIs.
Token authentication setup
You enable TokenAuthentication by including
'rest_framework.authtoken'
in INSTALLED_APPS settings (documentation).
You must run migrate after that. After you run migration, you need to create tokens for your users. Here is an example code that does that:
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
users = User.objects.all()
for user in users:
token, created = Token.objects.get_or_create(user=user)
You run this only once. Also, you need to create token for every new user. You can automate that with post_save signal:
from django.contrib.auth.models import User
from django.dispatch import receiver
from django.db.models.signals import post_save
from rest_framework.authtoken.models import Token
#receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)
Additionally, you have to add configure authentication classes by including
'rest_framework.authentication.TokenAuthentication'
in your settings 'DEFAULT_AUTHENTICATION_CLASSES' (documentation)
Last thing you need to do is add url for token authentication to your urls.py:
from rest_framework.authtoken import views as drf_views
urlpatterns += [
path('api-token-auth/', drf_views.obtain_auth_token)
]
Session-based authentication is meant for logging to your API with your browser. Token-based Authentication is stateless, which means that the server doesn't store any state about the client session on the server. Read more about the difference here. If you login via Token-based authentication, you won't have a session and won't be able to access API in any other way but via token.
Authentication example
Below is a sample code for token authentication in Python with the use of requests library.
# Authentication
import requests
r = requests.post(<add your token auth url here>, data={'username': 'my_username', 'password': 'my_password'})
if r.status_code == 200:
response = r.json()
token = response['token']
print(token)
Token must be used for every other API request. It's sent via headers.
# Consume API
import requests
headers = {'Authorization': 'Token {}'.format(<your token here>)}
# Request method is either GET, POST, PUT, PATCH or DELETE
r = requests.request(method=<request method>, url=<url to api>, headers=headers)
# or you can also use
# requests.get(url=<url to api>, headers=headers) or
# requests.post(url=<url to api>, headers=headers, data=<your data>) etc.
I would recommend you to use JWT, it much more safety than what rest_framework.authtoken is provided Such as a pair of token/refresh token to set for your main token small expiration time. That reduces the chance for the token to be stolen or corrupted. Also inside your JWT token, you can store payload which is very useful in many cases.
There is a very good library for DRF which implements all aspects of using JWT with DRF and it's pretty flexible to adapt to your purposes.
http://getblimp.github.io/django-rest-framework-jwt/
Could I use token authentication simply for user login, and session authentication for the other 3 APIs?
Yes, you definitely can. Each instance of APIView has property 'authentication_classes' and you can set SessionAuthentication specifically for APIs you want.
For example:
class RecommendationQuestions(generics.ListCreateAPIView):
authentication_classes = (SessionAuthentication, )
def post(self, request, format = 'json'):
"""Save the post data when logging in."""
uid = request.data['user_id']
resp_list = MovieSerializer.provide_movie_choices(uid)
return Response(resp_list, status=status.HTTP_400_BAD_REQUEST)
Or you can use both
class RecommendationQuestions(generics.ListCreateAPIView):
authentication_classes = (SessionAuthentication, JSONWebTokenAuthentication)
def post(self, request, format = 'json'):
"""Save the post data when logging in."""
uid = request.data['user_id']
resp_list = MovieSerializer.provide_movie_choices(uid)
return Response(resp_list, status=status.HTTP_400_BAD_REQUEST)

Run django api from postman: CSRF verification failed

I'm trying to run an api using postman. My application is developed in django 1.11.6 using python 3.5.
My app is installed on an ubuntu server. I have no login mechanism to create a csrf token.
These are the steps that I follow:
Click on "import" tab on the upper left side.
Select the Raw Text option and paste my cURL command.
Hit import and I have the command in your Postman builder
Press send button.
My curl command is:
curl -i -H 'Accept: application/json; indent=4' -X POST https://127.0.0.1/users/:register/ -d "id=111&firstname=zinonas&yearofbirth=2007&lastname=Antoniou&othernames="
The error I get is Forbidden (403) - CSRF verification failed. Request aborted.
When I run the curl command via cygwin, it's working properly.
This is the view function that I'm using:
class ApiUserRegister(APIView):
permission_classes = ()
serializer_class = RegisterUserSerializer
def post(self, request):
serializer = RegisterUserSerializer(data=request.data)
# Check format and unique constraint
serializer.is_valid(raise_exception=True)
data = serializer.data
if User.objects.filter(id=data['id']).exists():
user = User.objects.get(id=data['id'])
is_new = "false"
resp_status = status.HTTP_200_OK
else:
user = User.objects.create(id=data['id'],
firstname=data['firstname'],
yearofbirth=data['yearofbirth'],
lastname=data['lastname'],
othernames=data['othernames'])
user.save()
is_new = "true"
resp_status = status.HTTP_201_CREATED
resp = {"user": serializer.get_serialized(user),
"isnew": is_new}
return Response(resp, status=resp_status)
In settings.py I have:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
)
}
Try this.
from django.views.decorators.csrf import csrf_exempt
class ApiUserRegister(APIView):
permission_classes = ()
serializer_class = RegisterUserSerializer
#csrf_exempt
def post(self, request):
serializer = RegisterUserSerializer(data=request.data)
To make AJAX requests, you need to include CSRF token in the HTTP header, as described in the Django documentation.
1st option
2nd option
simple just make sure to put as_view()
urlpatterns = [
path('sign_up', views.SignUp.as_view()),
]
update your class to be like this
from braces.views import CsrfExemptMixin
class your_class(CsrfExemptMixin, ......yours_here)
def post(...):
[....]
this will tell django to allow requests without csrf
Django sets csrftoken cookie on login. After logging in, we can see the csrf token from cookies in the Postman. (see image)
CSRFtoken from cookies
We can grab this token and set it in headers manually.
But this token has to be manually changed when it expires. This process becomes tedious to do it on an expiration basis.
Instead, we can use Postman scripting feature to extract the token from the cookie and set it to an environment variable. In Test section of the postman, add these lines.
var xsrfCookie = postman.getResponseCookie("csrftoken"); postman.setEnvironmentVariable('csrftoken', xsrfCookie.value);
This extracts csrf token and sets it to an environment variable called csrftoken in the current environment.
Now in our requests, we can use this variable to set the header.(see image)
Set {{csrftoken}} in your header
When the token expires, we just need to log in again and csrf token gets updated automatically.
Thanks to #chillaranand from hackernoon.com to original post
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.csrf import csrf_protect
#csrf_protect
#csrf_exempt
def home(request):
Add "#csrf_protect, #csrf_exempt" Before the method
In urls file, try this:
urlpatterns = [
url(r'^your_uri/', views.YourView.as_view()),
]
this will tell django to allow requests without csrf

How can I return user ID with token in Django?

I generate tokens using default view in Django:
url(r'^login/', rest_auth_views.obtain_auth_token),
I have a problem because my front-end doesn't know what is the currently logged in user ID.
Should I return it with token or maybe create some another request?
I know that there is a lot of different ways, but I would like to choose the most optimal solution.
You could override rest_framework.authtoken.views.ObtainAuthToken.post in order to get the result you want.
myapp/views.py
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
class CustomObtainAuthToken(ObtainAuthToken):
def post(self, request, *args, **kwargs):
response = super(CustomObtainAuthToken, self).post(request, *args, **kwargs)
token = Token.objects.get(key=response.data['token'])
return Response({'token': token.key, 'id': token.user_id})
myapp/urls.py
from django.conf.urls import url
from .views import CustomObtainAuthToken
urlpatterns = [
url(r'^authenticate/', CustomObtainAuthToken.as_view()),
]
Sample results
$ http :8000/authenticate/ username=someuser password=secretpassword
HTTP/1.0 200 OK
Allow: POST, OPTIONS
Content-Language: en
Content-Type: application/json
Date: Tue, 22 Mar 2017 18:30:10 GMT
Server: WSGIServer/0.2 CPython/3.5.1
Vary: Accept-Language, Cookie
X-Frame-Options: SAMEORIGIN
{
"id": 16,
"token": "82e0bc9980a6b2c9a70969b0f8dc974418dda399"
}
The idea here is to override the post method of the ObtainAuthToken view class. Here all I have done is call the parent class to get the token, then look up that token to find the associated user id.
Hope that helps.
if you need to get user information on a webpage, you need to pass user information in a response of Login API or other API.
While using Token based authentication, after login, access token and refresh token are generated which are shall be given to client in login API response. This access token shall be passed in header as:
Authorization : Bearer <insert token here>
You need to put authentication_classes = [OAuth2Authentication]
in your view.
This will validate the if user is logged in also you will get to access logged in user's information by user=request.user.
I think the good practice will be to return user details in the response of login api.
If your built_in view doesn't return user details you can may be override the post method of obtain_auth_token. I once did this for djangorestframework-jwt obtain token method
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
user = serializer.object.get('user') or request.user
token = serializer.object.get('token')
response_data = {
'token': token,
'user': UserSerializer(user).data
}
response = Response(response_data, status=status.HTTP_200_OK)
if api_settings.JWT_AUTH_COOKIE:
expiration = (datetime.utcnow() +
api_settings.JWT_EXPIRATION_DELTA)
response.set_cookie(api_settings.JWT_AUTH_COOKIE,
response.data['token'],
expires=expiration,
httponly=True)
return response
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
by default response_data dict only had token details i added user object as well to achieve what you are trying to do.
I needed more than just user id with the token, so I did code to return the whole user object:
*I use custom user, so if you are using django default user, change model and serializer to default.
from users.serializers import CustomUserSerializer
from users.models import CustomUser
class CustomObtainAuthToken(ObtainAuthToken):
def post(self, request, *args, **kwargs):
response = super(CustomObtainAuthToken, self).post(request, *args, **kwargs)
token = Token.objects.get(key=response.data['token'])
user = CustomUser.objects.get(id=token.user_id)
return Response({'token': token.key, 'user': CustomUserSerializer(user).data})
Thanks to this thread it was easy! Hope my answer will save someones time too. :)

Categories