I am trying to integrate Social Authentication in my DRF backend. I decided to go with python-social-auth. If I serve my social login through Django (and an HTML view), I can see my login in successful. I also figured that I can redirect after successful social authentication as outlined here.
Until now, my frontend (a Nuxt app) was using DRF tokens. Even though:
I can create valid tokens for social accounts.
My frontend can redirect the users to -> complete authorization with OAuth sites e.g. Google, Twitter -> return back to the frontend.
Is it possible for me to somehow manage to redirect a successfully authenticated user to the frontend with the associated and valid DRF token as a query parameter?
You can, it's not trivial, tho. It's possible because the mechanism to retrieve URLs (success or errors ones) is delegated to strategies setting() method, which in the end invokes get_setting() on final implementations. This method you can override to add your custom logic.
These steps should get you on the road:
Define a custom strategy
from social_django.strategy import DjangoStrategy
class CustomStrategy(DjangoStrategy):
def get_setting(self, name):
if name == 'LOGIN_REDIRECT_URL':
token = get_drf_token()
return f'/?toke={token}'
else:
return super().get_setting(name)
Point your SOCIAL_AUTH_STRATEGY settings to this new strategy (import path syntax)
Besides using a custom strategy, which I believe is more the library's style, there is another explicit solution:
Since python-social-auth allows redirection after successful authentication, open another endpoint that will a. generate a token b. redirect to the frontend. So I defined follow api_view:
#api_view(http_method_names=['get'])
def user_token(request):
if request.user.is_authenticated:
token, e = Token.objects.get_or_create(user=request.user)
return redirect('<front-end>/?token='+token.key)
else:
return redirect(`<front-end>/login/?error=true')
Now a potential authentication flow is like this:
User visits front-end -> Click 'login using Github et.al.' -> Login at the third party (say, Github) -> Back to backend /success/ -> redirect to front-end url (based on the above view) with the token -> Handle the query parameter on the frontend (which is pretty trivial in my case where I am using Nuxt).
If we mount user_token at path /success/token/, then once the user visits: http:<backend>:/login/github/?next=/success/token/, every right step on users part will take him/her to the front-end with the query param token set as the right token.
UPDATE: This will only work if DRF has session authentication active, otherwise request.user.is_authenticated can never be true.
Related
I'm building an API using Django Rest Framework. Later this API is supposed to be consumed by iOS and Android devices. I want to allow my users to sign-up with oauth2-providers like Facebook and Google. In this case, they shouldn't have to create an account with my platform at all. But users should also be able to sign-up when not having a Facebook/Google account, for which I'm using django-oauth-toolkit, so I have my own oauth2-provider.
For external providers I'm using python-social-auth, which works fine and automatically creates the user objects.
I want the clients to authenticate by using bearer tokens, which works fine for users that signed up with my provider (django-oauth-toolkit provides authentication scheme and permission classes for Django REST Framework).
However, python-social-auth only implements session based authentication, so there is no straightforward way to make authenticated API requests on behalf of users that registered by an external oauth2 provider.
If I use an access_token that has been generated by django-oauth-toolkit, doing a request like this works:
curl -v -H "Authorization: Bearer <token_generated_by_django-oauth-toolkit>" http://localhost:8000/api/
However, the following doesn't work since there is no corresponding authentication scheme for Django REST Framework and the AUTHENTICATION_BACKENDS provided by python-social-auth only work for session-based authentication:
curl -v -H "Authorization: Bearer <token_stored_by_python-social-auth>" http://localhost:8000/api/
Using the browseable API provided by Django REST Framework after authenticating with python-social-auth works just fine, only API calls without a session cookie don't work.
I'm wondering what the best approach is for this problem. The way I see it, I have basically two options:
A: When a user signs up with an external oauth2 provider (handled by python-social-auth), hook into the process to create an oauth2_provider.models.AccessToken and continue to use 'oauth2_provider.ext.rest_framework.OAuth2Authentication', now authenticating also users that registered with an external provider. This approach is suggested here:
https://groups.google.com/d/msg/django-rest-framework/ACKx1kY7kZM/YPWFA2DP9LwJ
B: Use python-social-auth for API request authentication. I could get my own users into python-social-auth by writing a custom backend and using register_by_access_token. However, since API calls cannot utilize Django sessions this would mean I would have to write an authentication scheme for Django Rest Framework that utilizes the data stored by python-social-auth. Some pointers on how to do this can be found here:
http://psa.matiasaguirre.net/docs/use_cases.html#signup-by-oauth-access-token
http://blog.wizer.fr/2013/11/angularjs-facebook-with-a-django-rest-api/
http://cbdev.blogspot.it/2014/02/facebook-login-with-angularjs-django.html
However, the way I understand it python-social-auth only verifies the token when doing a login and relies on the Django session afterwards. This would mean I would have to find a way to prevent python-social-auth from doing the whole oauth2-flow for each stateless API request and rather check against the data stored in the DB, which isn't really optimized for querying since it's stored as JSON (I could use UserSocialAuth.objects.get(extra_data__contains=) though).
I would also have to take care of verifying the scopes of an access token and use them to check permissions, something django-oauth-toolkit already does (TokenHasScope, required_scopes etc).
At the moment, I'm leaning towards using option A, since django-oauth-toolkit provides good integration with Django Rest Framework and I get everything I need out of the box. The only drawback is that I have to "inject" the access_tokens retrieved by python-social-auth into the AccessToken model of django-oauth-toolkit, which feels wrong somehow, but would probably be by far the easiest approach.
Does anybody have any objections on doing that or has maybe tackled the same problem in a different way? Am I missing something obvious and making my life harder than necessary?
If anybody has already integrated django-oauth-toolkit with python-social-auth and external oauth2 providers I would be very thankful for some pointers or opinions.
A lot of the difficulty in implementing OAuth comes down to understanding how the authorization flow is supposed to work. This is mostly because this is the "starting point" for logging in, and when working with a third-party backend (using something like Python Social Auth) you are actually doing this twice: once for your API and once for the third-party API.
Authorizing requests using your API and a third-party backend
The authentication process that you need is go through is:
Mobile App -> Your API : Authorization redirect
Your API -> Django Login : Displays login page
Django Login -> Facebook : User signs in
Facebook -> Django Login : User authorizes your API
Django Login -> Your API : User signs in
Your API -> Mobile App : User authorizes mobile app
I'm using "Facebook" as the third-party backend here, but the process is the same for any backend.
From the perspective of your mobile app, you are only redirecting to the /authorize url provided by Django OAuth Toolkit. From there, the mobile app waits until the callback url is reached, just like in the standard OAuth authorization flow. Almost everything else (Django login, social login, etc.) is handled by either Django OAuth Toolkit or Python Social Auth in the background.
This will also be compatible with pretty much any OAuth libraries that you use, and the authorization flow will work the same no matter what third party backend is used. It will even handle the (common) case where you need to be able to support Django's authentication backend (email/username and password) as well as a third-party login.
Mobile App -> Your API : Authorization redirect
Your API -> Django Login : Displays login page
Django Login -> Your API : User signs in
Your API -> Mobile App : User authorizes mobile app
What's also important to note here is that the mobile app (which could be any OAuth client) never receives the Facebook/third-party OAuth tokens. This is incredibly important, as it makes sure your API acts as an intermediary between the OAuth client and you user's social accounts.
Mobile App -> Your API : Authorization redirect
Your API -> Mobile App : Receives OAuth token
Mobile App -> Your API : Requests the display name
Your API -> Facebook : Requests the full name
Facebook -> Your API : Sends back the full name
Your API -> Mobile App : Send back a display name
Otherwise, the OAuth client would be able to bypass your API and make requests on your behalf to the third-party APIs.
Mobile App -> Your API : Authorization redirect
Your API -> Mobile App : Receives Facebook token
Mobile App -> Facebook : Requests all of the followers
Facebook -> Mobile App : Sends any requested data
You'll notice that at this point you would have lost all control over the third-party tokens. This is especially dangerous because most tokens can access a wide range of data, which opens the door to abuse and eventually goes down under your name. Most likely, those logging into your API/website did not intend on sharing their social information with the OAuth client, and were instead expecting you to keep that information private (as much as possible), but instead you are exposing that information to everyone.
Authenticating requests to your API
When the mobile application then uses your OAuth token to make requests to your API, all of the authentication happens through Django OAuth Toolkit (or your OAuth provider) in the background. All you see is that there is a User associated with your request.
Mobile App -> Your API : Sends request with OAuth token
Your API -> Django OAuth Toolkit : Verifies the token
Django OAuth Toolkit -> Your API : Returns the user who is authenticated
Your API -> Mobile App : Sends requested data back
This is important, because after the authorization stage it shouldn't make a difference if the user is coming from Facebook or Django's authentication system. Your API just needs a User to work with, and your OAuth provider should be able to handle the authentication and verification of the token.
This isn't much different from how Django REST framework authenticates the user when using session-backed authentication.
Web Browser -> Your API : Sends session cookie
Your API -> Django : Verifies session token
Django -> Your API : Returns session data
Your API -> Django : Verifies the user session
Django -> Your API : Returns the logged in user
Your API -> Web Browser : Returns the requested data
Again, all of this is handled by Django OAuth Toolkit and does not require extra work to implement.
Working with a native SDK
In most cases, you are going to be authenticating the user through your own website and using Python Social Auth to handle everything. But the one notable exception is when using a native SDK, as authentication and authorization is handled through the native system, which means you are bypassing your API entirely. This is great for applications which need to sign in with a third party, or applications which don't use your API at all, but it's a nightmare when both come together.
This is because your server can't validate the login and is forced to assume that the login is valid and genuine, which means it bypasses any and all security that Python Social Auth gives you.
Mobile App -> Facebook SDK : Opens the authorization prompt
Facebook SDK -> Mobile App : Gets the Facebook token
Mobile App -> Your API : Sends the Facebook token for authorization
Your API -> Django Login : Tries to validate the token
Django Login -> Your API : Returns a matching user
Your API -> Mobile App : Sends back an OAuth token for the user
You'll notice that this skips over your API during the authentication phase, and then forces your API to make assumptions about the token that is passed in. But there are definitely cases where this risk may be worth it, so you should evaluate that before throwing it out. It's a trade off between quick and native logins for your user and potentially handling bad or malicious tokens.
I solved it by using your A. option.
What I do is registering users that use a third party to sign up by their third party access token.
url(r'^register-by-token/(?P<backend>[^/]+)/$',
views.register_by_access_token),
This way, I can issue a GET request like this one:
GET http://localhost:8000/register-by-token/facebook/?access_token=123456
And register_by_access_token gets called. request.backend.do_auth will query the provider for the user info from the token and magically register a user account with the info or sign in the user if he's already registered.
Then, I create a token manually and return it as JSON for letting the client query my API.
from oauthlib.common import generate_token
...
#psa('social:complete')
def register_by_access_token(request, backend):
# This view expects an access_token GET parameter, if it's needed,
# request.backend and request.strategy will be loaded with the current
# backend and strategy.
third_party_token = request.GET.get('access_token')
user = request.backend.do_auth(third_party_token)
if user:
login(request, user)
# We get our app!
app = Application.objects.get(name="myapp")
# We delete the old token
try:
old = AccessToken.objects.get(user=user, application=app)
except:
pass
else:
old.delete()
# We create a new one
my_token = generate_token()
# We create the access token
# (we could create a refresh token too the same way)
AccessToken.objects.create(user=user,
application=app,
expires=now() + timedelta(days=365),
token=my_token)
return "OK" # you can return your token as JSON here
else:
return "ERROR"
I'm just not sure about the way I generate the token, is this good practice? Well, in the mean time, it works!!
Maybe django-rest-framework-social-oauth2 is what you're looking for. This package depends on python-social-auth and django-oauth-toolkit, which you already use. I quickly scanned through the documentation, and it seems to implement just what you are trying to do.
I was doing React Native with expo and Django with Django REST framework. This blogpost ended being the way I solved registration (signup) with facebook https://medium.com/#gabriel_gamil/react-native-expo-django-facebook-authentication-sign-in-83625c49da7
tldr; use django-rest-auth https://django-rest-auth.readthedocs.io/en/latest/index.html
use Django-allauth https://django-allauth.readthedocs.io/en/latest/
I have an AngularJS Single Page Application that uses a Django backend API based on the Django Rest Framework. The API is protected via django-rest-framework-jwt. I would like to use django-allauth for account management and authentication on the server side.
I am just missing one single piece in the flow: How does my Oauth-Token from the client get transferred into a JWT-token? Basically, I would like to do as described here http://blog.wizer.fr/2013/11/angularjs-facebook-with-a-django-rest-api/ based on python-social-auth.
So my question is, how do I implement the ObtainAuthToken class from the link with django-allauth?
There are usually two login flows with social login: client-side ("Javascript SDK") and server-side. If your server needs to be authorised, it's usually a lot easier to go through the server-side flow. And that's also what all-auth does I think (and you didn't mention you use a frontend library like the blogpost you mentioned does).
Now the challenge is to provide the token from the server to the frontend. You would probably load the token in the HTML of the initialisation of the SPA, and then from Angular save the token client side (cookie, localStorage, etc.) so the session isn't lost on a refresh.
If you don't want the user to leave your app, you can open your /accounts/login/ or /accounts/signup/ url in a new window. In that new window they authorise your app, and your server receives the token upon return. There, you will have to generate a JWT token manually, and render that into the template so that javascript can access it. With js in that popup window, you can then communicate with your app that opened the popup and pass it the token – see this SO answer for an example – so it can save it.
Django-allauth provides signals that let you hook into the social login process. In your case, I would recommend subscribing to the allauth.socialaccount.signals.pre_social_login signal. The code will look something like this:
from allauth.socialaccount.signals import pre_social_login
#receiver(pre_social_login)
def create_jwt_token(sender, request, sociallogin, **kwargs):
# dig into the sociallogin object to find the new access token.
We used hello.js for O-Auth at the company I worked at.
You provide a shim on the Python end and get the refresh token and whatever other data needed once the user connects their social account.
We redirect them via Django to the page they attempted to access from their OAuth provider's page.
Each user still has their own email account which is needed for the JWT, but you could assume that whatever email is in the scope of their social account is their email then use django's functionality to create new users: User.objects.create(email=emailStringFromOauthData) etc.
I used Django Tastypie to build my api and i'm thinking in the correct way to create a login form so the user can login in the application, right now what I do is send a GET request with username/password he submited in the form as filtering options, but i'm pretty sure thats not secure at all. How can i do the same using POST request?
When i open the console with firebug:
GET URL/app/api/v1/user/?email=USER&password=PASS
Api-Auth and Content-Type are on the header.
#leosilvano
Don't handle user authentication and authorization using angular js not that its impossible but just that its not too secure and implementing also take some effort when Django's provides something which far easy than this.
I happen to be using django + angularjs + tastypie (REST API ). If you like take a look at my way of implementation.
Include your index.html of the angularJs in your templates ( Django Templates ) and place your directives, controllers, js, css and etc in the static folder ( Django Static ). Make your API calls after the auth processes. This will work seamlessly and you will run into less issues as well.
Reasons:
user_auth models becomes so handy while registering and logging in using templates and you don't need to sweat trying to write your own authentication which i'm sure you have to do when you go with Angular Js login auth implementation.
Use of decorators like for a view lets say "profile" if you need to check if the user is logged in all you need to do it something like the following
#login_required(login_url='/login/')
def profile(request):
return render_to_response('profile.html')
Passing password through "GET" is bad .. and passing auth values through "POST" and getting it via JSON .. is also not a good idea. Because you will be susceptible to middle-man attacks ..
Remember you have to take measures for CORS requests when using Angularjs for login since anyone can view the json response and they will be able to reproduce the same structure. Implementing Perm-Mixins and Groups is way more easier when using Django templates.
Handling exceptions like 404 or if you want to handle only post requests and thereby take user to a custom page ( actions like redirect ) becomes difficult. I am aware of SPA's but still if there happens to be redirection .. in my case i needed to redirect to another site. Following shows how simple it can be achieved including http statuses.
if request.method != 'POST':
return HttpResponseNotFound(render_to_response('404.html', { 'message' : 'Only POST Requests are allowed for authentication process.', 'baseurl' :request.build_absolute_uri('/').rstrip('/')}))
Solution:
Use Angular Js and REST (Tastypie) interaction to happen after you login. Use Django template for login authorization. Make use of the django modules .. it saves a lot of time.
If you still want to login using REST API .. by send post to django .. please take a look at the following post
How can I login to django using tastypie
You can POST data using the $http module
Just do:
$http.post(url, data)
Always send authentication data through https or your password will be sent in clear text.
Why does Django REST Framework implement a different Authentication mechanism than the built-in Django mechanism?
To wit, there are two settings classes that one can configure:
settings.AUTHENTICATION_BACKENDS which handles the Django-level authentication, and
settings.REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] which authenticates at the REST-Framework level
The problem I'm experiencing is that I have a Middleware layer which checks whether a user is logged-in or not.
When using a web client which authenticates via sessions, this works fine. However, from mobile or when running the test suite (i.e. authenticating using HTTP headers and tokens), the middleware detects the user as an AnonymousUser, but by the time we get to the REST Framework layer, the HTTP Authorization header is read, and the user is logged-in.
Why do these not both happen BEFORE the middleware? Furthermore, why doesn't REST Framework's authentication methods not rely on the Django authentication backend?
Django Rest Framework does not perform authentication in middleware by default for the same reason that Django does not perform authentication in middleware by default: middleware applies to ALL views, and is overkill when you only want to authenticate access to a small portion of your views. Also, having the ability to provide different authentication methods for different API endpoints is a very handy feature.
Rest Framework's authentication methods do not rely on the Django authentication backend because the Django's backend is optimised for the common case, and is intimitely linked to the user model. Rest Framework aims to make it easy to:
Use many different authentication methods. (You want HMAC based authentication? done! This is not possible with django auth framework)
Serve API data without ever needing a database behind it. (You have a redis database with all your data in-memory? Serve it in milliseconds without ever waiting for a round trip to DB user model.)
Thomas' answer explains the why quite well. My answer deals more with what you can do about it. I also have a middleware class that determines the current user. I need this because we have some automatic processes (cron) and I want to attribute these to a super user, and I have some model functions that don't have access to the request object but need to know who the current user is for various functions. So this created some issues as you noticed when we added an API via the REST framework. Here's my solution (yours will need to be adjusted to your use case):
from threading import local
_user = local() # thread-safe storage for current user used by middleware below
class CurrentUserMiddleware(object):
""" Defines Middleware for storing the currently active user """
def process_request(self, request):
if not request:
# Clear the current user if also clearing the request.
_user.value = 1 # One represents automatic actions done by super admin
else:
_user.value = request.user
if not _user.value or not _user.value.is_authenticated:
try:
authenticator = TokenAuthentication()
auth = authenticator.authenticate(request)
if auth:
_user.value = auth[0]
except Exception as e:
pass
def process_response(self, request, response):
_user.value = None
return response
The authenticator bit deals with seeing if the request has been authenticated by the REST Framework and getting the user.
Are there basic authentication examples with Django and Tastypie?. I'm a little bit confused about how the authentication in Django works, specially with Tastypie.I wanna know how the authentication works with api keys and how to authenticate a user with the built-in User model which Django has. Any suggestion or code are really appreciated.
Thanks.
Just to answer your questions regarding authentication:
How the authentication in Django works?
Django authentication required SessionMiddleware to work. Once a session has been loaded, the Django authentication backend reads a special cookie _auth_user (IIRC) which contains currently logged in user's ID. If you have access to the django shell, you can manipulate it and make yourself logged in as any user! Once the backend notices there is a _auth_user key, it then adds a lazy User object to the request (so it delays the User.objects.get(...) until it is really needed). If there is no such key in the session dict, the user is claimed to be anonymous and an instance of AnonymousUser is added to the request object instead.
How does the authentication work in Tastypie?
Before your resource view is executed, a Resource.is_authenticated(request) method is called, which in turn calls the is_authenticated(request) method of the authentication backend of your the Resource of your choice. If the method returns False, the authentication is claimed to be failed and returns with Unauthorized error. If the method returns a HttpResponse, the response is returned instead. If the method returns True, the request is claimed to have been authenticated.
How does User model authentication work in Tastypie?
The User model authentication can be performed using SessionAuthentication backend provided by the Tastypie itself. What it does is creating a session for the current request so that the authentication middleware can then automatically insert relevant user model to the request. Notice that for this method to work, your API client has to support storing cookies and resending them in future requests.
You might find this useful. It allows you to authenticate the user based on the Django session cookie.
https://github.com/amezcua/TastyPie-DjangoCookie-Auth/blob/master/DjangoCookieAuth.py
I am using this in my application and it works!