How to synchronize users in django rest api with Auth0 - django

I use Angular 2 as frontend, django rest framework as backend.
At frontend I use Auth0 for authenticating user (https://auth0.com/docs/quickstart/spa/angular2 ). and after I send the idtoken to my backend for creating news users (https://auth0.com/docs/quickstart/backend/python connected auth0
the code in angular 2:
import { Component } from '#angular/core';
import { Auth } from './auth.service';
import { AuthHttp } from 'angular2-jwt';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
#Component({
selector: 'ping',
templateUrl: 'app/ping.template.html'
})
export class PingComponent {
API_URL: string = 'http://localhost:8000/callback/';
message: string;
constructor(private auth: Auth, private http: Http, private authHttp: AuthHttp) {}
// the code for sending idtoken to my backend
//correct me please if I am wrong
public securedPing() {
this.message = '';
this.authHttp.post(`${this.API_URL}`,localStorage.getItem('id_token'))
.map(res => res.json())
.subscribe(
data => this.message= data.text,
error => this.message = error._body || error
);
}
};
and here the code of my backend in Django :
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.http import JsonResponse
from places_management.serializers import UserSerializer
from django.contrib.auth.models import User
import jwt
class Callbacks(APIView):
authentication_classes = []
permission_classes = []
def authenticate(error):
return Response(error,401)
def post(self, request, format=None):
"""
Callback for after user logs in. It creates a
django auth user if one does not exist, username is the
user_id retured frrom auth0
"""
#token = request.META['HTTP_AUTHORIZATION'].split('JWT ')[1]
auth = request.META.get('HTTP_AUTHORIZATION', None)
if not auth:
return Response({'code': 'authorization_header_missing', 'description': 'Authorization header is expected'},status=status.HTTP_401_UNAUTHORIZED)
parts = auth.split()
if parts[0].lower() != 'bearer':
return authenticate({'code': 'invalid_header', 'description': 'Authorization header must start with Bearer'})
elif len(parts) == 1:
return authenticate({'code': 'invalid_header', 'description': 'Token not found'})
elif len(parts) > 2:
return authenticate({'code': 'invalid_header', 'description': 'Authorization header must be Bearer + \s + token'})
token = parts[1]
try:
payload = jwt.decode(
token,
'Z-HWF9cDxGTk7aMZe0A2Ygt81vGBPihz1FCRzJfS87B0mCw1ClQzp1HgA7U3WsSg',
audience='GwtnxdwhMWsuGz6JxabDkrNvFhAvn5ZJS'
)
except jwt.ExpiredSignature:
return authenticate({'code': 'token_expired', 'description': 'token is expired'})
except jwt.InvalidAudienceError:
return authenticate({'code': 'invalid_audience', 'description': 'incorrect audience, expected: GwtnxdwhMWsuGz6JxabDkrNvFhvn5ZJS'})
except jwt.DecodeError:
return authenticate({'code': 'token_invalid_signature', 'description': 'token signature is invalid'})
#try:
#payload = settings.JWT_AUTH['JWT_DECODE_HANDLER'](token)
#except:
#return Response('text',status=status.HTTP_401_UNAUTHORIZED)
user = User.objects.filter(username=payload['sub']).first()
if(user is None):
password = User.objects.make_random_password()
user = User.objects.create_user(
username=payload['sub'], password=password)
serializer = UserSerializer(user, context={'user': user})
return Response(serializer.data, status=status.HTTP_200_OK)
Maybe I am wrong, please help

As usual in software development there's more than one way to approach a given problem and the most suitable solution usually requires delving into a lot of small details.
However, there are a few things that are safe to say based on the information you shared. If you're leveraging Auth0 for use authentication you should not then be creating users in your back-end with passwords. Passwords are for authentication and you delegated that part, which is a good thing, as it's less work for you.
Having said that it's completely normal to still have some kind of per-user storage associated with your application, for example, to store user-specific application settings or other user owned data.
For this you'll have to store that data in association with a user identifier that can be safely used to identify the same user across multiple authentication sessions. That identifier should be the sub claim value contained within the ID Token given that per specification that value is stable and will allow you to identify recurring users when they complete the authentication through Auth0.
In conclusion, you back-end should not create new user identities as that is within the scope of authentication which is handled by Auth0; instead you should store data in association with a user identifier that allows you to continuously identify recurring users.

Please Refer , It do contain full demo of Rest Api With JWT, Auth() , Api Integrated in front end with angular(1) js
https://github.com/rmemon/Angular-JS-django-rest-framework-jwt

Related

Setup google login using Django rest framework plus React Js

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?

How to use Django's session authentication with Django Channels?

I am developing a chess web app using Django, Django Channels and React. I am using websockets for the game play between the online players and for getting updated which players are now online and available to play. However I am stuck on the authentication part. I first started with token authentication, but I found that it is not possible to send custom headers with the token in them as part of the websocket request. Then I went back to the default django.contrib.auth session authentication. Unfortunately, when the client logs in and connects to the websocket I am not able to get their user info as if the user is using a different session with the websocket. I get the value AnonymousUser when I print self.scope["user"] in the websocket consumers. Note that I am able to exchange messages using the websocket, and the authentication works well with normal http requests as I can prevent users who are not logged in from accessing views.
I am guessing the problem is related to the fact that websocket requests on the client side don't access or use the cookie for the authentication like the http requests.
Has anybody faced a similar problem and how did they fix it?
This is how I send the websocket message in react:
submitMessage = (evt) => {
//console.log("token in Messenger=",this.props.token);
evt.preventDefault();
const message = { message: this.state.message_to_send}
this.ws.send(JSON.stringify(message))
}
This is the backend code for handling the websocket requests:
from channels.generic.websocket import WebsocketConsumer
import json
from asgiref.sync import async_to_sync
class LoggedUsersConsumer(WebsocketConsumer):
def connect(self):
self.user = self.scope["user"]
print(self.scope)
print(self.user,"+++++++++++++")
#Join group
async_to_sync(self.channel_layer.group_add)(
"logged_users",
self.channel_name
)
self.accept()
def disconnect(self, close_code):
async_to_sync(self.channel_layer.group_discard)(
"logged_users",
self.channel_name
)
def receive(self, text_data):
self.user = self.scope["user"]
print(self.user,"+++++++++++++")
text_data_json = json.loads(text_data)
print(text_data_json)
message = text_data_json['message']
# Send message to room group
async_to_sync(self.channel_layer.group_send)(
"logged_users",
{
'type': 'logged_user_message',
'message': message
}
)
def logged_user_message(self, event):
message = event['message']
# Send message to WebSocket
self.send(text_data=json.dumps({
'message': message
}))
I think you are right, you probably do not have a session-cookie for requests from the client side that's why you get AnonymousUser. I don't think this has anything to do with how you handle websocket requests neither in React nor in Django.
Please check your browser's cookies in your React frontend (through the Developer Tools in Chrome/Firefox). You should have at least 2 cookies, csrftoken and sessionid. If any of these are lacking, the following might help you in the right direction. I experienced the same when developing with Vue, Django Channels and Django Rest Framework.
If you visit your Django backend through your browser, the HTML-templates and your browser takes care of setting the cookies. When you do this from React or Vue, the HTML is not rendered. Therefore you need to implement authentication and the setting of cookies yourself. And of course you need to authenticate from React in the same session as you later use for accessing the web-sockets.
I use the following Django-views to authenticate from the frontend:
#api_view()
#permission_classes([AllowAny])
#ensure_csrf_cookie
#csrf_exempt
def session_info(request):
"""
View to retrieve user info for current user. (Can be adapted to your needs). If user is not logged in, view will
still return CSRF cookie which in neccessary for authentication.
"""
if not request.user.is_authenticated:
return Response({"message": "Not authenticated.", "authenticated": False})
return Response(
{"message": "Authenticated.", "authenticated": True, "user": str(request.user)}
)
#api_view(['POST'])
#permission_classes([AllowAny])
def session_auth(request):
"""
Login-view.
"""
username = request.data['username']
password = request.data['password']
user = authenticate(request, username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
request.session['authenticated_user'] = user.username
return Response(
{
"message": "Authenticated.",
"authenticated": True,
"name": user.name,
}
)
return Response({"message": "Not authenticated", "authenticated": False})
In your urls.py you need to add something like the following:
urlpatterns = [
path(
'session/',
views.session_info,
name='session',
),
path(
'sessionauth/',
views.session_auth,
name='sessionauth',
),
]
Now you can from your frontend do something like this (the following is my javascript/Vue-code, slightly adapted for this post, but you can probably do something similar with React):
// Please note: this code can probably be improved a lot as my skills in javascript are not the best
login: ({ username, password }) => {
window.$cookies.set("username", username);
const session_url = `${BACKEND_URL}/api/v1/session/`;
const url = `${BACKEND_URL}/api/v1/sessionauth/`;
axios.get(session_url).then(response => {
axios.defaults.headers.common["Authorization"] = "";
// NOTE: the CSRF-cookie need to be included in subsequent POSTs:
axios.defaults.headers.post["X-CSRF-Token"] = response.data._csrf;
axios
.post(url, { username: username, password: password })
.then(response => {
axios.get(session_url);
})
.catch(e => {
console.log("ERROR: ", e);
commit("SET_LOGIN_ERROR", e);
});
});
I hope this is of some help. If not, let me know.
Let me just add this in case it might help someone,
You always get AnonymousUser because probably you are not passing the cookie header right.
with session authentcation, pass the session header like this.
Cookie: csrftoken=wBD7Qri09dyAR8oMY8fuL1nqCOvGGmaO; sessionid=hpb8xx873holf3zl01wfe6f7511bhxqi
You might try to do
Set-Cookie: sessionid=hpb8xx873holf3zl01wfe6f7511bhxqi;
Set-Cookie: csrftoken=wBD7Qri09dyAR8oMY8fuL1nqCOvGGmaO;
That won't work.

Understanding django channels - QueryAuthMiddleware

How to write custom authentication of user, that connects to chat over ws:// protocol? This user is on the other side of Django app, he is the mobile user, connecting websocket via ws:// from mobile app. I tried to test websocket with chrome extenshion, it couldn't connect to my websocket. I think it was because of authentication.
In Django channels docs it says:
If you have a custom authentication scheme, you can write a custom middleware to parse the details and put a user object (or whatever other object you need) into your scope.Middleware is written as a callable that takes an ASGI application and wraps it to return another ASGI application. Most authentication can just be done on the scope, so all you need to do is override the initial constructor that takes a scope, rather than the event-running coroutine.
Here’s a simple example of a middleware that just takes a user ID out of the query string and uses that:
The same principles can be applied to authenticate over non-HTTP protocols; for example, you might want to use someone’s chat username from a chat protocol to turn it into a user.
from django.db import close_old_connections
class QueryAuthMiddleware:
def __init__(self, inner):
# Store the ASGI application we were passed
self.inner = inner
def __call__(self, scope):
# Look up user from query string (you should also do things like
# check it's a valid user ID, or if scope["user"] is already populated)
user = User.objects.get(id=int(scope["query_string"]))
close_old_connections()
# Return the inner application directly and let it run everything else
return self.inner(dict(scope, user=user))
What query do I need to do? I don't know anything about that user, in fact its anonymous.
Help me, please.
In this example code, you probably have to open the websocket connection with:
ws://SERVER:PORT/PATH?1
Everything after the ? is the query string. In your example code, your query_string has to be a user id, so for example 1.
You could change the code to use different query strings. For example you could use:
from urllib.parse import parse_qs
from django.db import close_old_connections
class QueryAuthMiddleware:
def __init__(self, inner):
# Store the ASGI application we were passed
self.inner = inner
def __call__(self, scope):
# Look up user from query string (you should also do things like
# check it's a valid user ID, or if scope["user"] is already populated)
query_string = parse_qs(self.scope['query_string'])
if b'user_id' in query_string:
user = User.objects.get(id=int(query_string[b'user_id'][0]))
close_old_connections()
else:
user = AnonymousUser
# Return the inner application directly and let it run everything else
return self.inner(dict(scope, user=user))
Now, you can use this uri:
ws://SERVER:PORT/PATH?user_id=1
You sill have to make sure, that the user with the ID exists in the database. You also have to write actual auth code. Every user can connect to this application with a arbitrary user id. There is no password or auth-token required.
I had the same issue as well, did research a bit came across solution with the code snippet below:
let's say you have User class defined. You want authenticate user through query send query while establishing connection with ws.
I will pass channels installation and configuration let's say you have successfully installed channels and configured.
QueryAuthMiddleware class as shown below:
from channels.auth import AuthMiddlewareStack
from django.conf import LazySettings
from urllib import parse
from rest_socket.models import User
from urllib.parse import parse_qs
from urllib.parse import unquote, urlparse
from channels.auth import AuthMiddlewareStack
from django.contrib.auth.models import AnonymousUser
class QueryAuthMiddleware:
"""
QueryAuthMiddleware authorization
"""
def __init__(self, inner):
self.inner = inner
def __call__(self, scope):
query_string = parse_qs(scope['query_string']) #Used for query string token url auth
headers = dict(scope['headers']) #Used for headers token url auth
print("query", query_string)
if b'user' in query_string:
try:
user = query_string[b'user'][0].decode()
print("user", user)
existing = User.objects.filter(id=user).last()
print(existing)
if existing:
print("existinguser")
scope['user'] = existing
else:
scope["user"] ="no user found"
except User.DoesNotExist:
pass
return self.inner(scope)
QueryAuthMiddlewareStack = lambda inner: QueryAuthMiddleware(AuthMiddlewareStack(inner))
Your routing.py should be like this:
from channels.security.websocket import AllowedHostsOriginValidator
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import OriginValidator
from django.urls import path
import your_app.routing
from project.utils.auth import QueryAuthMiddlewareStack
application = ProtocolTypeRouter({
# (http->django views is added by default)
'websocket': QueryAuthMiddlewareStack(
URLRouter(
your_app.routing.websocket_urlpatterns
)
),
})
Routing inside application:
websocket_urlpatterns = [
path("tasks/", consumers.Task)
]
and your ws connection to django-channels:
<script>
var notification;
if (location.protocol === 'https:') {
notification = new WebSocket('wss://' + "window.location.host" + "/tasks"+ "/?user=id");
console.log("with htpps")
}
else {
notification = new WebSocket('ws://' + window.location.host + "/tasks"+ "/?userr=id");
console.log("htpp")
}
notification.onopen = function open() {
console.log('notification connection created for NotificationWebsocket.');
};
notification.onmessage = function message(event) {
var data = JSON.parse(event.data);
console.log("Socket response from NotificationWebsocket => ", data);
};
if (notification.readyState === WebSocket.OPEN) {
notification.onopen();
}
</script>

Why is request object not the same between a "native" view and a ModelViewSet?

I am building an API with Django Rest Framework (DRF) and enabled the authentication/registration through social apps.
For authenticating users via their social accounts I use Django rest-framework Social Oauth2 and it works like a charm. To be sure my user is logged in I created a very simple view in the views.py of my app:
def index(request):
return HttpResponse("is_anonymous: %s" % request.user.is_anonymous)
The result in the browser is the following (it means that the user is logged in):
is_anonymous: False
Now as I am building an API with DRF I may need to retrieve some data of the current user (from request.user) in one of my viewsets but in the following code, the result is not what I expected:
class HelloViewSet(viewsets.ModelViewSet):
queryset = Hello.objects.all()
serializer_class = HelloSerializer
# This is just a random ViewSet, what is
# important is the custom view below
#action(detail=False)
def test(self, request):
return Response(request.user.is_anonymous)
Here the result shows that the user not logged in:
True
So the first view shows that request.user.is_anonymous = False and the second shows that request.user.is_anonymous = True. Both views are in the same file views.py in the same app.
What do I miss here? We are not supposed to get the user instance in an API REST?
I suppose this is because your first view is pure Django and it's not using DRF's DEFAULT_AUTHENTICATION_CLASSES. To enable it, you can add #api_view decorator:
from rest_framework.decorators import api_view
from rest_framework.response import Response
#api_view()
def index(request):
return Response("is_anonymous: %s" % request.user.is_anonymous)
Also you should update DEFAULT_AUTHENTICATION_CLASSES to enable OAuth, like this:
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': (
...
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
'rest_framework_social_oauth2.authentication.SocialAuthentication',
),
}
As neverwalkaloner mentioned in the in the comments, the problem was that I didn't pass any access_token in the header via Authorization: Bearer <token>, so of course the server wasn't able to identify the "logged" user that was making the request. Using curl (or Postman) I could add this token for checking purpose and it worked.

python-social-auth not getting correct Google OAuth2 details

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)