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

217 Views Asked by At

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.

1

There are 1 best solutions below

0
On BEST ANSWER

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',]