Integrating django-axes with customized django rest framework and simpleJWT - python

I am a beginner at Django and was building a login system. I wanted to use axes with django rest framework and simpleJWT.
According to the documentation for Django-Axes:
Modern versions of Django REST Framework after 3.7.0 work normally with Axes out-of-the-box and require no customization in DRF.
Now I have built a Custom User for my project who gets authenticated using my own custom authentication backend. For creating the Access and the Refresh tokens I have localhost:8000/api/loginend point which creates both the tokens and stores them as HTTPOnly cookie.
The login view is like this :
#api_view(['POST'])
#permission_classes([AllowAny])
#ensure_csrf_cookie
def login_view(request):
# print("captcha token = ", request.POST.get('g-recaptcha-response'))
Account = get_user_model()
EmailId = request.data.get('EmailId')
password = request.data.get('password')
response = Response()
if (EmailId is None) or (password is None):
raise exceptions.AuthenticationFailed('username and password required')
# get the user with the credentials provided
user = Account.objects.filter(EmailId=EmailId).first()
if not check_user_validity(user, password):
# locking the user
raise exceptions.AuthenticationFailed('The credentials are invalid')
else:
print('user is valid')
# get the token values
tokenvals = get_jwt_tokens(user)
response.set_cookie(key='refreshtoken', value=tokenvals['refresh_token'], httponly=True)
response.set_cookie(key="accesstoken", value=tokenvals['access_token'])
response.data = {
'access_token': tokenvals['access_token'],
'id': user.id
}
return response
I can refresh the access token using localhost:8000/api/Refresh endpoint.
get_jwt_token(user) returns a dictionary with both access and refresh tokens created in a customized way. It doesn't make use of any JWT Serializers or Views.
For the Login View I want to use axes to block users upon multiple invalid logins. But I have no clue how I could proceed from here on.
I followed the documentation for axes and modified the settings file like it was mentioned.
AUTHENTICATION_BACKENDS = [
# AxesBackend should be the first backend in the AUTHENTICATION_BACKENDS list.
'axes.backends.AxesBackend',
# Django ModelBackend is the default authentication backend.
'django.contrib.auth.backends.ModelBackend',
]
MIDDLEWARE = [
..
..
..
# at the end
'axes.middleware.AxesMiddleware',
]
INSTALLED_APPS = [
..
..
..
"authentication",
"axes",
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
# a custom authentication method
'authentication.authentication.SafeJWTAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated', # make all endpoints private
)
}
How can I proceed from this point onwwards? I couldn't really figure out much since I have done plenty customization..
Also Should I include 'django.contrib.auth.backends.ModelBackend', in the Backend since I am using a custom authentication?
How can I proceed from this point onwwards?

Related

How can I authenticate a user who belongs to another database through my other microservice in django rest framework?

I'm new to django and I am required to create two microservices with separate databases;
One to hold user information and the other to hold todo/tasks information. So far, I have created two separate projects with two separate databases,
To authenticate the user using simplejwt authentication. (todo_auth project with todo_auth database)
To show the todo/task information specific to that user. (todo project with todo database)
I need the todo project to verify the token by routing it back to the todo_auth project, and then I need the todo_auth project to send a response to the todo project. (By specifying the port)
How can I achieve this? Many thanks.
PS: I'm running the two django projects on the same server with different port numbers.
Simple JWT provides a verify route that you can pass a token to which will validate it was singed by the server and it is not expired.
From the documentation:
You can also include a route for Simple JWT’s TokenVerifyView if you wish to allow API users to verify HMAC-signed tokens without having access to your signing key:
from rest_framework_simplejwt.views import TokenVerifyView
urlpatterns = [
...
path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
...
]
If you want to do some other logic you should just write a normal view, use the JWT auth provided, and have the other one forward the token in the request
# todo-project
class ToDoView(APIView):
def get(self, request):
auth = request.headers["authorization"]
response = requests.get(
"http://todo-auth.sevice.com/api/do-thing/",
headers={
"Authorization": auth
}
)
if response.status_code = 200:
do_something(response.json())
# todo-auth-service
class DoThing(APIView):
authentication_classes = [JWTAuthentication]
def get(self, request):
...

Why do we need to send JWT from client for every API after login, as DRF maintains isAuthenticated Flag after login in settings.py?

I have just started to explore django. I want to know whether JWT should be verified for every view manually in the server side or is there any better way of doing it.
Login View:
class LoginUser(APIView):
permission_classes = ()
def post(self, request, *args, **kwargs):
username = request.data.get('username')
password = request.data.get('password')
user = authenticate(username=username, password=password)
if user:
payload = jwt_payload_handler(user)
return Response({
'response_code':'success',
'response_msg':'Login successfull',
'username':user.username,
'token': jwt.encode(payload, SECRET_KEY)
},status.HTTP_200_OK)
else:
return Response(
{'response_code':'error',
'response_msg':'Invalid credentials'},status.HTTP_400_BAD_REQUEST
)
Menu View (to fetch menu)
def getMenu(request):
menu_list = serializers.serialize("json", Menu.objects.all())
return HttpResponse(menu_list)
settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
),
}
My Login View returns JWT token in response. Login view works fine, but post login if I want to fetch Food Menu without sending JWT(got in response of login view), i am able to get the menu list, but ideally it should fail, saying Token is missing and should not return the response, but i am getting response.
I am curious to know, if DRF is only checking whether user is Authenticated or not and based on that if it returns response, then what is the point of JWT.
Your menu view looks like a normal django view. You have to use a DRF view
since you've only configured authentication and permission for DRF.

Django REST Framework Web Login not working

I can't seem to figure out why my rest framework login in my django project won't work... I have got the log in link there and all and it does redirect me to the rest login page but after filling in my credentials and clicking login it just takes me back to the page I came from but it does not seem to authenticate me as it still says "Login" in the top right corner...
I would expect it to change to "admin" in this case as I am trying to login using my superuser named "admin"...
I am surely missing something..
Here are my REST Urls
path("rest/auctions", auction_list, name="auction-list"),
path('rest/auctions/<int:pk>', auction_detail, name="auction-detail"),
path('rest-auth/', include('rest_framework.urls', namespace='rest_framework')),
The rest-auth/ one I know is the one that somehow adds the login button and all (Magic?) But maybe I have to do something else too to make it authenticate? I don't know... I am using the built in django authentication system and User model.
views.py
#api_view(['GET'])
def auction_list(request, format=None):
if request.method == 'GET':
query = request.GET.get('q')
if query is not None:
auctions = Auction.objects.filter(Q(title__icontains=query), deadline__gte=timezone.now(),
status=Auction.STATUS_ACTIVE)
else:
auctions = Auction.objects.all()
serializer = AuctionSerializer(auctions, many=True, context={'request': request})
return Response(serializer.data)
serializer.py
class AuctionSerializer(serializers.ModelSerializer):
winning_bid = serializers.SlugRelatedField(many=False, read_only=True, slug_field='amount', allow_null=True)
seller = serializers.SlugRelatedField(many=False, read_only=True, slug_field='username', allow_null=False)
class Meta:
model = Auction
fields = ('id', 'url', 'title', 'description', 'deadline', 'min_price', 'winning_bid', 'seller')
in my Settings.py
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly'
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
],
}
Lastly I can mention that the authentication works when I use the API via Postman.
If the other solution doesn't work, add this line too
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
),
You should remove BasicAuthentication from the settings.py and it should work as expected. BasicAuthentication works by base64 encoding the user login information and attaching them to HTTP Authorization Header. That's why your requests via Postman work normally.
By default Django uses Session based authentication, which depends on cookies on the client to store the user session information once the user is logged in. Obviously username and password are not stored in cookie with BasicAuthentication.
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly'
],
}

Python Social Auth, Google, and refresh token

In a personal project, I am trying to use Django as my front end and then allow data entered by users in a particular form to be copied to google sheets.
Google's own docs recommend using https://github.com/google/oauth2client which is now deprecated, and the docs have not been updated. With this, I have started attempting to use Python Social Auth and Gspread. For Gspread to be able to function correctly, I need to be able to pass it not only an access token but also a refresh token. Python Social Auth however is not persisting the refresh token along with the rest of the "extra data". Looking at the data preserved and the URLs routed to, it seems to me more like somewhere it is routing through Google+.
I have the following configurations in my Django settings files:
AUTHENTICATION_BACKENDS = (
'social_core.backends.google.GoogleOAuth2',
'django.contrib.auth.backends.ModelBackend',
)
SOCIAL_AUTH_PIPELINE = (
'social_core.pipeline.social_auth.social_details',
'social_core.pipeline.social_auth.social_uid',
'social_core.pipeline.social_auth.social_user',
'social_core.pipeline.user.get_username',
'social_core.pipeline.user.create_user',
'social_core.pipeline.social_auth.associate_user',
'social_core.pipeline.social_auth.load_extra_data',
'social_core.pipeline.user.user_details',
'social_core.pipeline.social_auth.associate_by_email',
)
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = '...'
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = '...'
SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = ['https://www.googleapis.com/auth/spreadsheets']
Is there a better way to access a google sheet?
Am I correct that PSA or google is redirecting me into a Google+ auth flow instead of the Google Oauth2?
If not, what must change so that Python Social Auth keeps the refresh token?
It's true that python-social-auth will use some bits of the Google+ platform, at least the API to retrieve details about the user to fill in the account.
From your settings, I see you have associate_by_email at the bottom, at that point, at that point it has no use since the user is already be created, if you really plan to use it, it must be before the create_user one, you can check the DEFAULT_PIPELINE as a reference.
In order to get a refresh_token from google, you need to tell it that you want one, to do that you need to set the offline access type:
SOCIAL_AUTH_GOOGLE_OAUTH2_AUTH_EXTRA_ARGUMENTS = {
'access_type': 'offline'
}
With that setting Google will give you a refresh_token and it will automatically stored in extra_data.
Just provide this in your settings.py:
SOCIAL_AUTH_GOOGLE_OAUTH2_AUTH_EXTRA_ARGUMENTS = {
'access_type': 'offline',
'hd': 'xyzabc.com',
'approval_prompt':'force'
}
remeber there is {'approval_prompt' : 'force'} which will force the user to select the gmail account, this way you will get refresh token.
You can send extra parameters to the OAuth2 provider using the variable
SOCIAL_AUTH_<PROVIDER>_AUTH_EXTRA_ARGUMENTS
For Google, you can see the extra parameters they accept in their documentation (scroll down to "parameters"). The one we are looking for is access_type:
access_type: Indicates whether your application can refresh access tokens when the user is not present at the browser. Valid parameter values are online, which is the default value, and offline.
So we can add the following to settings.py, to indicate that we want to receive a refresh token:
SOCIAL_AUTH_GOOGLE_OAUTH2_EXTRA_ARGUMENTS = {"access_type: offline"}
The results from EXTRA_ARGUMENTS will be stored in extra_data, so the refresh token can be accessed like this:
refresh_token = user.social_auth.get(provider="google-oauth2").extra_data["refresh_token"]
One possible solution is to store the refresh token alongside the user in a UserProfile model, by adding a custom function to the social-auth pipeline:
Create the model
# models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
refresh_token = models.CharField(max_length=255, default="")
Add a function to access store the refresh token
# pipeline.py
from .models import UserProfile
def store_refresh_token(user=none, *args, **kwargs):
extra_data = user.social_auth.get(provider="google-oauth2").extra_data
UserProfile.objects.get_or_create(
user=user, defaults={"refresh_token": extra_data["refresh_token"]}
)
Add our new function to the social-auth pipeline.
# settings.py
...
SOCIAL_AUTH_PIPELINE = (
...
"my-app.pipeline.store_refresh_token"
)
SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = [
'https://www.googleapis.com/auth/spreadsheets'
# any other scopes you need
]
...
The token is now stored alongside the user and can be used to initialise the sheets client or whatever else you need.

How do I create a login API using Django Rest Framework?

I want to create a login api (or use an existing one if it is already pre-bundled) using django rest framework. However, I'm completely at a loss. Whenever I send a post request to the django rest framework "login" url, it just sends back the browsable api template page...
MY CONFIGURATION
urls.py
url(r'^api/v1/', include('rest_framework.urls', namespace='rest_framework'))
settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}
WHAT I WANT
Request:
POST /api/v1/login username='name' pass='pass'
Response:
200 OK "{username: 'name', 'userId': '54321'}" set-cookie: sessionid="blahblah"
Take a look at the api view from django-rest-framework-jwt. It's an implementation for creating auth tokens rather than cookie sessions, but your implementation will be similar. See views.py and serializers.py. You can probably use the serializers.py unchanged, and just adjust your views to return the right parameters and possibly set the session cookie (can't recall if that's already performed in authentication).
If you want something like this I do the same thing however I use Token authentication.
Check out their token page here
This may not be what you want but the way I do it is (since I'm using it as a rest api endpoints for mobile clients)
I can do my url localhost:8000/api/users/ -H Authorization : Token
A browser could then use the regular login page that you create at the provided rest framework url
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')
and to get tokens for 'login-less' navigation
url(r'^api-token-auth/', 'rest_framework.authtoken.views.obtain_auth_token')
Then if you make calls and such you can pass the authorization tokens. Of course this is just how I do it and it's probably not the most efficient way but my goal was to create a way that I can provide users with session authentication for browsers and mobile access via tokens.
Then in your views.py make sure you add the authentication requirements for that view. Almost the same as session authentication section
permission_classes = (permissions.IsAdminUser,)
but also include
authentication_classes = (authentication.TokenAuthentication,)
I hope this helps but if not, good luck on your search.
Of course token is a good way to authenticate, but questioner is asking about session authentication.
Request:
POST /api/v1/login username='username' password='password'
Put csrftoken value at X-CSRFToken in header
Even though someone using email as username filed, username name parameter is required for email input (e.g. username='sample#domain.com')
Response:
302 FOUND sessionid="blahblah"
If you not specified next value, it will automatically redirect into /accounts/profile/ which can yield 404 error
Adding our views:
from rest_framework_jwt.views import refresh_jwt_token
urlpatterns = [
...
url(r'^rest-auth/', include('rest_auth.urls')),
url(r'^rest-auth/registration/', include('rest_auth.registration.urls')),
...
url(r'^refresh-token/', refresh_jwt_token),
]

Categories