views
class GoogleCreateAccount(APIView):
permission_classes = [permissions.AllowAny]
def post(self, request):
reg_serializer = RegisterSerializer(data=request.data)
if reg_serializer.is_valid():
new_user = reg_serializer.save()
if new_user:
# add these
r = requests.post('http://127.0.0.1:8000/auth/token', data={
'username': new_user.email,
'id_token': request.data['code'],
#'password': request.data['password'],
'client_id': '304129974707-79jslq7l318va16eacni8cveokn237g8.apps.googleusercontent.com',
'client_secret': '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',
'grant_type': 'id_token'
})
return Response(r.json(), status=status.HTTP_201_CREATED)
return Response(reg_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
urls.py
urlpatterns = [
path('auth/', include('drf_social_oauth2.urls',namespace='drf')),
path('googleregister/',GoogleCreateAccount.as_view()),
]
I was setting up an google login with django and react app i was setting it up based on the https://abhik-b.medium.com/step-by-step-guide-to-email-social-logins-in-django-5e5436e20591 above link. It is working on the bcakend as expected. But for getting the access token the parameters I giving is
{
clientid:
client secret:
grant type: password
username:
password:
}
But in the react it wasn't able to get the password parameter so it is not getting connected to the rest api call. I don't know exactly how to get this issue sorted. Please suggest me a best link or docs for setting up google login for django rest framework as backend and react js as frontend.
And is this possible to change the grant type to authorisation code and if so what needs to be changed?
Related
I'm currently working on a Django project and it's hosted on Heroku from GitHub. The front-end is served via Ajax calls to an API.
let myurl = "/api/properties/";
$.ajax({
async: true,
url:myurl,
method:'GET',
success: function(result){
....
}
});
Everything works well on localhost, but on Heroku, I keep getting the error:
GET http://127.0.0.1:8000/api/properties/ net::ERR_CONNECTION_REFUSED
My API URL has no http://127.0.0.1:8000 on GitHub but on Heroku it has it after analyzing the source code on the Sources tab on Chrome:
let myurl = "http://127.0.0.1:8000/api/properties/";
$.ajax({
async: true,
url:myurl,
method:'GET',
success: function(result){
....
}
});
I don't understand why my app's API URL isn't taking the domain URL https://appname.herokuapp.com/api/properties/ and it's across all the Ajax API URLs on the Django app. I have cleared my browser cache, opened the app in 'incognito mode' and even redeployed the project several times but not change. Kindly help me debug this and understand where the localhost URL is coming from.
UPDATE
My property API view
#login_required(login_url='Login' )
def AllProperties(request):
return render(request, 'Dashboard - Properties.html')
class PropertyAPI(generics.ListCreateAPIView):
serializer_class = PropertiesSerializer
permission_classes = [permissions.IsAuthenticated ]
def get_queryset(self):
"""
This view should return a list of all the properties
created by the currently authenticated user.
"""
user = self.request.user
return RentalProperties.objects.filter(created_by=user)
def perform_create(self, serializer):
"""
This view should create a new property listing
for the currently authenticated user.
"""
serializer.save(created_by=self.request.user)
class PropertyDetailAPI(generics.RetrieveUpdateDestroyAPIView):
queryset = RentalProperties.objects.all()
serializer_class = PropertiesSerializer
permission_classes = [permissions.IsAuthenticated , IsOwnerOrReadOnly]
Property API URLs
urlpatterns = [
path('dashboard/properties/', views.AllProperties, name="AllProperties"),
path('api/properties/', views.PropertyAPI.as_view(), name="PropertyListAPI"),
path('api/properties/<int:pk>/', views.PropertyDetailAPI.as_view(), name="PropertyDetailAPI"),
]
I am working on a Django+React project. My React app is my only UI renderer (which means no template or any markup is being served by Django), while my Django app is only used for serving APIs for me to access my database resources.
On a particular React page, I have login and test buttons that send requests to a DRF API in my Django backend.
Login API View
class AuthLogin(generics.GenericAPIView):
serializer_class = LoginSerializer
authentication_classes = [SessionAuthentication]
permission_classes = ()
def get(self, request):
user = request.user
if not user and not user.is_authenticated or user.is_anonymous:
return Response(
{
"status": True,
"message": "Login page accessed"
}, status=status.HTTP_200_OK
)
else:
return Response(
{
"status": False,
"message": "You are currently logged in"
}, status=status.HTTP_400_BAD_REQUEST
)
def post(self, request, *args, **kwargs):
res = []
res_stat = status.HTTP_401_UNAUTHORIZED
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=False)
validated_data = serializer.validated_data
if validated_data:
user = dj_auth(username=request.data.get('username'),
password=request.data.get('password'))
if user and user.is_authenticated and not user.is_anonymous:
auth_login(request, user)
res_stat = status.HTTP_200_OK
res = {
"status": True,
"message": "Login successful!",
"login": {
"date_time": timezone.now(),
"user": {
'id': user.id,
'first_name': user.first_name,
'last_name': user.last_name,
'email': user.email,
}
}
}
else:
res_stat = status.HTTP_401_UNAUTHORIZED
res = {
"status": False,
"message": "Invalid username and/or password"
}
else:
res_stat = status.HTTP_401_UNAUTHORIZED
res = {
"status": False,
"message": "Invalid username and/or password"
}
return Response(res, status=res_stat)
Test API View
class Test(APIView):
authentication_classes = [SessionAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request, *args, **kwargs):
if request.user and request.user.is_authenticated and not request.user.is_anonymous:
return Response({
"test": "coolsss",
"user": request.user.id
}, status=status.HTTP_200_OK)
else:
return Response({
"error": "Mind to login first?"
}, status=status.HTTP_401_UNAUTHORIZED)
So far so good in terms of Django+React integration on the above APIs. However, I noticed a security issue and I'm not sure completely how to move on from this.
I did the test in an incognito browser window. But, I also tested it in Postman. What I noticed was I already closed my incognito browser before testing on to Postman using the same csrftoken and sessionid cookies. When I closed the browser, I assume the session is destroyed since it was in incognito.
Now when I run the same in Postman, the test endpoint still authenticating, recognizes the user, and returns the positive response. I'm not sure if that is supposed to happen, and what approach shall be used to resolve this.
One sample scenario I see where this would be a nightmare is say Kate logs in and leaves her workstation for a minute. Her workmate John sneaks in to her browser and steals the csrftoken and sessionid cookie values of her current session.
Kate came back and for some reason closed her browser. John, using Postman on his own computer, made some POST request to an API which Kate is authorized to access using the token and sessionid he stole.
With this problem in session/cookie-based authentication, I am considering implementing token-based authentication instead, even if the login view is more appropriate to be authenticated by a server session and csrftoken cookie.
However, if I use token-based, I would also be storing that token in local storage so React will have the knowledge that the user is still logged in and for it to use that token for subsequent requests. But then again, a geek can always go to dev tool and sneak into that token value in the local storage.
I am so stuck at how I should properly implement authentication and security in the backend, while keeping the frontend of the user's login state.
Token-based (JWT for example) is the go-to option for integrating a server-side service to a client-side app. And as for the problem you are facing, the simplest option would be adding an expire option to the token which still might not be the solution to all scenarios.
The fact is that when you create a client-side app, the client has control and you can't do much about it. protecting the token is the same as protecting a password that users should be thinking about. The most you can do is to make it harder for someone else to be able to simply take the token and use it somewhere else.
You can make multiple tokens with different specifications that the client-side has to generate and if all of them match, then you can serve the request. but again, that just makes it harder for someone else to duplicate the request not impossible.
If it was a compiled app that was hard to decompile and with the combination of some methods like using some certificate file that is also securely stored in the client's system, you could be more sure about the safety of your request but JS is too simple to read and duplicate.
Making a POST requests to register a new user through postman returns 403 Forbidden, CSRF verification failed. Request aborted... Going by DRF documentation and knox auth documentation i have everything set up correctly. It appears that Django's SessionAuthentication is being activated even though i do not have it in my DEFAULT_AUTHENTICATION_CLASSES. I have tried every potential solution i could find but nothing is working. The app is a Django rest api with React front end. Any help would be greatly appreciated.
Authentication and Permission settings
'DEFAULT_AUTHENTICATION_CLASSES': (
'knox.auth.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
),
url calling the view as_view
re_path('auth/register', Register.as_view(), name='register'),
Class based Register view extending APIView which should handle csrf
class Register(APIView):
"""User Registration API View"""
def post(self, request, *args, **kwargs):
if request.method == 'POST':
serializer = RegistrationSerializer(data=request.data)
data = {}
if serializer.is_valid():
user = serializer.save()
data['response'] = 'Account registered successfully'
data['firstName'] = user.first_name
data['lastName'] = user.last_name
data['email'] = user.email
data['token'] = AuthToken.objects.get(user=user).key
return Response(data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Stack trace error
Forbidden (CSRF cookie not set.): /api/account/auth/register
[20/Jun/2020 12:15:14] "POST /api/account/auth/register HTTP/1.1" 403 2864
Update
I have found the issue and have added it as an answer below
For anyone else who may find themselves in this situation I hope this can be of some help. It appears that because I am integrating React with Django and using Reacts router the Django urls must be placed before the React urls in the base urls.py file otherwise React's router takes the request and runs it through its router, cannot find any matching urls and throws an error thus it never gets run through the Django api endpoints. This explains why i was getting CSRF errors as Django's SessionAuthentication was being hit through React router. After all the testing the answer was as simple as swapping two lines.
Previous Throwing Errors
urlpatterns = [
# Admin portal
re_path('admin/', admin.site.urls),
# React UI Entry
re_path('', include('frontend.urls'), name='frontend'),
# Rest API Urls
re_path('api/account/', include('account.api.urls'), name='account_api'),
]
Refactored No Errors
urlpatterns = [
# Admin portal
re_path('admin/', admin.site.urls),
# Rest API Urls
re_path('api/account/', include('account.api.urls'), name='account_api'),
# React UI Entry
re_path('', include('frontend.urls'), name='frontend'),
]
TL;DR: Is it OK to not store (Google/Facebook) OAuth2 access tokens, but rather request new ones on every login?
I'll explain:
To add social logins ("Login with Google/Facebook") to a Django app, you have Python Social Auth. The problem with it, at least for me, is that it's much more complicated than I'd like, requires a lot of configurations, creates additional tables in the database, and in general feels like a lot of moving parts I don't understand for a rather simple task.
So, I read a bit about Google's and Facebook's flows, and they're simple enough:
The server has an ID and a secret.
The clients/users have their standard login credentials.
Then:
The server redirects the user to Google/Facebook, and provides its ID and a redirection URI.
After the user has logged in, Google/Facebook redirects them to that URI with a code.
The server sends its ID, secret, and the received code to Google/Facebook, and gets an access token in exchange, which it can now use to make API calls on behalf of the user.
Even the most basic permissions are enough to query Google/Facebook for the user's email, which can then be matched against Django's standard User model (and if it doesn't exist, a password-less user can created).
The access token can now be discarded, as it was only necessary to translate the Google/Facebook login to an actual email (validated by them), which is used as the only user identifier - no more confusion due to different accounts when logging via different social services; no additional tables; no unnecessary complexity.
Here's the code, just to show how little is necessary:
# views.py
def login_view(request):
return render(request, 'login.html', {
'google_url': 'https://accounts.google.com/o/oauth2/v2/auth?' + urllib.parse.urlencode({
'client_id': GOOGLE_ID,
'redirect_uri': request.build_absolute_uri(reverse('oauth_google')),
'scope': 'profile email',
'response_type': 'code',
}),
}) # use this url in the template as a social login
def oauth_google(request):
code = request.GET['code']
response = requests.post('https://www.googleapis.com/oauth2/v4/token', {
'code': code,
'client_id': GOOGLE_ID,
'client_secret': GOOGLE_SECRET,
'redirect_uri': request.build_absolute_uri(reverse('oauth_google')),
'grant_type': 'authorization_code',
}).json()
id_token = response['id_token']
response = requests.get('https://www.googleapis.com/oauth2/v3/tokeninfo', {
'id_token': id_token,
}).json()
email = response['email']
user = User.objects.filter(username=email).first()
if not user:
user = User(username=email)
user.save()
auth.login(request, user)
return redirect('index')
# urls.py
urlpatterns = [
...
path('oauth/google', views.oauth_google, name='oauth_google'),
]
My question is: what am I missing? If it's really that simple, why couldn't I find any answers on StackOverflow / tutorials on the web describing just that?
The only reason I can think of is that access tokens come with an expiration time (anywhere between 1 hour and 60 days); so maybe I'm supposed to keep reusing the same access token as long as it's valid (which will require storing it, and would explain why Python Social Auth needs additional tables). Will Google/Facebook get mad at me for requesting new access tokens too frequently and block me? I couldn't find any mention of this in their documentation.
EDIT
Here's the Facebook code, in case anyone finds these snippets useful:
# views.py
def login_view(request):
return render(request, 'login.html', {
'facebook_url': 'https://www.facebook.com/v3.2/dialog/oauth?' + urllib.parse.urlencode({
'client_id': FACEBOOK_ID,
'redirect_uri': request.build_absolute_uri(reverse('oauth_facebook')),
'scope': 'email',
}),
}) # use this url in the template as a social login
def oauth_facebook(request):
code = request.GET['code']
response = requests.get('https://graph.facebook.com/v3.2/oauth/access_token', {
'code': code,
'client_id': FACEBOOK_ID,
'client_secret': FACEBOOK_SECRET,
'redirect_uri': request.build_absolute_uri(reverse('oauth_facebook')),
}).json()
access_token = response['access_token']
response = requests.get('https://graph.facebook.com/me', {
'access_token': access_token,
'fields': 'email',
}).json()
email = response['email']
user = User.objects.filter(username=email).first()
if not user:
user = User(username=email)
user.save()
auth.login(request, user)
return redirect('index')
# urls.py
urlpatterns = [
...
path('oauth/facebook', views.oauth_facebook, name='oauth_facebook'),
]
I want to login a user using the python-social-auth functionality for Google Plus signin in Django. When logging in from my website, everything works fine and the correct details are added to the database.
However, I want to authenticate from my Android application as well. The user logs in in the application, which then sends the access token to the django API, which handles the login process in the following code, adapted from the documentation:
#csrf_exempt
#serengeti_api_request
#psa('social:complete')
def login_social_token(request, backend):
# Ensure the token has been specified.
token = request.META.get('HTTP_ACCESSTOKEN')
if token is None:
raise SerengetiApiRequestException('Access token is missing!')
# Login the user for this session
user = request.backend.do_auth(token)
if user is None:
raise SerengetiApiRequestException('Could not authenticate user!')
login(request, user)
# Store the email address if one has been specified (e.g. Twitter)
email = request.META.get('HTTP_EMAIL')
if email is not None:
user.email = email
user.save()
# Prepare the parameters to be returned
response = dict({
'id': user.id,
'first_name': user.first_name,
'last_name': user.last_name,
'api_key': request.session.session_key,
})
# Return a 200 status code to signal success.
return HttpResponse(json.dumps(response, indent=4), status=200)
When logging in from the website, the social_auth_usersocialauth table contains:
id | provider | uid | extra_data
==========================================
10 | google-oauth2 | <myemail> | {"token_type": "Bearer", "access_token": "<token>", "expires": 3600}
However, when logging in from the application using the above function, the operation completes ok, but the entry in the table looks like this:
id | provider | uid | extra_data
=========================================
10 | google-oauth2 | <empty> | {"access_token": "", "expires": null}
Also, the auth_user table contains a username like eeed494412obfuscated48bc47dd9b instead of the Google Plus username and the email field is empty.
What am I doing wrong and how can I obtain the same functionality as I get on the website?
I would like to mention that I have implemented Facebook and Twitter authentication from the Android application, which call the above-mentioned function and store the correct details, only Google Plus is causing problems.
Just wanted to share an alternative way of doing this. This example is quite primitive and doesn't cover all cases (e.g. failed authentication). However, it should give enough insight into how OAuth2 authentication can be done.
Obtain CLIENT ID
Obtain a CLIENT ID from OAuth2 service provider (e.g. Google) and configure redirect URLs.
I assume you have already done this.
Create a login / registration link
You need to generate a login / registration link in your view. It should be something like this:
https://accounts.google.com/o/oauth2/auth?response_type=code&client_id={{CLIENT_ID}}&redirect_uri={{REDIRECT_URL}}&scope=email
Replace {{CLIENT_ID}} and {{REDIRECT_URL}} with the details you obtained in the previous step.
Create a new view
In urls.py add something like:
url(r'^oauth2/google/$', views.oauth2_google),
In your views.py create a method:
def oauth2_google(request):
# Get the code after a successful signing
# Note: this does not cover the case when authentication fails
CODE = request.GET['code']
CLIENT_ID = 'xxxxx.apps.googleusercontent.com' # Edit this
CLIENT_SECRET = 'xxxxx' # Edit this
REDIRECT_URL = 'http://localhost:8000/oauth2/google' # Edit this
if CODE is not None:
payload = {
'grant_type': 'authorization_code',
'code': CODE,
'redirect_uri': REDIRECT_URL,
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET
}
token_details_request = requests.post('https://accounts.google.com/o/oauth2/token', data=payload)
token_details = token_details_request.json()
id_token = token_details['id_token']
access_token = token_details['access_token']
# Retrieve the unique identifier for the social media account
decoded = jwt.decode(id_token, verify=False)
oauth_identifier = decoded['sub']
# Retrieve other account details
account_details_request = requests.get('https://www.googleapis.com/plus/v1/people/me?access_token=' + access_token)
account_details = account_details_request.json()
avatar = account_details['image']['url']
# Check if the user already has an account with us
try:
profile = Profile.objects.get(oauth_identifier=oauth_identifier)
profile.avatar = avatar
profile.save()
user = profile.user
except Profile.DoesNotExist:
user = User.objects.create_user()
user.save()
profile = Profile(user=user, oauth_identifier=oauth_identifier, avatar=avatar)
profile.save()
user.backend = 'django.contrib.auth.backends.ModelBackend'
login(request, user)
return redirect('/')
You might need the following imports:
from django.shortcuts import redirect
import jwt # PyJWT==0.4.1
import requests # requests==2.5.0
import json
I have a project (not running actually) with google oauth2 authentication. I leave here my config file so it may be useful to you (I was only using oauth2 so some things may vary):
AUTHENTICATION_BACKENDS = (
'social.backends.google.GoogleOAuth2', # /google-oauth2
'django.contrib.auth.backends.ModelBackend',
)
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = 'your google oauth 2 key'
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = 'your secret google oauth 2 key'
SOCIAL_AUTH_PIPELINE = (
'social.pipeline.social_auth.social_details',
'social.pipeline.social_auth.social_uid',
'social.pipeline.social_auth.auth_allowed',
'social.pipeline.social_auth.associate_by_email',
'social.pipeline.social_auth.social_user',
'social.pipeline.user.get_username',
'social.pipeline.user.create_user',
'social.pipeline.social_auth.associate_user',
'social.pipeline.social_auth.load_extra_data',
'social.pipeline.user.user_details'
)
I attach the view also (note that I'm using django rest framework).
class ObtainAuthToken(APIView):
permission_classes = (permissions.AllowAny,)
serializer_class = AuthTokenSerializer
model = Token
# Accept backend as a parameter and 'auth' for a login / pass
def post(self, request, backend):
if backend == 'auth': # For admin purposes
serializer = self.serializer_class(data=request.DATA)
if serializer.is_valid():
token, created = Token.objects.get_or_create(user=serializer.object['user'])
return Response({'token': token.key})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
# Here we call PSA to authenticate like we would if we used PSA on server side.
user = register_by_access_token(request, backend)
# If user is active we get or create the REST token and send it back with user data
if user and user.is_active:
token, created = Token.objects.get_or_create(user=user)
return Response({'id': user.id, 'name': user.username, 'token': token.key})
else:
return Response("Bad Credentials, check the Access Token and/or the UID", status=403)
#strategy('social:complete')
def register_by_access_token(request, backend):
# This view expects an access_token GET parameter
token = request.GET.get('access_token')
backend = request.strategy.backend
user = backend.do_auth(access_token=token, backend=backend)
if user:
# login(request, user) #Only useful for web..
return user
else:
return None
and in the urls.py:
urlpatterns = patterns(
'',
url(r'^login/(?P<backend>[\w-]+)$', ObtainAuthToken.as_view(), ),
)
Sorry for attaching all this code and not providing a specific answer but more data is needed because the error can come from many sources (bad api keys, bad settings configuration, pipeline..). I hope the code helps.
I finally figured it out myself. According to this article in the Android's Google Plus documentation, I also need to request the plus.profile.emails.read scope when making the request in the Android app. Once I added this, the python-social-auth code managed to store the email properly in the uid fields. This allows it to recognize the same user whether logging in from the website or the app, which is what I needed. Here's the scopes string I use:
String scopes = "oauth2:" + Plus.SCOPE_PLUS_LOGIN + " https://www.googleapis.com/auth/plus.profile.emails.read";
However, the extra_data field still contains the values I mentioned above. I believe this is due to needing to request offline access as well, which would allow Google Plus to pass the missing fields back to python-django-auth. More details can be found here.
I've been running into the same problem. The reason why the extra_fields on your google user isn't being set is because python-social-auth calls the google server to set those things, but if you're calling Google with just an access_token, it won't be enough to get Google to return the refresh_token and all those other auth related fields. You can hack it by setting them manually, but then you'd end up using the same access and refresh tokens as the client. Google recommends that you use the client to generate a new authorization token with whatever scopes you need, and then send that auth token to the server, which then will turn it into an access and refresh token. See here for the details (it's a bit of an involved read): https://developers.google.com/identity/protocols/CrossClientAuth
If you're really committed to doing this in the scope of what python-social-auth does, I'd recommend making a custom auth backend, call it GoogleOAuth2AuthorizationCodeAuth (see here for details).
The lazier and probably easy-to-break and gross way is to post the access_token to my server to sign in as a google user (which you're doing properly, it seems), and then later, get another authorization token from the client in order to post to a separate endpoint, which I'll then handle turning into another Credentials model object that's connected to a user profile.
In DjangoRestFramework:
class GoogleAuthorizationCodeView(APIView):
def post(self, request, format=None):
credentials = flow.step2_exchange(code)
saved_creds = GoogleCredentials.objects.create(credentials=credentials)
return Response(status=status.HTTP_201_CREATED)