python-social-auth not getting correct Google OAuth2 details - django

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)

Related

Get all events of user

So currently I can login and store my refresh token(if need be I can also store the access token) in a db using Google OAuth2 while using python social auth.
models.py:
from django.db import models
from django.contrib.auth.models import AbstractUser
class Profile(AbstractUser):
refresh_token = models.CharField(max_length=255, default="")
I have a url /calendar that needs to retrieve the Google calendar events of the currently logged user. I also have a regular login that if the user has a refresh token which means that he has linked his google account to his account. How would I use get_events to just give all of the events associated with that account.
from googleapiclient.discovery import build
from google.oauth2.credentials import Credentials
def get_events(request):
print(request.user.refresh_token)
credentials = Credentials(get_access_token(request), scopes=SCOPES)
service = build('calendar', 'v3', credentials=credentials)
google_calendar_events = service.events().list(calendarId='primary', singleEvents=True,
orderBy='startTime').execute()
google_calendar_events = google_calendar_events.get('items', [])
return google_calendar_events
def get_access_token(request):
social = request.user.social_auth.get(provider='google-oauth2')
return social.extra_data['access_token']
views.py
def login(request):
if request.method == 'POST':
form = AuthenticationForm(request.POST)
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
if user:
if user.is_active:
auth_login(request, user)
return redirect('home')
else:
messages.error(request,'User blocked')
return redirect('login')
else:
messages.error(request,'username or password not correct')
return redirect('login')
else:
form = AuthenticationForm()
return render(request, 'registration/login.html',{'form':form})
Traceback:
RefreshError at /calendar
The credentials do not contain the necessary fields need to refresh the access token. You must specify refresh_token, token_uri, client_id, and client_secret.
Request Method: GET
Request URL: http://localhost:8000/calendar
Django Version: 3.2.9
Exception Type: RefreshError
Exception Value:
The credentials do not contain the necessary fields need to refresh the access token. You must specify refresh_token, token_uri, client_id, and client_secret.
credentials.json
{
"web": {
"client_id": "-.apps.googleusercontent.com",
"project_id": "=",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_secret": "-",
"redirect_uris": ["http://127.0.0.1:8000/oauth/complete/google-oauth2/", "http://localhost:8000/oauth/complete/google-oauth2/"],
"javascript_origins": ["http://127.0.0.1:8000", "http://localhost:8000"]
}
}
From your following replying,
It's most likely credentials = Credentials(get_access_token(request), scopes=SCOPES)
request.user_refresh_token is the value of the refresh token as well.
I also have a credentials.json file that contains a token_uri, client_id, and client_secret if that is correct which was used for login.
And, your question is to retrieve the access token using the refresh token. In this case, in order to retrieve the access token, how about the following sample script?
Sample script:
If client_id, client_secret, refresh_token and token_uri are put in a variable, you can use the following script.
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
credentials = Credentials(
token=None,
client_id="###", # Please set the cliend ID.
client_secret="###", # Please set client secret.
refresh_token='###', # Please set refresh token.
token_uri="###" # Please set token URI.
)
credentials.refresh(Request())
access_token = credentials.token
print(access_token)
# If you want to use the following script, you can use it.
# service = build('calendar', 'v3', credentials=credentials)
Or, if client_id, client_secret, refresh_token and token_uri are put in a file, you can use the following script.
tokenFile = '###' # Please set the filename with the path.
credentials = Credentials.from_authorized_user_file(tokenFile) # or, Credentials.from_authorized_user_file(tokenFile, scopes)
credentials.refresh(Request())
access_token = credentials.token
print(access_token)
# If you want to use the following script, you can use it.
# service = build('calendar', 'v3', credentials=credentials)
Reference:
google.oauth2.credentials module

GitHub user authentication in GitHub application through Django framework

I want to authenticate GitHub user to my GitHub application and serve to my local server 127.0.0.1:8000, but I am not able to take tokens.
This is how GitHub is showing authentication.
From GitHub documentation, I am not able to understand the process of authentication after generating private key, then how to create JWT and installation tokens ?
Could someone show me what to do next ?
You can follow "Obtaining an Access Token from a GitHub Application Webhook" (Jerrie Pelser), which itself takes from "JWT RSA & HMAC + ASP.NET Core" from Piotr Gankiewicz
Jerrie mentions as first step to convert your PEM file to XML format.
You can use an online tool or write a class
And you need your GitHub application Id:
You will find in the article the class JwtSecurityTokenHandler used to create the JSON Web Token from the XML key.
In Django:
#api_view(['POST'])
#permission_classes([AllowAny, ])
def authenticate_user(request):
try:
email = request.data['email']
password = request.data['password']
user = User.objects.get(email=email, password=password)
if user:
try:
payload = jwt_payload_handler(user)
token = jwt.encode(payload, settings.SECRET_KEY)
user_details = {}
user_details['name'] = "%s %s" % (
user.first_name, user.last_name)
user_details['token'] = token
user_logged_in.send(sender=user.__class__,
request=request, user=user)
return Response(user_details, status=status.HTTP_200_OK)
except Exception as e:
raise e
else:
res = {
'error': 'can not authenticate with the given credentials or the account has been deactivated'}
return Response(res, status=status.HTTP_403_FORBIDDEN)
except KeyError:
res = {'error': 'please provide a email and a password'}
return Response(res)

How should I be implementing user SSO with AAD in a Django application (using the Django Microsoft Authentication Backend module)?

I'm developing a Django (2.2.3) application with Django Microsoft Auth installed to handle SSO with Azure AD. I've been able to follow the quickstart documentation to allow me to log into the Django Admin panel by either using my Microsoft identity, or a standard username and password I've added to the Django user table. This all works out of the box and is fine.
My question put (really) simply is "What do I do next?". From a user's perspective, I'd like them to:
Navigate to my application (example.com/ or example.com/content) - Django will realise they aren't authenticated, and either
automatically redirect them to the SSO portal in the same window, or
redirect them to example.com/login, which requires them to click a button that will open the SSO
portal in a window (which is what happens in the default admin case)
Allow them to sign in and use MFA with their Microsoft Account
Once successful redirect them to my #login_required pages (example.com/content)
Currently, at the root of my navigation (example.com/), I have this:
def index(request):
if request.user.is_authenticated:
return redirect("/content")
else:
return redirect("/login")
My original idea was to simply change the redirect("/login") to redirect(authorization_url) - and this is where my problems start..
As far as I can tell, there isn't any way to get the current instance(?) of the context processor or backend of the microsoft_auth plugin to call the authorization_url() function and redirect the user from views.py.
Ok... Then I thought I'd just instantiate the MicrosoftClient class that generates the auth URL. This didn't work - not 100% sure why, but it think it may have something to do with the fact that some state variable used by the actual MicrosoftClient instance on the backend/context processor is inconsistent with my instance.
Finally, I tried to mimic what the automatic /admin page does - present an SSO button for the user to click, and open the Azure portal in a separate window. After digging around a bit, I realise that I fundamentally have the same problem - the auth URL is passed into the admin login page template as inline JS, which is later used to create the Azure window asynchronously on the client side.
As a sanity check, I tried to manually navigate to the auth URL as it is presented in the admin login page, and that did work (though the redirect to /content didn't).
At this point, given how difficult I think I'm making it for myself, I'm feel like I'm going about this whole thing completely the wrong way. Sadly, I can't find any documentation on how to complete this part of the process.
So, what am I doing wrong?!
A couple more days at this and I eventually worked out the issues myself, and learned a little more about how Django works too.
The link I was missing was how/where context processors from (third party) Django modules pass their context's through to the page that's eventually rendered. I didn't realise that variables from the microsoft_auth package (such as the authorisation_url used in its template) were accessible to me in any of my templates by default as well. Knowing this, I was able to implement a slightly simpler version of the same JS based login process that the admin panel uses.
Assuming that anyone reading this in the future is going through the same (learning) process I have (with this package in particular), I might be able to guess at the next couple of questions you'll have...
The first one was "I've logged in successfully...how do I do anything on behalf of the user?!". One would assume you'd be given the user's access token to use for future requests, but at the time of writing this package didn't seem to do it in any obvious way by default. The docs for the package only get you as far as logging into the admin panel.
The (in my opinion, not so obvious) answer is that you have to set MICROSOFT_AUTH_AUTHENTICATE_HOOK to a function that can be called on a successful authentication. It will be passed the logged in user (model) and their token JSON object for you to do with as you wish. After some deliberation, I opted to extend my user model using AbstractUser and just keep each user's token with their other data.
models.py
class User(AbstractUser):
access_token = models.CharField(max_length=2048, blank=True, null=True)
id_token = models.CharField(max_length=2048, blank=True, null=True)
token_expires = models.DateTimeField(blank=True, null=True)
aad.py
from datetime import datetime
from django.utils.timezone import make_aware
def store_token(user, token):
user.access_token = token["access_token"]
user.id_token = token["id_token"]
user.token_expires = make_aware(datetime.fromtimestamp(token["expires_at"]))
user.save()
settings.py
MICROSOFT_AUTH_EXTRA_SCOPES = "User.Read"
MICROSOFT_AUTH_AUTHENTICATE_HOOK = "django_app.aad.store_token"
Note the MICROSOFT_AUTH_EXTRA_SCOPES setting, which might be your second/side question - The default scopes set in the package as SCOPE_MICROSOFT = ["openid", "email", "profile"], and how to add more isn't made obvious. I needed to add User.Read at the very least. Keep in mind that the setting expects a string of space separated scopes, not a list.
Once you have the access token, you're free to make requests to the Microsoft Graph API. Their Graph Explorer is extremely useful in helping out with this.
So I made this custom view in Django based on https://github.com/Azure-Samples/ms-identity-python-webapp.
Hopefully, this will help someone.
import logging
import uuid
from os import getenv
import msal
import requests
from django.http import JsonResponse
from django.shortcuts import redirect, render
from rest_framework.generics import ListAPIView
logging.getLogger("msal").setLevel(logging.WARN)
# Application (client) ID of app registration
CLIENT_ID = "<appid of client registered in AD>"
TENANT_ID = "<tenantid of AD>"
CLIENT_SECRET = getenv("CLIENT_SECRET")
AUTHORITY = "https://login.microsoftonline.com/" + TENANT_ID
# This resource requires no admin consent
GRAPH_ENDPOINT = 'https://graph.microsoft.com/v1.0/me'
SCOPE = ["User.Read"]
LOGIN_URI = "https://<your_domain>/login"
# This is registered as a redirect URI in app registrations in AD
REDIRECT_URI = "https://<your_domain>/authorize"
class Login(ListAPIView):
'''initial login
'''
def get(self, request):
session = request.session
id_token_claims = get_token_from_cache(session, SCOPE)
if id_token_claims:
access_token = id_token_claims.get("access_token")
if access_token:
graph_response = microsoft_graph_call(access_token)
if graph_response.get("error"):
resp = JsonResponse(graph_response, status=401)
else:
resp = render(request, 'API_AUTH.html', graph_response)
else:
session["state"] = str(uuid.uuid4())
auth_url = build_auth_url(scopes=SCOPE, state=session["state"])
resp = redirect(auth_url)
else:
session["state"] = str(uuid.uuid4())
auth_url = build_auth_url(scopes=SCOPE, state=session["state"])
resp = redirect(auth_url)
return resp
class Authorize(ListAPIView):
'''authorize after login
'''
def get(self, request):
session = request.session
# If states don't match login again
if request.GET.get('state') != session.get("state"):
return redirect(LOGIN_URI)
# Authentication/Authorization failure
if "error" in request.GET:
return JsonResponse({"error":request.GET.get("error")})
if request.GET.get('code'):
cache = load_cache(session)
result = build_msal_app(cache=cache).acquire_token_by_authorization_code(
request.GET['code'],
# Misspelled scope would cause an HTTP 400 error here
scopes=SCOPE,
redirect_uri=REDIRECT_URI
)
if "error" in result:
resp = JsonResponse({"error":request.GET.get("error")})
else:
access_token = result["access_token"]
session["user"] = result.get("id_token_claims")
save_cache(session, cache)
# Get user details using microsoft graph api call
graph_response = microsoft_graph_call(access_token)
resp = render(request, 'API_AUTH.html', graph_response)
else:
resp = JsonResponse({"login":"failed"}, status=401)
return resp
def load_cache(session):
'''loads from msal cache
'''
cache = msal.SerializableTokenCache()
if session.get("token_cache"):
cache.deserialize(session["token_cache"])
return cache
def save_cache(session,cache):
'''saves to msal cache
'''
if cache.has_state_changed:
session["token_cache"] = cache.serialize()
def build_msal_app(cache=None, authority=None):
'''builds msal cache
'''
return msal.ConfidentialClientApplication(
CLIENT_ID, authority=authority or AUTHORITY,
client_credential=CLIENT_SECRET, token_cache=cache)
def build_auth_url(authority=None, scopes=None, state=None):
'''builds auth url per tenantid
'''
return build_msal_app(authority=authority).get_authorization_request_url(
scopes or [],
state=state or str(uuid.uuid4()),
redirect_uri=REDIRECT_URI)
def get_token_from_cache(session, scope):
'''get accesstoken from cache
'''
# This web app maintains one cache per session
cache = load_cache(session)
cca = build_msal_app(cache=cache)
accounts = cca.get_accounts()
# So all account(s) belong to the current signed-in user
if accounts:
result = cca.acquire_token_silent(scope, account=accounts[0])
save_cache(session, cache)
return result
def microsoft_graph_call(access_token):
'''graph api to microsoft
'''
# Use token to call downstream service
graph_data = requests.get(
url=GRAPH_ENDPOINT,
headers={'Authorization': 'Bearer ' + access_token},
).json()
if "error" not in graph_data:
return {
"Login" : "success",
"UserId" : graph_data.get("id"),
"UserName" : graph_data.get("displayName"),
"AccessToken" : access_token
}
else:
return {"error" : graph_data}

Re-implementing Social Logins (in Django)

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'),
]

Python Social Auth - User is authenticated with facebook but cannot access pages

I have an issue with Facebook authentication with Python Social Auth.
I have login with Facebook, Google and Twitter.
After login, I redirect the user to my dashboard at /user/dashboard with the use of login_redirect decorator. While it works fine with Google and Twitter, I am not able to redirect the user authenticated with Facebook.
#login_required
def home(request):
user = ""
if '_auth_user_id' in request.session:
user = AuthUser.objects.get(id=request.session['_auth_user_id'])
template = 'user/index.html'
return render(request, template, context)
In Settings.py
SOCIAL_AUTH_FACEBOOK_KEY = '******'
SOCIAL_AUTH_FACEBOOK_SECRET = '*******'
SOCIAL_AUTH_FACEBOOK_SCOPE = ['email', 'public_profile', 'user_location']
SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = {
'locale': 'en_US',
'fields': 'id, name, email, age_range, about, picture, location'
}
SOCIAL_AUTH_FACEBOOK_API_VERSION = '2.10'
When I remove the login_required decorator, the user is redirected to the dashboard. But when the user tries to go to another page, there django says user is not authenticated. Is this an issue with the Facebook API or the application?
Thanks for any replies.
1) Check AUTHENTICATION_BACKENDS. Facebook authentication backend must be in this list.
2) Cleanup cookies and check that facebook user is_active on you site.
Here's a quick and dirty fix. I didn't look at all possible scenarios. This answer can be improved. First step is to get rid of the login required decorator from the redirect view. Then use the following code in the view
if request.user.is_anonymous():
# check if user logged in through facebook, csrf token will be validated by the middleware
if '_auth_user_id' in request.session:
auth_user_id = request.session['_auth_user_id']
user_obj = User.objects.filter(id=auth_user_id)
request.user = user_obj[0]
userProfile = model_to_dict(user_obj[0])
else:
# redirect user to login page
return HttpResponseRedirect('/login/')
You may have to update your app permission to provide the desired pieces of information(including email).
Go to https://developers.facebook.com/tools/explorer/, select your app and the permission you want to provide. Then generate a new test access token.