Get user uid from Facebook with Django Social Auth - django

I'm trying to get the user profile picture from Facebook with django-social-auth.
I saw in an other post, that I should get the user uid from Facebook to have access to the profile picture.
How can I get the uid?
From django-social-auth I just installed it and configured the basic stuff to login/logout with django-registrations.
This is my html to login:
Login with FB
How can I do a request to a 'home' view to the user in facebook and get the uid?
I found this in the django-socail-auth docs:
def social_associate_and_load_data(backend, details, response, uid, user,
social_user=None, *args, **kwargs):
"""
The combination of associate_user and load_extra_data functions
of django-social-auth. The reason for combining these two pipeline
functions is decreasing the number of database visits.
"""
extra_data = backend.extra_data(user, uid, response, details)
created = False
if not social_user and user:
social_user, created = UserSocialAuth.objects.get_or_create(
user_id=user.id,
provider=backend.name,
uid=uid,
defaults={'extra_data': extra_data})
if not created and extra_data and social_user.extra_data != extra_data:
social_user.extra_data.update(extra_data)
social_user.save()
return {'social_user': social_user}
Where should I put it in my django app? In views.py? If it yes, how can I activated the view, if I'm just logging in with commom social-auth urls.

Facebook uid is stored in UserSocialAuth instance, you can get it by doing
user.social_auth.get(provider='facebook').uid

Related

Django allauth return Account Inactive page after login

I am new to programming and I don't fully understand how allauth work and what exactly to do.
I have an application where the user is inactive after signing up and he must click on the confirmation email so that he becomes active.
I tried to configure allauth so that a user can also log in with google, but when a new user logs in he is redirected to a page that says Account Inactive.In admin I can see that it creates an account (inactive) and also an entry in social accounts but it doesn't generate a social application token.
On the other hand when a user that already has an acount tries to log in with google it redirect to allauth sign up page.
And so I don't understand how activation with allauth works. Did I make something wrong with my allauth configuration? Should I edit my login function or something else?
Take a look at the DefaultAdapter class. There is a method for pre_login that checks if your user is inactive and if they are it immediately redirects them to the account_inactive url.
def pre_login(
self,
request,
user,
*,
email_verification,
signal_kwargs,
email,
signup,
redirect_url
):
from .utils import has_verified_email, send_email_confirmation
if not user.is_active:
return self.respond_user_inactive(request, user)
....
def respond_user_inactive(self, request, user):
return HttpResponseRedirect(reverse("account_inactive"))

How should I be implementing user SSO with AAD in a Django application (using the Django Microsoft Authentication Backend module)?

I'm developing a Django (2.2.3) application with Django Microsoft Auth installed to handle SSO with Azure AD. I've been able to follow the quickstart documentation to allow me to log into the Django Admin panel by either using my Microsoft identity, or a standard username and password I've added to the Django user table. This all works out of the box and is fine.
My question put (really) simply is "What do I do next?". From a user's perspective, I'd like them to:
Navigate to my application (example.com/ or example.com/content) - Django will realise they aren't authenticated, and either
automatically redirect them to the SSO portal in the same window, or
redirect them to example.com/login, which requires them to click a button that will open the SSO
portal in a window (which is what happens in the default admin case)
Allow them to sign in and use MFA with their Microsoft Account
Once successful redirect them to my #login_required pages (example.com/content)
Currently, at the root of my navigation (example.com/), I have this:
def index(request):
if request.user.is_authenticated:
return redirect("/content")
else:
return redirect("/login")
My original idea was to simply change the redirect("/login") to redirect(authorization_url) - and this is where my problems start..
As far as I can tell, there isn't any way to get the current instance(?) of the context processor or backend of the microsoft_auth plugin to call the authorization_url() function and redirect the user from views.py.
Ok... Then I thought I'd just instantiate the MicrosoftClient class that generates the auth URL. This didn't work - not 100% sure why, but it think it may have something to do with the fact that some state variable used by the actual MicrosoftClient instance on the backend/context processor is inconsistent with my instance.
Finally, I tried to mimic what the automatic /admin page does - present an SSO button for the user to click, and open the Azure portal in a separate window. After digging around a bit, I realise that I fundamentally have the same problem - the auth URL is passed into the admin login page template as inline JS, which is later used to create the Azure window asynchronously on the client side.
As a sanity check, I tried to manually navigate to the auth URL as it is presented in the admin login page, and that did work (though the redirect to /content didn't).
At this point, given how difficult I think I'm making it for myself, I'm feel like I'm going about this whole thing completely the wrong way. Sadly, I can't find any documentation on how to complete this part of the process.
So, what am I doing wrong?!
A couple more days at this and I eventually worked out the issues myself, and learned a little more about how Django works too.
The link I was missing was how/where context processors from (third party) Django modules pass their context's through to the page that's eventually rendered. I didn't realise that variables from the microsoft_auth package (such as the authorisation_url used in its template) were accessible to me in any of my templates by default as well. Knowing this, I was able to implement a slightly simpler version of the same JS based login process that the admin panel uses.
Assuming that anyone reading this in the future is going through the same (learning) process I have (with this package in particular), I might be able to guess at the next couple of questions you'll have...
The first one was "I've logged in successfully...how do I do anything on behalf of the user?!". One would assume you'd be given the user's access token to use for future requests, but at the time of writing this package didn't seem to do it in any obvious way by default. The docs for the package only get you as far as logging into the admin panel.
The (in my opinion, not so obvious) answer is that you have to set MICROSOFT_AUTH_AUTHENTICATE_HOOK to a function that can be called on a successful authentication. It will be passed the logged in user (model) and their token JSON object for you to do with as you wish. After some deliberation, I opted to extend my user model using AbstractUser and just keep each user's token with their other data.
models.py
class User(AbstractUser):
access_token = models.CharField(max_length=2048, blank=True, null=True)
id_token = models.CharField(max_length=2048, blank=True, null=True)
token_expires = models.DateTimeField(blank=True, null=True)
aad.py
from datetime import datetime
from django.utils.timezone import make_aware
def store_token(user, token):
user.access_token = token["access_token"]
user.id_token = token["id_token"]
user.token_expires = make_aware(datetime.fromtimestamp(token["expires_at"]))
user.save()
settings.py
MICROSOFT_AUTH_EXTRA_SCOPES = "User.Read"
MICROSOFT_AUTH_AUTHENTICATE_HOOK = "django_app.aad.store_token"
Note the MICROSOFT_AUTH_EXTRA_SCOPES setting, which might be your second/side question - The default scopes set in the package as SCOPE_MICROSOFT = ["openid", "email", "profile"], and how to add more isn't made obvious. I needed to add User.Read at the very least. Keep in mind that the setting expects a string of space separated scopes, not a list.
Once you have the access token, you're free to make requests to the Microsoft Graph API. Their Graph Explorer is extremely useful in helping out with this.
So I made this custom view in Django based on https://github.com/Azure-Samples/ms-identity-python-webapp.
Hopefully, this will help someone.
import logging
import uuid
from os import getenv
import msal
import requests
from django.http import JsonResponse
from django.shortcuts import redirect, render
from rest_framework.generics import ListAPIView
logging.getLogger("msal").setLevel(logging.WARN)
# Application (client) ID of app registration
CLIENT_ID = "<appid of client registered in AD>"
TENANT_ID = "<tenantid of AD>"
CLIENT_SECRET = getenv("CLIENT_SECRET")
AUTHORITY = "https://login.microsoftonline.com/" + TENANT_ID
# This resource requires no admin consent
GRAPH_ENDPOINT = 'https://graph.microsoft.com/v1.0/me'
SCOPE = ["User.Read"]
LOGIN_URI = "https://<your_domain>/login"
# This is registered as a redirect URI in app registrations in AD
REDIRECT_URI = "https://<your_domain>/authorize"
class Login(ListAPIView):
'''initial login
'''
def get(self, request):
session = request.session
id_token_claims = get_token_from_cache(session, SCOPE)
if id_token_claims:
access_token = id_token_claims.get("access_token")
if access_token:
graph_response = microsoft_graph_call(access_token)
if graph_response.get("error"):
resp = JsonResponse(graph_response, status=401)
else:
resp = render(request, 'API_AUTH.html', graph_response)
else:
session["state"] = str(uuid.uuid4())
auth_url = build_auth_url(scopes=SCOPE, state=session["state"])
resp = redirect(auth_url)
else:
session["state"] = str(uuid.uuid4())
auth_url = build_auth_url(scopes=SCOPE, state=session["state"])
resp = redirect(auth_url)
return resp
class Authorize(ListAPIView):
'''authorize after login
'''
def get(self, request):
session = request.session
# If states don't match login again
if request.GET.get('state') != session.get("state"):
return redirect(LOGIN_URI)
# Authentication/Authorization failure
if "error" in request.GET:
return JsonResponse({"error":request.GET.get("error")})
if request.GET.get('code'):
cache = load_cache(session)
result = build_msal_app(cache=cache).acquire_token_by_authorization_code(
request.GET['code'],
# Misspelled scope would cause an HTTP 400 error here
scopes=SCOPE,
redirect_uri=REDIRECT_URI
)
if "error" in result:
resp = JsonResponse({"error":request.GET.get("error")})
else:
access_token = result["access_token"]
session["user"] = result.get("id_token_claims")
save_cache(session, cache)
# Get user details using microsoft graph api call
graph_response = microsoft_graph_call(access_token)
resp = render(request, 'API_AUTH.html', graph_response)
else:
resp = JsonResponse({"login":"failed"}, status=401)
return resp
def load_cache(session):
'''loads from msal cache
'''
cache = msal.SerializableTokenCache()
if session.get("token_cache"):
cache.deserialize(session["token_cache"])
return cache
def save_cache(session,cache):
'''saves to msal cache
'''
if cache.has_state_changed:
session["token_cache"] = cache.serialize()
def build_msal_app(cache=None, authority=None):
'''builds msal cache
'''
return msal.ConfidentialClientApplication(
CLIENT_ID, authority=authority or AUTHORITY,
client_credential=CLIENT_SECRET, token_cache=cache)
def build_auth_url(authority=None, scopes=None, state=None):
'''builds auth url per tenantid
'''
return build_msal_app(authority=authority).get_authorization_request_url(
scopes or [],
state=state or str(uuid.uuid4()),
redirect_uri=REDIRECT_URI)
def get_token_from_cache(session, scope):
'''get accesstoken from cache
'''
# This web app maintains one cache per session
cache = load_cache(session)
cca = build_msal_app(cache=cache)
accounts = cca.get_accounts()
# So all account(s) belong to the current signed-in user
if accounts:
result = cca.acquire_token_silent(scope, account=accounts[0])
save_cache(session, cache)
return result
def microsoft_graph_call(access_token):
'''graph api to microsoft
'''
# Use token to call downstream service
graph_data = requests.get(
url=GRAPH_ENDPOINT,
headers={'Authorization': 'Bearer ' + access_token},
).json()
if "error" not in graph_data:
return {
"Login" : "success",
"UserId" : graph_data.get("id"),
"UserName" : graph_data.get("displayName"),
"AccessToken" : access_token
}
else:
return {"error" : graph_data}

DRF and Knox Authentication: Allow login for non-staff users

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!

Python Social Auth - User is authenticated with facebook but cannot access pages

I have an issue with Facebook authentication with Python Social Auth.
I have login with Facebook, Google and Twitter.
After login, I redirect the user to my dashboard at /user/dashboard with the use of login_redirect decorator. While it works fine with Google and Twitter, I am not able to redirect the user authenticated with Facebook.
#login_required
def home(request):
user = ""
if '_auth_user_id' in request.session:
user = AuthUser.objects.get(id=request.session['_auth_user_id'])
template = 'user/index.html'
return render(request, template, context)
In Settings.py
SOCIAL_AUTH_FACEBOOK_KEY = '******'
SOCIAL_AUTH_FACEBOOK_SECRET = '*******'
SOCIAL_AUTH_FACEBOOK_SCOPE = ['email', 'public_profile', 'user_location']
SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = {
'locale': 'en_US',
'fields': 'id, name, email, age_range, about, picture, location'
}
SOCIAL_AUTH_FACEBOOK_API_VERSION = '2.10'
When I remove the login_required decorator, the user is redirected to the dashboard. But when the user tries to go to another page, there django says user is not authenticated. Is this an issue with the Facebook API or the application?
Thanks for any replies.
1) Check AUTHENTICATION_BACKENDS. Facebook authentication backend must be in this list.
2) Cleanup cookies and check that facebook user is_active on you site.
Here's a quick and dirty fix. I didn't look at all possible scenarios. This answer can be improved. First step is to get rid of the login required decorator from the redirect view. Then use the following code in the view
if request.user.is_anonymous():
# check if user logged in through facebook, csrf token will be validated by the middleware
if '_auth_user_id' in request.session:
auth_user_id = request.session['_auth_user_id']
user_obj = User.objects.filter(id=auth_user_id)
request.user = user_obj[0]
userProfile = model_to_dict(user_obj[0])
else:
# redirect user to login page
return HttpResponseRedirect('/login/')
You may have to update your app permission to provide the desired pieces of information(including email).
Go to https://developers.facebook.com/tools/explorer/, select your app and the permission you want to provide. Then generate a new test access token.

Django - How to remove the cached user data of the last session in Python Social Auth?

I'm making a sample login app with Python Social Auth.
My app provides the user the chance to login with facebook or twitter.
Let's say the name of my facebook account is boel.facebook and the one for twitter
is boel.twitter.
This is a fragment of my papeline.
def get_profile_picture(
strategy,
user,
response,
details,
is_new=False,
*args,
**kwargs
):
img_url = None
if strategy.backend.name == 'facebook':
img_url = 'http://graph.facebook.com/%s/picture?type=large' \
% response['id']
elif strategy.backend.name == 'twitter':
img_url = response.get('profile_image_url', '').replace('_normal', '')
print('THE USER IS: %s'%(user.username))
###
If the user makes login with facebook, the console prints
"THE USER IS: boel.facebook"
and the app redirects to the logged page where I have a link for making logout,
which calls the following function in views.py:
def logout(request):
auth_logout(request)
#redirect to login page.
At this point the user is back to the login page. Until here, everything is OK.
Now, if the user tries to login with twitter this time, Python Social Auth
redirects to the twitter login and after the user successfully authenticated,
the pipeline is called but the console prints:
"THE USER IS boel.facebook" again.
So, what am I doing wrong here?
Since the user is authenticated with twitter this time, the console had to print:
"THE USER IS boel.twitter".
Please help.