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

Already on GitHub? Sign in to your account

Allow Resources to have multiple (list) authenticators #197

Closed
derks opened this Issue Jul 17, 2011 · 5 comments

Comments

Projects
None yet
4 participants

derks commented Jul 17, 2011

In our apps we build everything around the API. We catch form submissions, and send them via jQuery/AJAX/JSON, etc.. which (I believe) sends the session cookies same as if we just POSTed from a form, therefore using/needing the 'default django authentication'. However, from interface libraries and other applications calling the API in a more standard way we use the APIKeyAuthentication() or something similar in the future (OAuth, etc).

We really need to support two mechanisms for authentication of the API.. one for API key and one that just bases authentication off request.user.is_authenticated (authenticated via browser... normal user session).

Does that make sense, or am I missing something? Is this something that might be useful to build in, or would it be recommended to just write a custom authentication class that accounts for both?

Contributor

toastdriven commented Jul 19, 2011

For now, the best option is a custom Authentication class that delegates off to both.

I've toyed with the idea of adding support for multiple Authentication classes but am not sure if I want to go down that route. If I do, it will be before Tastypie v1.0. Leaving it open for now.

derks commented Jul 22, 2011

I took the ApiKeyAuthentication() class and just tweaked it a little bit. Would appreciate your thoughts on this:

class ApiKeyPlusWebAuthentication(Authentication):
    """
    Handles API key auth, in which a user provides a username & API key as 
    well as standard Django Web Authentication.  The purpose of the dual
    authentication is to allow AJAX calls to be performed (which do not use
    an API key but rather send the session cookie).
    
    Uses the ``ApiKey`` model that ships with tastypie. If you wish to use
    a different model, override the ``get_key`` method to perform the key check
    as suits your needs.
    """
    def _unauthorized(self):
        return HttpUnauthorized()
    
    def is_authenticated(self, request, **kwargs):
        """
        First look for an existing web session, otherwise find the user and 
        check their API key.
        
        Should return either ``True`` if allowed, ``False`` if not or an
        ``HttpResponse`` if you need something custom.
        """
        
        # already have a web session?
        if request.user.is_authenticated():
            return True
            
        from django.contrib.auth.models import User
        
        username = request.GET.get('username') or request.POST.get('username')
        api_key = request.GET.get('api_key') or request.POST.get('api_key')
        
        if not username or not api_key:
            return self._unauthorized()
        
        try:
            user = User.objects.get(username=username)
        except (User.DoesNotExist, User.MultipleObjectsReturned):
            return self._unauthorized()
        
        request.user = user
        return self.get_key(user, api_key)
    
    def get_key(self, user, api_key):
        """
        Attempts to find the API key for the user. Uses ``ApiKey`` by default
        but can be overridden.
        """
        from tastypie.models import ApiKey
        
        try:
            key = ApiKey.objects.get(user=user, key=api_key)
        except ApiKey.DoesNotExist:
            return self._unauthorized()
        
        return True
    
    def get_identifier(self, request):
        """
        Provides a unique string identifier for the requestor.
        
        This implementation returns the user's username.
        """
        # Is a web session?
        if request.user.is_authenticated():
            return request.user.username
        else:
            return request.REQUEST.get('username', 'anonymous')

Essentially all I added was a "if request.user.is_authenticated()" check in a few places.... meaning, if request.user.is_authenticated (they logged in via the web/session cookie) then let them pass... otherwise act just like the ApiKeyAuthentication(). Not sure how sane that is, but it seems to work in the little testing I've done.

Contributor

toastdriven commented Jul 22, 2011

Looks fine to me, though I think you could shorten it up to:

class ApiKeyPlusWebAuthentication(ApiKeyAuthentication):
    def is_authenticated(self, request, **kwargs):
        if request.user.is_authenticated():
            return True

        return super(ApiKeyPlusWebAuthentication, self).is_authenticated(request, **kwargs)

    def get_identifier(self, request):
        if request.user.is_authenticated():
            return request.user.username
        else:
            return super(ApiKeyPlusWebAuthentication, self).get_identifier(request)

@toastdriven toastdriven reopened this Jul 22, 2011

Contributor

poswald commented Nov 16, 2011

This is great for people who build a UI for integration with outside users (API Key) but also want to use that API with browser-based Javascript. I think this would be really useful to people as a built-in option.

@joshbohde joshbohde referenced this issue Mar 29, 2012

Closed

Multi auth #256

Contributor

issackelly commented Jun 4, 2012

Closing #256 closed this issue.

@issackelly issackelly closed this Jun 6, 2012

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment