Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Complete authentication through REST API #68

Closed
simonluijk opened this issue Oct 28, 2013 · 17 comments
Closed

Complete authentication through REST API #68

simonluijk opened this issue Oct 28, 2013 · 17 comments

Comments

@simonluijk
Copy link

I have a web client (Javascript) that authenticates users with G+ and Facebook. The client then needs to authenticate with a REST API implemented with django-rest-framework. I have seen the previous discussions implementing this with dsa. But could not get this working with psa. Do you have any pointers?

@omab
Copy link
Owner

omab commented Oct 28, 2013

Any error? This snipped proved to work:

from social.apps.django_app.utils import strategy

@strategy()
def register_by_access_token(request, backend):
    backend = request.strategy.backend
    user = request.user
    user = backend.do_auth(
        access_token=request.GET.get('access_token'),
        user=user.is_authenticated() and user or None
    )
    ...

Also an URL with this format is needed:

url(r'^register/(?P<backend>[^/]+)/', register_by_access_token)

@simonluijk
Copy link
Author

Thank you for your quick response. With a few tweaks from the snippet given this is the code I have:

class AuthViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
    queryset = User.objects.all()
    serializer_class = AuthSerializer

    def create(self, request):
        serializer = self.get_serializer(data=request.DATA,
                                         files=request.FILES)

        if serializer.is_valid():
            auth_token = serializer.data['token']
            backend = serializer.data['backend']

            strategy = load_strategy(request=request, backend=backend)
            user = strategy.backend.do_auth(
                access_token=auth_token,
                user=request.user.is_authenticated() and request.user or None
            )
            login(request, user)
            return Response({'status': 'Done'})

When there is no sessionid cookie, no exception is raised, the user is created, a sessionid cookie is created but the user is not logged in. Afterwards visiting a url that requires login or reauthenticating produces the following exception.

AttributeError at /api/accounts/auth/
'NoneType' object has no attribute 'get_user'

Request Method: POST
Request URL: http://localhost:8000/api/accounts/auth/
Django Version: 1.6c1
Python Version: 2.7.5
Server time: Tue, 29 Oct 2013 04:52:21 -0500
Installed Applications:
('django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.sites',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'django.contrib.admin',
 'south',
 'django_nose',
 'rest_framework',
 'social.apps.django_app.default')
Installed Middleware:
('django.middleware.common.CommonMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware')

Traceback:
File "/home/simon/.virtualenvs/django/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response
  114.                     response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/simon/.virtualenvs/django/lib/python2.7/site-packages/django/db/transaction.py" in inner
  339.                 return func(*args, **kwargs)
File "/home/simon/.virtualenvs/django/lib/python2.7/site-packages/rest_framework/viewsets.py" in view
  78.             return self.dispatch(request, *args, **kwargs)
File "/home/simon/.virtualenvs/django/lib/python2.7/site-packages/django/views/decorators/csrf.py" in wrapped_view
  57.         return view_func(*args, **kwargs)
File "/home/simon/.virtualenvs/django/lib/python2.7/site-packages/rest_framework/views.py" in dispatch
  399.             response = self.handle_exception(exc)
File "/home/simon/.virtualenvs/django/lib/python2.7/site-packages/rest_framework/views.py" in dispatch
  396.             response = handler(request, *args, **kwargs)
File "/home/simon/Dropbox/Projects/django/src/django/apps/accounts/views.py" in create
  46.                 user=request.user.is_authenticated() and request.user or None
File "/home/simon/.virtualenvs/django/lib/python2.7/site-packages/django/utils/functional.py" in inner
  213.             self._setup()
File "/home/simon/.virtualenvs/django/lib/python2.7/site-packages/django/utils/functional.py" in _setup
  298.         self._wrapped = self._setupfunc()
File "/home/simon/.virtualenvs/django/lib/python2.7/site-packages/django/contrib/auth/middleware.py" in <lambda>
  18.         request.user = SimpleLazyObject(lambda: get_user(request))
File "/home/simon/.virtualenvs/django/lib/python2.7/site-packages/django/contrib/auth/middleware.py" in get_user
  10.         request._cached_user = auth.get_user(request)
File "/home/simon/.virtualenvs/django/lib/python2.7/site-packages/django/contrib/auth/__init__.py" in get_user
  137.         user = backend.get_user(user_id) or AnonymousUser()
File "/home/simon/.virtualenvs/django/lib/python2.7/site-packages/social/backends/base.py" in get_user
  149.         return self.strategy.get_user(user_id)

Exception Type: AttributeError at /api/accounts/auth/
Exception Value: 'NoneType' object has no attribute 'get_user'

It behaves the same using the view you provided above. When visiting /login/facebook/ all works as expected.

@omab
Copy link
Owner

omab commented Oct 29, 2013

@simonluijk, instead of calling login(request, user) try with _do_login(strategy, user) which can be imported with from social.apps.django_app.views import _do_login.

@simonluijk
Copy link
Author

Thanks that has solved my issue.

@rodrigogadea
Copy link

I had the same issue - although a slightly different implementation in the views:

# For login with a Facebook account, decorators seems not play well with CBVs...
@strategy()
def auth_by_fb_token(request, backend):
    backend = request.strategy.backend

    try:
        user = backend.do_auth(
            access_token=request.GET.get('access_token')
            )
    except Exception as err:
        print err
        user = None

    if user and user.is_active:
        return user# Return anything that makes sense here
    else:
        return None


class LoginWithFacebook(views.APIView):
    permission_classes = (permissions.AllowAny,)
    authentication_classes = (UnsafeSessionAuthentication,)

    def get(self, request, backend, *args, **kwargs):
        uid = request.GET.get("uid", None)
        if not uid:
            return Response("No UID provided", status=400)

        user = auth_by_fb_token(request, backend)
        if user:
            #login(request, user)
            strategy = load_strategy(request=request, backend=backend)
            _do_login(strategy, user)
            return Response("Login Successful!")
        else:
            return Response("Bad Credentials, check the Token and/or the UID", status=403)

And using _do_login() totally solved the problem!

I'm astonished about the quality of the code of python-social-auth and how well it integrates (other packages are way too invasive) and how good it works.

KEEP ROCKIN'

@nwilson5
Copy link

I'm having a similar issue ("'NoneType' object has no attribute 'get_user'"). When I initially log in via google OpenId for example, it works fine. I can browse my site logged in for a minute or so. Then I start getting this error for every page.

I'm using the default django_app/views.py for completing login, which implements _do_login. I notice the only difference I see between _do_login and django's login is _do_login looks for social_user.expiration_datetime(), which in my case is None.

I'm not sure if there's something else going on with my middleware which may be mucking things up, but it's looking like my request.user is getting initialized to None thus all middleware I'm using that relies on checking request.user attributes is breaking. I made my own social_auth model, seeing as our user model is quite abstracted from standard django users. I may try adding expiration_datetime() method to my social_user model, and see if that does anything. But might you have an idea of where I might be straying?

@omab
Copy link
Owner

omab commented Nov 20, 2013

@nwilson5, which version of python-social-auth are you using? _do_login() in last codebase was simplified a lot removing a workaround for django auth mechanism.

@nwilson5
Copy link

0.1.17.

I'm finding in social.backends.base.BaseAuth in get_user():

    def get_user(self, user_id):
        """
        Return user with given ID from the User model used by this backend.
        This is called by django.contrib.auth.middleware.
        """
        from social.strategies.utils import get_current_strategy
        strategy = self.strategy or get_current_strategy()
        return strategy.get_user(user_id)

The times it breaks are due to strategy being None. The user_id is correct. I am not entirely sure how that variable is set and why it is None on some pageviews and correctly set in others.

@omab
Copy link
Owner

omab commented Nov 20, 2013

@nwilson5
Copy link

That seems to have done it, thanks. I wasn't using social.apps.django_app.default (or mongoengine) in my installed apps (made my own).

@julienaubert
Copy link

I just upgraded from 0.1.22 to 0.1.23, and I see it changed signature of _do_login in reference to #190. This is however causing me issues as I am relying on _do_login as recommended here in #68.

What is the canonical way to solve this? (I.e. not use non-public method so that it won't break on minor updates).

@julienaubert
Copy link

Actually, I am not sure I could reproduce #68 on 0.1.23. Will use login() for now.

@omab
Copy link
Owner

omab commented Mar 30, 2014

@julienaubert, a new parameter was added to _do_login, I know it breaks compatibility but _do_login should be considered an internal function. The new parameter is the social_user which I'm sure you have available in your view.

As you pointed the usual Django login should work also.

@julienaubert
Copy link

@omab, thanks for the reply. I was just under the impression I had to use _do_login to avoid the issue reported here. Has it been confirmed that #68 is fixed / no need to use _do_login?

@antitoxic
Copy link

_do_login no longer works with 2 arguments. What would the updated snippet be?

@omab
Copy link
Owner

omab commented Jun 18, 2014

@antitoxic
Copy link

@omab yep. I got that far :) I'm simply passing what _do_login was making before: user.social_user

It might be useful to put a changelog warning somewhere.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants