Search code examples
djangoauthenticationdjango-modelstastypieapi-key

tastypie - Login from mobile to Django, how do I set up with ApiKeyAuthentication


I've set up Tastypie within a Django project and the API is correctly serving resources. I am now trying to allow mobile users (applications) to sign up, sign in and sign out through said API.

class BaseResource(ModelResource):

    class Meta:
        allowed_methods = [ 'get' ]
        authentication  = BasicAuthentication()
class UserResource(BaseResource):

    class Meta:
        queryset      = User.objects.all()
        resource_name = 'users'
        ...


class ProfileResource(BaseResource):

    class Meta:
        queryset      = Profile.objects.all()
        resource_name = 'profiles'
        ...

So this serves my first purpose. Regarding the login, I don't think BasicAuthentication is appropriated for requests from a mobile. From what I've read there seem to be several ways to do what I want:

What bothers me in the first link (see the answer) is that the mobile application has to send JSON containing the raw password:

{ 'username' : 'me', 'password' : 'l33t' }

Isn't it possible that someone/thing grab this JSON and thus have access to the password ? Wouldn't it be better to use ApiKeyAuthentication ?

I understand less and less the more I read about it. If the account has been created from the Web platform (django-userena) then I can't use ApiKeyAuthentication because the key should be created when a new User is saved.

I can find several ways of doing what I want, and I can't find the right one... I do realize this question has been asked and answered many times, but I'm looking fo directions about implementing this in the best way regarding my needs.


Solution

  • I ended up doing the following:

    # ──────────────────────────────────────────────────────────────────────────────
    class BaseResource(ModelResource):
    
        # ──────────────────────────────────────
        def prepend_urls(self):
            try:
                additional_urls = self._meta.additional_urls
            except AttributeError:
                additional_urls = []
            return [url(r'^'+u[0]+'$', self.wrap_view(u[1]), name=u[2]) for u in additional_urls]
    
        # ──────────────────────────────────────
        def update_in_place(self, request, original_bundle, new_data):
            try:
                allowed_fields = self._meta.allowed_fields
            except AttributeError:
                allowed_fields = None
            if allowed_fields and set(new_data.keys()) - set(allowed_fields):
                raise BadRequest('Only alterable field(s): {}'.format(', '.join(allowed_fields)))
            return super(ProfileResource, self).update_in_place(request, original_bundle, new_data)
    
        # ──────────────────────────────────────
        class Meta:
            abstract = True
            allowed_methods = ['get',]
            authentication  = ApiKeyAuthentication()
            authorization   = DjangoAuthorization()
            max_limit = 1000
    
    # ──────────────────────────────────────────────────────────────────────────────
    class UserResource(BaseResource):
    
        ...
    
        # ──────────────────────────────────────
        def signup(self, request, **kwargs):
            ...
    
        # ──────────────────────────────────────
        def signin(self, request, **kwargs):
            self.method_check(request, allowed=['post'])
            data = self.deserialize(
                request,
                request.body,
                format=request.META.get('CONTENT_TYPE', 'application/json')
            )
            username = data.get('username', '')
            password = data.get('password', '')
            user = authenticate(username=username, password=password)
            if user:
                if user.is_active:
                    # login(request, user)
                    try:
                        key = ApiKey.objects.get(user=user)
                        if not key.key:
                            key.save()
                    except ApiKey.DoesNotExist:
                        key = ApiKey.objects.create(user=user)
                    return self.create_response(request, {
                        'success': True,
                        'data': key.key,
                    })
                else:
                    return self.create_response(request, {
                        'success': False,
                        'message': 'User is not active',
                    }, HttpForbidden)
            else:
                return self.create_response(request, {
                    'success': False,
                    'message': 'Wrong password',
                    }, HttpUnauthorized)
    
        # ──────────────────────────────────────
        class Meta(BaseResource.Meta):
            allowed_methods = ['get', 'patch',]
            queryset        = User.objects.all()
            resource_name   = 'users'
            excludes        = [ 'first_name', 'last_name', 'password' ]
            filtering = {
                ...
            }
            additional_urls = [
                ('signup/', 'signup', 'api-signup'),
                ('signin/', 'signin', 'api-signin'),
            ]
            allowed_fields = ['email',]