Plug in django-allauth as endpoint in django-rest-framework - python

I'm using django-allauth on my website for social logins. I also have a REST API powered by django-rest-framework that serves as the backend of a mobile app. Is there a way I can directly plug in allauth's authentication backend to the REST api so that I can validate (and register) users who use Facebook login in the mobile app?
To clarify: The Facebook login part is handled by native SDKs. I need an endpoint that works like POST /user (that is, creates a new user), but takes Facebook oauth token as input instead of email/password etc.

You can use Django Rest Auth for this which depends on django-allauth. It's very easy to integrate.

You can use this libray for social authentication django-rest-framework-social-oauth2. Try this django-allauth related code
urls.py
urlpatterns = [
url(
r'^rest/facebook-login/$',
csrf_exempt(RestFacebookLogin.as_view()),
name='rest-facebook-login'
),
]
serializers.py
class EverybodyCanAuthentication(SessionAuthentication):
def authenticate(self, request):
return None
views.py
class RestFacebookLogin(APIView):
"""
Login or register a user based on an authentication token coming
from Facebook.
Returns user data including session id.
"""
# this is a public api!!!
permission_classes = (AllowAny,)
authentication_classes = (EverybodyCanAuthentication,)
def dispatch(self, *args, **kwargs):
return super(RestFacebookLogin, self).dispatch(*args, **kwargs)
def get(self, request, *args, **kwargs):
try:
original_request = request._request
auth_token = request.GET.get('auth_token', '')
# Find the token matching the passed Auth token
app = SocialApp.objects.get(provider='facebook')
fb_auth_token = SocialToken(app=app, token=auth_token)
# check token against facebook
login = fb_complete_login(original_request, app, fb_auth_token)
login.token = fb_auth_token
login.state = SocialLogin.state_from_request(original_request)
# add or update the user into users table
complete_social_login(original_request, login)
# Create or fetch the session id for this user
token, _ = Token.objects.get_or_create(user=original_request.user)
# if we get here we've succeeded
data = {
'username': original_request.user.username,
'objectId': original_request.user.pk,
'firstName': original_request.user.first_name,
'lastName': original_request.user.last_name,
'sessionToken': token.key,
'email': original_request.user.email,
}
return Response(
status=200,
data=data
)
except:
return Response(status=401, data={
'detail': 'Bad Access Token',
})

While I'm not quite sure how to use allauth and rest-fremework together, allauth does not offer such an endpoint.
Suggestion: make your own that does a variation of the following:
Call allauth.socialaccount.providers.facebook.views.fb_complete_login(None, socialtoken) where socialtoken is as created in login_by_token. That performs (a few functions deeper) a django.contrib.auth.login, possibly creating the acct.
After that, for use on mobile devices, it might be possible to the the auth (not FB) token: get the user data (from session?), and call rest_framework.authtoken.views.obtain_auth_token
Notes:
1. This offers no way to resolve email conflicts or connect social/local accts.
2. I haven't tried it - please post code if you can get it working.

You could use djoser but I don't know how it cooperates with allauth:
https://github.com/sunscrapers/djoser

Related

using DRF with an other authentication backend

i am using keycloak as IAM for my project which is a multi service project.
i want to use django rest framework for my django service.
i have managed to make authentication work and request.user returns an instance of User from django.contrib.auth.models, the problem is drf creates a new request object for the view, and the user authenticated by keycloak backend set in AUTHENTICATION_BACKENDS is lost,
to solve this problem i have made a permission class like the following:
class ClientRoleBasedPermission(permissions.BasePermission):
role = "create_room"
def has_permission(self, request, view):
if self.role in request._request.user.get_all_permissions():
return True
return False
i am using this simple view to test things
class ListUsers(APIView):
"""
View to list all users in the system.
* Requires token authentication.
* Only admin users are able to access this view.
"""
permission_classes = [CreateRoomPermission]
permission = "create_room"
def get(self, request, format=None):
"""
Return a list of all users.
"""
usernames = [user.username for user in User.objects.all()]
return Response(usernames)
authentication backend i am using :KeycloakAuthorizationCodeBackend
is there a better way to solve this issue ?

Django register user & CSRF token

I am beginner to django. I just want to post and register user. If I do that, I got error "CSRF Failed: CSRF token missing or incorrect." My apiview is like this.
class RegistrationView(APIView):
""" Allow registration of new users. """
permission_classes = (permissions.AllowAny,)
def post(self, request):
serializer = RegistrationSerializer(data=request.DATA)
# Check format and unique constraint
if not serializer.is_valid():
return Response(serializer.errors,\
status=status.HTTP_400_BAD_REQUEST)
data = serializer.data
# u = User.objects.create_user(username=data['username'],
# email=data['email'],
# password='password')
u = User.objects.create(username=data['username'])
u.set_password(data['password'])
u.save()
# Create OAuth2 client
name = u.username
client = Client(user=u, name=name, url='' + name,\
client_id=name, client_secret='', client_type=1)
client.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
I have read through about CSRF token.
How to make a POST simple JSON using Django REST Framework? CSRF token missing or incorrect
In my setting.py, it is like this. Do I need to change in global level? or where shall I modify so that I won't have CSRF token error when I register new user?
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES':
('rest_framework.authentication.OAuth2Authentication',
'rest_framework.authentication.SessionAuthentication'),
'DEFAULT_MODEL_SERIALIZER_CLASS':
'rest_framework.serializers.ModelSerializer',
'DEFAULT_PERMISSION_CLASSES':
('rest_framework.permissions.IsAdminUser',)
}
If you are using SessionAuthentication in Django REST framework then you need to supply the CSRF token in your POST. Take a look at this link Working with AJAX, CSRF & CORS.
Also take a look at the example javascript from the Django docs.

Is it possible to use Flask-Login's authentication as simple HTTP auth for a REST API?

Right now, my webapp has a full auth system implemented through Flask-Login. But say that I have this controller:
#app.route("/search_query")
#login_required
def search_query():
query = request.args.get("query")
return jsonify(search(query))
This works great when you login to the page and make searches against it, if you're authenticated it goes through, otherwise it routes you back to the login page (through an unauthorized_handler function).
#lm.unauthorized_handler
def unauthorized():
return redirect(url_for("login"))
The problem is, I want to be able to use some form of simple HTTP authentication for this without having two separate auth systems so that I can make REST API calls against it. In short, I want to avoid having to use both Flask-Login and HTTPBasicAuth, and just use Flask-Login to achieve simple authentication, is this possible through Flask-Login?
Use the flask-login's request_loader to load from request auth header. You don't need to implement the decorator yourself, and current_user will point to the user loaded.
Example:
login_manager = LoginManager()
#login_manager.request_loader
def load_user_from_header():
auth = request.authorization
if not auth:
return None
user = User.verify_auth(auth.username, auth.password):
if not user:
abort(401)
return user
With that, you can mark a view as login required by:
#app.route('/user/info')
#login_required
def user_info():
return render_template('user_info.html', current_user.name)
If you looking for users to authenticate and make REST API calls they are going to have to send their credentials with every api call.
What you could do is a new decorator that checks for the auth header and authenticate the user.
Please refer to the entire app that I've posted on codeshare for more info:
http://codeshare.io/bByyF
Example using basic auth:
def load_user_from_request(f):
#wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if current_user.is_authenticated:
# allow user through
return f(*args, **kwargs)
if auth and check_auth(auth.username, auth.password):
return f(*args, **kwargs)
else:
return bad_auth()
return decorated
With that code you can now decorate your views to check for the header.
#app.route("/search_query")
#load_user_from_request
def search_query():
query = request.args.get("query")
return jsonify(search(query))
you can curl my example app like this:
curl --user admin#admin.com:secret http://0.0.0.05000/success

Rest authentication with python social auth and Satellizer

I am trying to enable authentication in my single page angular app using Satellizer as client on frontend and python social auth on backend using django rest framework. The main problem is that I want to use JWT instead of session authentication.
I have tried passing the response I get after the user confirms the popup window to social/complete/backend (python social auth view) but with no luck.
In satellizer for google configuration I put:
$authProvider.google({
clientId:'609163425136-1i7b7jlr4j4hlqtnb1gk3al2kagavcjm.apps.googleusercontent.com',
url: 'social/complete/google-oauth2/',
optionalUrlParams: ['display', 'state'],
state: function() {
return Math.random();
}
});
The problems I encounter are in python social auth:
Missing needed parameter state
for google-oauth2
and
Missing needed parameter code
for facebook.
That is very strange to me because those parameters are present in the request payload and I can get then in my own custom view.
The closest I have come to a solution is writing my own view in which I can accept the parameter state normally.
This is my view which handles the response and creates a new jwt token using the django-rest-framework-jwt:
class SocialAuthView(APIView):
throttle_classes = ()
permission_classes = ()
authentication_classes = ()
social_serializer = SocialAuthSerializer
user_serializer = None
def post(self, request):
access_token_url = 'https://accounts.google.com/o/oauth2/token'
people_api_url = 'https://www.googleapis.com/plus/v1/people/me/openIdConnect'
from django.conf import settings
payload = dict(client_id=request.data['clientId'],
redirect_uri=request.data['redirectUri'],
client_secret=settings.SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET,
code=request.data['code'],
grant_type='authorization_code')
# Step 1. Exchange authorization code for access token.
import requests
import json
r = requests.post(access_token_url, data=payload)
token = json.loads(r.text)
headers = {'Authorization': 'Bearer {0}'.format(token['access_token'])}
# Step 2. Retrieve information about the current user.
r = requests.get(people_api_url, headers=headers)
profile = json.loads(r.text)
try:
user = User.objects.filter(email=profile['email'])[0]
except IndexError:
pass
import jwt
from rest_framework_jwt.utils import jwt_payload_handler, jwt_encode_handler
if user:
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return Response({'token': token.decode('unicode_escape')},
status=status.HTTP_200_OK)
u = User.objects.create(username=profile['name'], first_name=profile['given_name'],
last_name=profile['family_name'], email=profile['email'])
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return Response({'Bearer': token.decode('unicode_escape')},
status=status.HTTP_200_OK)
I mean, it works, but I don't get all the benefits that I would get if I could implement it with python social auth. Maybe there is a way to authenticate with python social auth calling some functions like do_auth from this view?
It amazes me how much I struggle with this problem.
All help is welcome.
tl,dr: How to implement angular single page application using Satellizer, django-rest-framework, python social auth and JWT?
Not quite an answer, but I don't have sufficient reputation to comment.
I've been trying to do something similar. Satellizer works well with DRF's djoser module for email/pass token auth.
As for the social aspect, I'm finding OAuth2 quite complicated. You might want to look at the following project (no relation to me), it seems to be trying to implement just that (angularjs-satellizer/psa/jwt):
https://github.com/hsr-ba-fs15-dat/opendatahub
specifically:
https://github.com/hsr-ba-fs15-dat/opendatahub/blob/master/src/main/python/authentication/views.py

Are sessions needed for python-social-auth

I'm building a django app with an API backend(built with DRF) and angularjs client. My goal is to completely decouple the server and client using JWT in place of sessions. I'm attempting to integrate python-social-auth(PSA) with django-rest-framework-jwt(DRFJWT), so my goal is to have an auth flow something to this:
User logs with Email/facebook via angular client -> client posts form to PSA's url -> PSA login/create user ->[!] DRFJWT creates token that it then sends back to client -> client stores token in local storage then uses token each request
[!]: This is currently where I'm struggling. My thinking is that I can modify the do_complete method in PSA like so
from rest_framework_jwt.utils import jwt_payload_handler, jwt_encode_handler
def do_complete(backend, login, user=None, redirect_name='next',
*args, **kwargs):
# pop redirect value before the session is trashed on login()
data = backend.strategy.request_data()
redirect_value = backend.strategy.session_get(redirect_name, '') or \
data.get(redirect_name, '')
is_authenticated = user_is_authenticated(user)
user = is_authenticated and user or None
partial = partial_pipeline_data(backend, user, *args, **kwargs)
if partial:
xargs, xkwargs = partial
user = backend.continue_pipeline(*xargs, **xkwargs)
else:
user = backend.complete(user=user, *args, **kwargs)
if user_is_active(user):
# catch is_new/social_user in case login() resets the instance
is_new = getattr(user, 'is_new', False)
social_user = user.social_user
login(backend, user, social_user)
payload = jwt_payload_handler(user)
return { 'token': jwt_encode_handler(payload) }
Is this the only way of doing what I'm trying to accomplish?
I'm also wondering if its okay from a best-practices standpoint to use sessions to manage the pipeline and JWT for auth?
I'm also using python-social-auth and django-rest-framework-jwt for user authentication.
The way I was able to integrate the two authentication systems together was by creating a custom view that takes in the 'access_token' provided by the oAuth provider and attempts to create a new user with it. Once the user is created, instead of returning the authenticated user/session I return the JWT token.
The following code snippets explain the solution.
Back-End
In my views.py file I included the following:
#psa()
def auth_by_token(request, backend):
"""Decorator that creates/authenticates a user with an access_token"""
token = request.DATA.get('access_token')
user = request.user
user = request.backend.do_auth(
access_token=request.DATA.get('access_token')
)
if user:
return user
else:
return None
class FacebookView(views.APIView):
"""View to authenticate users through Facebook."""
permission_classes = (permissions.AllowAny,)
def post(self, request, format=None):
auth_token = request.DATA.get('access_token', None)
backend = request.DATA.get('backend', None)
if auth_token and backend:
try:
# Try to authenticate the user using python-social-auth
user = auth_by_token(request, backend)
except Exception,e:
return Response({
'status': 'Bad request',
'message': 'Could not authenticate with the provided token.'
}, status=status.HTTP_400_BAD_REQUEST)
if user:
if not user.is_active:
return Response({
'status': 'Unauthorized',
'message': 'The user account is disabled.'
}, status=status.HTTP_401_UNAUTHORIZED)
# This is the part that differs from the normal python-social-auth implementation.
# Return the JWT instead.
# Get the JWT payload for the user.
payload = jwt_payload_handler(user)
# Include original issued at time for a brand new token,
# to allow token refresh
if api_settings.JWT_ALLOW_REFRESH:
payload['orig_iat'] = timegm(
datetime.utcnow().utctimetuple()
)
# Create the response object with the JWT payload.
response_data = {
'token': jwt_encode_handler(payload)
}
return Response(response_data)
else:
return Response({
'status': 'Bad request',
'message': 'Authentication could not be performed with received data.'
}, status=status.HTTP_400_BAD_REQUEST)
In my urls.py I included the following route:
urlpatterns = patterns('',
...
url(r'^api/v1/auth/facebook/', FacebookView.as_view()),
...
)
Front-End
Now that the backend authentication is wired up, you can use any frontend library to send the access_token and authenticate the user. In my case I used AngularJS.
In a controller file I call the API like so:
/**
* This function gets called after successfully getting the access_token from Facebook's API.
*/
function successLoginFbFn(response) {
var deferred = $q.defer();
$http.post('/api/v1/auth/facebook/', {
"access_token": response.authResponse.accessToken,
"backend": "facebook"
}).success(function(response, status, headers, config) {
// Success
if (response.token) {
// Save the token to localStorage and redirect the user to the front-page.
Authentication.setToken(response.token);
window.location = '/';
}
deferred.resolve(response, status, headers, config);
}).error(function(response, status, headers, config) {
// Error
console.error('Authentication error.');
deferred.reject(response, status, headers, config);
});
}
With this approach you can mix the two plugins. All sent tokens will be coming from django-rest-framework-jwt even though users can still authenticate themselves with the ones provided by sites such as Facebook, Google, Twitter, etc.
I only showed the approach to authenticate through Facebook, however you can follow a similar approach for other providers.
No, you do not need to use sessions(standard Django login system) with python-social-auth. What you need to make JWT and PSA work together is DRF.
Here's my solution:
I used standard PSA's url for making request too social /login/(?P<backend>[^/]+)/$, changed url in urls.py to match redirect from Facebook/Twitter to my own.
url(r'^complete/(?P<backend>[^/]+)/$', views.SocialAuthViewComplete.as_view()),
The point of using API is to have access to user data in request that PSA is doing. DRF allow you to do it if you have JWT authentication in DEFAULT_AUTHENTICATION_CLASSES
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
),}
In views.py
from social.apps.django_app.views import complete
class SocialAuthViewComplete(APIView):
permission_classes = ()
def post(self, request, backend, *args, **kwargs):
try:
#Wrap up PSA's `complete` method.
authentication = complete(request, backend, *args, **kwargs)
except Exception, e:
exc = {
'error': str(e)
}
return Response(exc, status=status.HTTP_400_BAD_REQUEST)
return Response({'data': authentication}, status=status.HTTP_202_ACCEPTED)
Then I modified the do_complete method in PSA:
def do_complete(backend, login, user=None, redirect_name='next',
*args, **kwargs):
# pop redirect value before the session is trashed on login()
data = backend.strategy.request_data()
redirect_value = backend.strategy.session_get(redirect_name, '') or \
data.get(redirect_name, '')
is_authenticated = user_is_authenticated(user)
user = is_authenticated and user or None
partial = partial_pipeline_data(backend, user, *args, **kwargs)
if partial:
xargs, xkwargs = partial
user = backend.continue_pipeline(*xargs, **xkwargs)
else:
user = backend.complete(user=user, *args, **kwargs)
user_model = backend.strategy.storage.user.user_model()
if user and not isinstance(user, user_model):
return user
if is_authenticated:
if not user:
information = 'setting_url(backend, redirect_value, LOGIN_REDIRECT_URL'
else:
information = 'setting_url(backend, redirect_value, NEW_ASSOCIATION_REDIRECT_URL,LOGIN_REDIRECT_URL'
elif user:
# Get the JWT payload for the user.
payload = jwt_payload_handler(user)
if user_is_active(user):
is_new = getattr(user, 'is_new', False)
if is_new:
information = 'setting_url(backend, NEW_USER_REDIRECT_URL, redirect_value, LOGIN_REDIRECT_URL'
else:
information = 'setting_url(backend, redirect_value, LOGIN_REDIRECT_URL'
else:
return Response({
'status': 'Unauthorized',
'message': 'The user account is disabled.'
}, status=status.HTTP_401_UNAUTHORIZED)
else:
information = 'setting_url(backend, LOGIN_ERROR_URL, LOGIN_URL'
return { 'an information i may use in future': information,
'token': jwt_encode_handler(payload) # Create the response object with the JWT payload.
}
I tried pipelines and user association and it works correctly.
Also you always can modify another method from PSA, if you need it to works with JWT.

Categories

Resources