Django + JQMobile + PhoneGap - django

I've been developing a mobile app to access one of my django websites. I've done the restful API using TastyPie and developed the front end using JQMobile. I've come to the part where I want to log users and have access to that logged in user.
I've done a lot of reading and searching, but I'm still really unsure what is the best approach. Ideally, I'd like to log in the user with their username and password, and then filter some of the API's returned data on this user (which I can do via the TastyPie documentation).
How have other people approached authenticating users with JQMobile and Django. I'm using PhoneGap as well so I can store returned user info from a login in the local storage if required. But I'm not quite sure how to code it all together to make request.user available on the django side when the mobile users are using the app.
So far I've come up with this from another couple of posts in the UserResource on the TastyPie side of things to sign in a user, but I'm not sure what to do once the user is signed in.
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = 'user'
list_allowed_methods = ['get', 'post']
def override_urls(self):
return [
url(r"^(?P<resource_name>%s)/signin%s$" %
(self._meta.resource_name, trailing_slash()),
self.wrap_view('signin'), name="api_signin"),
]
def signin(self, request, **kwargs):
self.method_check(request, allowed=['post'])
# Per https://docs.djangoproject.com/en/1.3/topics/auth/#django.contrib.auth.login...
username = request.GET['username']
password = request.GET['password']
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
return self.create_response(request, {'success': True})
else:
# Return a 'disabled account' error message
return self.create_response(request, {'success': False})
else:
# Return an 'invalid login' error message.
return self.create_response(request, {'success': False})
Does anyone have any code they can share, or any pointers how to log in the users and maintain their state?
Cheers,
Ben

Phonegap is actually just a browser wrapped in some native code, which means it has the same means to persist sessions like normal web browser do - cookies!
Every ajax request being sent to the backend API can contain the sessionid cookie just like a normal GET request. The requst.user object will be available to you in your views.
You don't need to build anything special or use localstorage for that. The only thing to verify is that your domain is whitelisted so your app can access it.

Related

Store extra data like refresh_token when signing up via access_token

I am trying to signup using access token for google. My frontend is next.js with next-auth.js fetching the access_token, refresh_token, etc. I am using python social auth with Django to persist the user information. I use the access_token provided by next-auth to signup the user in Django. How do I make sure that the other response fields like refresh_token, and expires are saved in the Django DB? I can pass the required fields from next-auth to an API in Django but not sure what the expected format is.
https://python-social-auth.readthedocs.io/en/latest/use_cases.html#signup-by-oauth-access-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.
token = request.GET.get('access_token')
user = request.backend.do_auth(token)
if user:
return HttpResponse(json.dumps(get_tokens_for_user(user)))
else:
return HttpResponse('Unauthorized', status=401)
Sounds like you need a table to store these tokens (not sure if you have one already), lets take this model as an example:
class StoredToken(models.Model):
refresh_token = models.CharField()
access_token = models.CharField()
# Maybe you need the related user too?
user = models.ForeignKey(User, on_delete=models.CASCADE)
After you created your table, all you need is to save your data when you have it, from your example:
def get_tokens_for_user(user):
refresh = RefreshToken.for_user(user)
update_last_login(None, user)
StoredToken.objects.create(refresh_token=refresh, access_token=refresh.access_token, user=user)
return {
'refresh': str(refresh),
'access': str(refresh.access_token),
}
You obviously need to do migrations to generate your table, but I assume you have the knowledge to do that. Otherwise refer to the django documentation to create models
I was able to store the refresh token along with other EXTRA_DATA in python social auth by adding response kwarg. This response kwarg is nothing but the dictionary containing the extra data.
For example: With Google, response is the return value of https://developers.google.com/identity/protocols/oauth2/web-server#exchange-authorization-code
#psa
def register_by_access_token(request, backend):
# If it's needed, request.backend and request.strategy will be loaded with the current
# backend and strategy.
response = json.loads(request.body.decode('utf-8'))
user = request.backend.do_auth(response['access_token'], response=response)
if user:
return HttpResponse(json.dumps(get_tokens_for_user(user)))
else:
return HttpResponse('Unauthorized', status=401)

how to customize django jwt graphql authentication

Hi I'm trying to customize
jwt graphql Django default authentication
I need to achieve log in with username or email
normal Django we customized authentication backend.
mutation{
tokenAuth(username:"myemail#email.com"
password:"pass")
{
token
}
}
Authenticate with username
mutation{
tokenAuth(username:"myname"
password:"pass")
{
token
}
}
the normal username is working fine.
how I can authenticate the user by username or email in jwt graphql
I tried this link
https://django-graphql-jwt.domake.io/en/latest/customizing.html
I don't get any idea about that...
Does anyone have any idea about that??
You have to make the changes on the Model level not in JWT, JWT will follow, because JWT is only a presentation of what is going on behind the scenes.
Look at the link under here, maybe it will help you! :)
Multiple USERNAME_FIELD in django user model
Disclaimer - mama's answer should work. Halfway through writting an answer I realised I'm wrong, but I still wanna show you what I wanted to suggest. It shows what JWT TokenAuth mutation does and a way to tap into that completely.
change the inbuild Django authentication like mama's answer suggests
rewrite graphql_jwt.decorators.token_auth to look at both fields, not just one
write your own class for the TokenMutation that uses this decorator on it's mutate function
Something like so (untested):
def two_field_token_auth(f):
#wraps(f)
#setup_jwt_cookie
#csrf_rotation
#refresh_expiration
def wrapper(cls, root, info, password, **kwargs):
context = info.context
context._jwt_token_auth = True
username = kwargs.get('username')
email = kwargs.get('email')
user = your_auth_method(
request=context,
username=username,
email=email,
password=password,
)
if user is None:
raise exceptions.JSONWebTokenError(
_('Please enter valid credentials'),
)
if hasattr(context, 'user'):
context.user = user
result = f(cls, root, info, **kwargs)
signals.token_issued.send(sender=cls, request=context, user=user)
return maybe_thenable((context, user, result), on_token_auth_resolve)
return wrapper
class TwoFieldJWTMutation(JSONWebTokenMutation):
#classmethod
#two_field_token_auth
def mutate(cls, root, info, **kwargs):
return cls.resolve(root, info, **kwargs)
All the necessary imports you can find here and here

Authenticate DRF automatically when request comes from page with user logged in. Request Token auth if api request is made externally

I have overwritten the custom user model such that I can login using email instead of username and such that I can redirect to changepassword on first login.
def login_view(request):
if request.method == 'POST':
form = AuthenticationForm(data=request.POST)
if form.is_valid():
user = form.get_user()
if user.last_login is None:
login(request, user)
return redirect('accounts:change_password')
else:
login(request, user)
return redirect('home')
else:
form = AuthenticationForm()
if request.user.is_authenticated:
return redirect('home')
else:
return render(request, 'login.html', {'form': form})
I have set up a basic rest API endpoint using DRF
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
When I access the homepage, I need to be logged in:
#login_required(login_url="/accounts/login/")
def home(request):
return render(request, 'index.html', {})
What I would like to do is to authenticate using django.contrib.auth and be redirected to homepage.
When the homepage loads, I would like to perform an AJAX call to display all users.
$.ajax(
{
type: "GET",
url: '/accounts/users/',
success: function(result){
console.log(result);
}
});
This call should only work if I am logged in with my user already.
If I access the endpoint externally, let's say in Postman, It should ask me to authenticate.
I should be able to authenticate externally in postman using token authentication.
The question:
How can I mix the django.contrib.auth authentication with the Django rest_framework Token Authentication in the manner described above? I would like to have a web application and a REST API at the same time. Authenticate in the web application using the django.contrib.auth. Authenticate to REST API using Token. But if user is already logged in the web application perform Rest API request without needing to authenticate again. Can I somehow reuse the web application session?
Already customized my user model:
https://docs.djangoproject.com/en/2.1/topics/auth/customizing/
Have looked at this, not started implementing yet. I am not sure how they can connect.
https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication
Solution
I accepted the answer below although I found the solution meanwhile. I assumed that you can only add one method of authentication but you can have more. What I actually ended up doing was:
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
It works like a charm. The IsAuthenticated permission is applied globally in my case. In the accepted answer is applied per endpoint
You can always use as many authentication methods as you want. DRF has SessionAuthentication that works like the native Django authentication, in addition to its TokenAuthentication. All you need to do is set both authentication classes either globally in settings file or per view.
For example, you can set the authentication classes for your UserViewSet in this way.
from rest_framework.authentication import TokenAuthentication, SessionAuthentication
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
authentication_classes = [TokenAuthentication, SessionAuthentication]
In this way, your web client can use sessions to authenticate, while other clients use tokens.

Django Tastypie Import Error on resources

I have something strange going on that I can't seem to crack. I'm building an API with Tastypie and when I issue this call in my browser against localserver, it works fine: localserver/api/v1/userfavorite/?user__username=testowner
However, in my code, I'm getting an error: "int() argument must be a string or a number, not 'SimpleLazyObject'". I realize it has to do with the user being treated as a request.user object, but I can't figure out where/why. I'm very confused why it works when issuing the API call in the browser, but in the code it is not working.
Here is my code:
# views.py
#login_required
def favorites(request):
'''
display a list of posts that a user has marked as favorite
'''
user = request.user
favorites_url = settings.BASE_URL + "/api/v1/userfavorite/?user__username=" + user.username
favorites = get_json(favorites_url)
return render(request, "maincontent/favorites.html", {'favorites':favorites})
# resources.py
class UserFavoriteResource(ModelResource):
'''
manage post favorites by a user. Users can use a favorites list
to easily view posts that they have liked or deemed important.
'''
user = fields.ForeignKey(UserResource, 'user')
post = fields.ForeignKey('blog.api.resources.PostResource', 'post', full=True)
class Meta:
queryset = UserFavorite.objects.all()
allowed_methods = ['get', 'post', 'delete']
authentication = Authentication()
authorization = Authorization()
filtering = {
'user':ALL_WITH_RELATIONS
}
def hydrate_user(self, bundle):
# build the current user to save for the favorite instance
bundle.data['user'] = bundle.request.user
return bundle
def get_object_list(self, request):
# filter results to the current user
return super(UserFavoriteResource, self).get_object_list(request)\
.filter(user=request.user)
# utils.py
def get_json(url):
# return the raw json from a request without any extraction
data = requests.get(url).json()
return data
Some notes:
1. I have the post method working to create the UserFavorite item
2. I can verify that the favorites_url is being generated correctly
3. I have tried hardcoding the favorites_url as well, same error.
EDIT: 4. I am logged in while doing this, and have verified that request.user returns the user
This doesn't work because there is Anonymous user in your request.user. You are using Authentication it does not require user to be logged in. So if you perform requests call that request is not authenticated and request.user is AnonymousUser and that error occurs when you try to save Anonymous user to db. Tastypie documentation advices to not using browsers to testing things up, just curl instead. Browsers stores a lot of data and yours one probably remembered you have been logged to admin panel in localhost:8000 on another tab that's why it worked in browser.
I would prefer something like this:
def hydrate_user(self, bundle):
"""\
Currently logged user is default.
"""
if bundle.request.method in ['POST', 'PUT']:
if not bundle.request.user.is_authenticated():
raise ValidationError('Must be logged in')
bundle.obj.user = bundle.request.user
bundle.data['user'] = \
'/api/v1/userauth/user/{}'.format(bundle.request.user.pk)
return bundle

Custom Auth backend with middleware

I tried looking at this answer, as well as using django sessions here.
The login with my custom auth works fine, but I want to validate the token on every request with middleware, and I can't figure out how to store the token so that it may be accessed from both the middleware as well as views.
I tried storing a session variable from my auth backend, but I would always get a key error when trying to access it from my views.
Is there a good way to do this?
Thanks!
class MyAuthBackend(object):
supports_inactive_user = False
supports_object_permissions = False
supports_anonymous_user = False
def authenticate(self, username=None, password=None):
# This makes a call to my API to varify login, then return token if valid. I need to make login_valid accessible to my middleware and views.
login_valid = auth.login(username,password)
if login_valid:
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
user = User(username=username, password='never_used')
user.is_active = True
user.save()
return user
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
class MyAuthMiddleware(object):
def process_request(self, request):
if not request.user.is_anonymous():
# API call to my backend to check if token is still valid. If not, return to login page.
token_variable = ???????????
if isTokenStillValid(token_variable):
return
else:
return HttpResponseRedirect('/accounts/login/?next=%s' % request.path)
Are you using the default django.contrib.auth login view for logging in? It seems to completely clear the session during the login process (which happens after your authentication backend is called, in contrib.auth.login function, described here).
I think you might either try to write your own login view, with an alternative login function that preserves the auth token, or store the token somewhere else (database table, cache system). The latter might make it difficult to allow multiple simultaneous logins for one user.