The django-rest-auth documentation discusses Facebook integration, which I am not interested in-- my concern is providing social login via Google. I have tried this for quite some time now and I'm wondering if anyone else has any documentation on how they did this...even just a rough sketch would be helpful. So far, I have not found any results for this search. I am almost there, but cannot get it to work with the Django rest framework (DRF) browsable API.
Here is what I have so far:
I started from the demo project supplied on the django-rest-auth github page and modified the social login template HTML page to only require the 'code' input, not both 'code' AND 'access_token'. When I supply a valid code (obtained by a separate request to google's auth endpoint), this works fine; the browsable API renders the usual web page with the 'key' (my application's API token for the user) in the response. Checking the django admin, everything worked- the user is logged in, email is authenticated, etc. Good so far.
The issue is that starting point of supplying the 'code'- and how I get that code from google in the first place. When I previously (successfully) used the allauth package, I could simply click on a link, which would "invisibly" perform the whole OAuth2 flow (i.e. request the code, use that code to get the access token, and use the access token to get user's google account info).
To recreate that seamless flow (i.e. NOT starting out with the code), I figured I could interrupt the OAuth2 flow and "intercept" the code returned from google, and then POST that code to the rest-auth social login API. To that end, I created a custom allauth.socialaccount.providers.oauth2.views.OAuth2CallbackView by overriding the dispatch method:
class CustomOAuth2CallbackView(OAuth2CallbackView):
def dispatch(self, request):
# gets the code correctly:
code = request.GET['code']
# rp is of type requests.methods.Response
rp = requests.post(<REST-AUTH API ENDPOINT>, data = {'code':code})
return rp
Usually, this method is called when google sends a GET request to the callback uri you initially supply to google's auth endpoint. With this override, I am able to successfully parse the code returned from google in that callback. The POST request works and has the user's key in the resp._content field. However, it ultimately fails to produce the intended view in the DRF browsable API.
Based on diving down in the callstack, I find that rest_framework.views.APIView.dispatch returns an object of type rest_framework.response.Response. However, when the requests.post method used above completes, it returns an instance of type requests.models.Response. As a result, it does not have the proper attributes and fails in the django middleware. For example, it has no acceptable_renderer attribute and no 'get' method (which is used in django.middleware.clickjacking.py). I could, conceivably, add these requirements to the requests.models.Response (rp) instance, but then this hack becomes even more of a kludge.
Thanks for any help you can provide!
https://github.com/st4lk/django-rest-social-auth
class SocialLoginSignup(SocialSessionAuthView):
"""
Mobile user social signup and login api view
args:
provider: name of the social network
access_token: auth token got from the social sites
"""
serializer_class = SocialSignUpSerializer
authentication_classes = (TokenAuthentication,)
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
provider_name = serializer.validated_data['provider']
decorate_request(request, provider_name) # assign the provider class object in request
authed_user = request.user if not request.user.is_anonymous() else None
token = serializer.validated_data['access_token']
if self.oauth_v1() and request.backend.OAUTH_TOKEN_PARAMETER_NAME not in serializer.validated_data:
request_token = parse_qs(request.backend.set_unauthorized_token())
return Response(request_token)
try:
# authentication function get call from assign class object in request
user = request.backend.do_auth(token, user=authed_user)
except social_exceptions.AuthException as e:
raise exceptions.ParseError({'error':str(e)})
except social_exceptions.AuthTokenError as e:
raise exceptions.ParseError({'error': str(e)})
except social_exceptions.AuthAlreadyAssociated as e:
raise exceptions.ParseError({'error': str(e)})
except social_exceptions.AuthFailed as e:
raise exceptions.ParseError({'error':str(e)})
except social_exceptions.AuthUnknownError as e:
raise exceptions.ParseError({'error': str(e)})
except social_exceptions.WrongBackend as e:
raise exceptions.ParseError({'error':str(e)})
except Exception as e:
raise exceptions.ParseError({'error': social_message.INVALID_AUTH_TOKEN})
token, created = Token.objects.get_or_create(user=user)
return Response({'auth_token':token.key})
Related
I have created a Login API which authenticates users from django.contrib.auth.models.User. I am using DRF and implementing a token authentication with django-rest-knox and so far so good.
The application I am developing is a bit complicated but I'm gonna use one of our sub-apps as an example. So we have a sub application called jobnet and the goal of this application is to allow people to register an account thru the website and be able to apply for available jobs in our company online.
The application shall have separate login pages for 2 different types of users (i.e. staff users (the company's employees) and those online applicants. The process here is a online applicant will register for an account and that will be marked is_staff=False. Every time he logs in, he shall be redirected to his non-staff dashboard where he can apply for jobs and manage applications.
Once he gets officially hired, his account will be updated to is_staff=True. Now, he can either login via the applicant's login interface, or via the staff's login page. Either way, the system will detect that he is already a staff and will redirect him to the staff's dashboard instead.
I already have a logic (in mind) for redirecting users thru different views depending on their account configuration. My problem now is I have no idea how to allow non-staff users to be able to login in the first place using the authentication tools I am using (Django's User model and knox token authentication). Everytime I try to login a non-staff user, the response says "Invalid credentials..."
I tried defining has_permission(self, request) method inside my LoginAPI class but to no avail.
Here is my Login API source code:
class LoginAPI(generics.GenericAPIView):
serializer_class = LoginSerializer
permission_classes = ()
authentication_classes = (knox.auth.TokenAuthentication,)
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data
allowed_modules = {}
is_staff = False
if user.is_staff:
allowed_modules = set(Permission.objects.filter(group__user=user).values_list('codename', flat=True))
is_staff = True
return Response(
{
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": AuthToken.objects.create(user)[1],
"authenticated": True,
"staff": is_staff,
"modules": allowed_modules
}
)
This inquiry is no longer relevant. I have just realized I have unchecked the active property of my test account in the admin portal that's why I was getting an error due to the ValidationError I have raised in my serializer. So so dumb... Nonetheless, code above works just as I want it and everything works fine in my authentication at the moment so far.
Although as per #ArakkalAbu pointed out in the comment above, I will take a look at my LoginAPI view and maybe actually pattern it the way Knox implements it on its LoginView since what I'm doing above is just overriding the post method, creating my custom login logic and just generating token via Knox's Authtoken model.
Thanks!
I'm really stuck at this problem for a couple of days now.
While I understand, what's happening here, I don't really know the best workaround/solution for this.
Problem:
I'm trying to create a user login endpoint using Django and DRF in general.
My login API needs to support a login via password as well as login via OTP.
My LoginView looks like:
def post(self, request, **kwargs):
"""
post
Method to handle user login
:param request:
:param args:
:param kwargs:
:return:
"""
request_data = request.data
login_using_password = request_data.get('login-with-password') is True
login_using_otp = request_data.get('login-with-otp') is True
if request_data is not None:
if all((login_using_password, login_using_otp)):
raise accounts_exceptions.InvalidLoginRequestError()
if login_using_password:
return Response(self._login_with_password(request))
elif login_using_otp:
return Response(self._login_with_otp(request))
raise accounts_exceptions.InvalidLoginRequestError()
return Response(self._login_with_password(request))
Also my _login_with_password looks like:
def _login_with_password(self, request, **kwargs):
"""
_login_with_password
A utility method to handle login with password
:param request:
:return:
"""
return getattr(ObtainJSONWebToken.as_view()(request=request._request, ), 'data')
When I try to login, Django complains saying RawPostDataException You cannot access body after reading from request's data stream
I'm using JWT to authenticate requests. ObtainJSONWebToken is a view provided by DRF-JWT to obtain access tokens to authenticate requests.
What is the workaround/solution for this?
Is there a better way to support such a login requirement?
Thanks in advance!
Resolved this.
There's no concrete way to solve the problem above.
Django disallows access to request.data multiple times.
It could be done only once for the entire request lifetime.
So, this left me with two solutions:
Move my request payload to query params.
Move my request payload to url context.
I ended up using a mix and match of both.
So, basically I used request.query_params and self.context to fetch data from the request and changed my URL and request structure accordingly.
I am using the Django Rest Framework in my Python-Django app, and am using a Custom authentication for the api.
If I work just with my custom authentication method, works correctly.
#authentication_classes((CustomAuthentication, ))
But If I try to have basic authentication and my custom authentication, in that order, my custom authentication never executes. I mean, I want that if the Basic authentication fails, then try with the custom authentication. The Basic Authentication is executes and then ends.
#authentication_classes((SessionAuthentication, BasicAuthentication, CustomAuthentication ))
Is possible to have at the same time this three authentication methods, and execute them in that order?
#Arpit Goyal's answer makes the workflow clear.
If you INDEED want to go through all the authentication classes,
here's a work around solution you can try. I hope it can help you.
#authentication_classes((AuthencationWrapper, ))
add a AuthencationWrapper
class AuthencationWrapper(BaseAuthentication):
authenication_classes = (
BasicAuthentication,
SessionAuthentication,
CustomAuthentication,
)
def authenticate(self, request):
exc = None
ret = None
for auth in self.authentication_classes:
try:
ret = auth().authenticate(request)
# if success, we will break, else we will continue to call next one
break
except exceptions.AuthenticationFailed as e:
# we only record the first failed exception
if exc is None:
exc = e
self.first_failed_auth = auth()
if ret is None:
raise exc
# one of the authentication_classes is passed
return ret
def authenticate_header(self, request):
# actualy this way breaks what django-rest-framework doing now
return self.first_failed_auth.authenticate_header(request)
# the one follows what django-rest-framework doing now
# choose the first authentication class header
## if self.authentication_classes:
## return self.authentication_classes[0].authenticate_header(request)
The Django Rest Framework authentication documentation clearly states this:
The authentication schemes are always defined as a list of classes.
REST framework will attempt to authenticate with each class in the
list, and will set request.user and request.auth using the return
value of the first class that successfully authenticates.
If no class authenticates, request.user will be set to an instance of
django.contrib.auth.models.AnonymousUser, and request.auth will be set
to None.
So whenever your first class authenticates request.user and request.auth is set.
In case you want to authenticate with your CustomAuthentication class return None for BasicAuthentication so that all your authentication classes are used but the user is set as per your CustomAuthentication
I am working on a project in angularjs and django 1.6
login(request, user)
print(request.user, request.user.id)
The login is done using standard login from django.contrib.auth in the LoginResource. Inside that it successfully prints user and its id.
Then after logging in i tried another api request from client side.Say there is a MessageResource model. And it has archive function.
def archive(self, request, **kwargs):
self.method_check(request, allowed=['post'])
data = self.deserialize(request, request.body,format=request.META.get('CONTENT_TYPE', 'application/json'))
arch_data = data.get('arch_list', '')
print(request.user, request.user.id)
So i tried to track the user inside this function.But
the last print line prints
AnonymousUser None
Means the user data is not stored in it. Can someone tell me what i'm doing wrong ? How can i get the login user from request?
Cause one - authentication that doesn't attach user to request
It depends of authorization in particular resource.
Let say if you are using ApiKeyAuthorization the user is fetched from credentials and assigned to the request during execution of authentication's is_authenticated method here: https://github.com/toastdriven/django-tastypie/blob/master/tastypie/authentication.py#L206
But if you are using standard Authentication, the method is_authenticated doesn't check or assign anything: https://github.com/toastdriven/django-tastypie/blob/master/tastypie/authentication.py#L47. So event if there are credentials in request they wont be checked or assigned.
Cause two - using resource extended methods doesn't provide authentication etc.
Yes in this case you have to check on your own: allowed http methods authorization, authentication, serialize request.body etc.
If your authentication assign user to request in is_authenticated method like ApiKeyAuthentication does for instance. You just add one line:
def archive(self, request, **kwargs):
self.method_check(request, allowed=['post'])
self.is_authenticated(request)
data = self.deserialize(request, request.body,format=request.META.get('CONTENT_TYPE', 'application/json'))
arch_data = data.get('arch_list', '')
print(request.user, request.user.id)
I am creating a mobile app where I need to use authentication. How can I achieve the following:
I need to create a user. After creating the user it needs to send Api_client and a secret as a response to the user.
I have a function to perform verification. After creating the user it needs to call the function for mobile verification.
Importantly, how can I stop a user who uses a for loop and starts adding users?
I tried this:
models.signals.post_save.connect(create_api_key, sender=User)
That created an API key but is not sending it as a response when creating the user is successful.
Here's what I understand from your question :
You want any user of your mobile app to register himself,
anonymously, as a user to your Django application.
This request must trigger a Tastypie api_key creation, and then return it.
You want to prevent this request from being spammed.
I don't understand this :
"I have a function for mobile without verification. After creating the user it needs to call the function for mobile verification."
To answer the points I get :
See this SO question regarding user registration with Tastypie How to create or register User using django-tastypie API programmatically?, notably this part :
def obj_create(self, bundle, request=None, **kwargs):
username, password = bundle.data['username'], bundle.data['password']
try:
bundle.obj = User.objects.create_user(username, '', password)
except IntegrityError:
raise BadRequest('That username already exists')
return bundle
For a complete walkthrough, check this article : http://psjinx.com/programming/2013/06/07/so-you-want-to-create-users-using-djangotastypie/
You're on the right track regarding the api_key creation, except you have to tell the api to actually send it back. You can use the regular way (it requires another request, though) :
i.e make it accessible from UserResource, as described in the article linked above, specifically :
def dehydrate(self, bundle):
bundle.data['key'] = bundle.obj.api_key.key
try:
# Don't return `raw_password` in response.
del bundle.data["raw_password"]
except KeyError:
pass
return bundle
If you want to send it right after a User's registration, don't forget to set "always_return_data" to True and add the api_key to the response.
Spam / loop registration :
You should look into your server's capabilities regarding this matter. For example, assuming you're using Nginx : http://wiki.nginx.org/NginxHttpLimitReqModule
Another option might be to use this : http://django-ratelimit-backend.readthedocs.org/en/latest/
Hope this helps !
Regards,