I am using the OAUTH API to verify access to the users google calendar. They are not logging into my system using OAUTH, just accepting my site accessing their data.
The problem is, if the user is logged out of their google account and after they hit verify, it force logs them out of their session and I have no way of linking them back up.
This DOES work if they are already logged into the google account in their browser session and hit accept, they will be redirected to the right page.
I replicate the error when the cache, cookies are clear and they need to relogin into their google account to verify.
I've tried storing the session ID, etc, but the request parameter is not containing the same request data as the initial view, so there is a conflict in the data I am trying to retrieve.
The user is logged in using the standard Django libraries for the credentials model.
CODE
FLOW = flow_from_clientsecrets(
CLIENT_SECRETS,
scope='https://www.googleapis.com/auth/calendar.readonly',
redirect_uri='http://127.0.0.1:8000/oauth2callback')
'''''''''''''''''''''''''''''''''''''''''''''''''''
Main function dealing with auth verification
'''''''''''''''''''''''''''''''''''''''''''''''''''
def index(request):
current_user = User.objects.get(username=request.user.username)
storage = Storage(CredentialsModel, 'id', current_user, 'credential')
credential = storage.get()
if credential is None or credential.invalid == True:
FLOW.params['state'] = xsrfutil.generate_token(settings.SECRET_KEY,
request.user.id)
authorize_url = FLOW.step1_get_authorize_url()
return redirect(authorize_url)
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''
User then calls the data function once authenticated
''''''''''''''''''''''''''''''''''''''
def auth_return(request):
print("THE CURRENTLY REQUESTED USER IN THIS SESSION REQUEST IS %s"%(request.user.username))
credential = FLOW.step2_exchange(request.REQUEST)
try:
current_user = User.objects.get(id=request.user.id)
except:
return HttpResponseRedirect("/login")
storage = Storage(CredentialsModel, 'id', current_user, 'credential')
storage.put(credential)
return HttpResponseRedirect("/get_cal")
Ok, so this was a bit more involved than I thought.
I fixed this by adding a state parameter of the currently logged in username.
logged_in_username = request.user.username
user_data = {'var_test' : logged_in_username}
pass_param = urllib.urlencode(user_data)
FLOW.params['state']=pass_param
authorize_url = FLOW.step1_get_authorize_url()
This gave me the ability to query the user from the DB via the User model in Django contrib. I parsed out the state var from the URL:
#Get the currently logged in username
user_variable_data = str(FLOW.params['state'])
#get rid of the var_test= preprended text data for parsing reasons
user_variable_data = user_variable_data[9:]
#Get that user from the database in the form of a user object
current_user = User.objects.get(username=user_variable_data)
and then built a custom backend authentication file to auth the user without a password to maintain the request like nothing weird ever even happened.
user = authenticate(username=user_variable_data)
login(request, user)
print("AUTHENTICATED")
Appended this to settings file
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'procrastinate.auth_backend.PasswordlessAuthBackend',
)
Custom Backend File
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User
class PasswordlessAuthBackend(ModelBackend):
"""Log in to Django without providing a password.
"""
def authenticate(self, username=None):
try:
return User.objects.get(username=username)
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
Related
I am using Django allauth to associate already logged in in Django users with their social accounts. For frontend I use Vue js, it sends in body pw and username.
My idea was to save Django user id in session parameters after login and grab it back during the allauth flow. As far as I understood it should work: Passing a dynamic state parameter using django-allauth during social login
However, by some reason when I am trying to access the session parameter in allauth flow it is empty. The id is set in session and it should be fine. Currently, it is tested on Google OAuth2.
My login view:
#api_view(('POST',))
def login_user(request):
# get credentials from Body
request_data = json.loads(request.body)
print(request_data) # DEBUG
username = request_data['username']
password = request_data['password']
try:
# get user instance
if not User.objects.filter(username=username).exists():
raise ValidationError("The user does not exist")
else:
if username and password:
# authenticate user
user = authenticate(username=username, password=password)
if not user:
raise ValidationError("Incorrect password or username")
if not user.is_active:
raise ValidationError("This user is no longer active")
print(user) # DEBUG
# set session cookie with user id
print('SESSION UID', user.id)
request.session['user_id'] = user.id # works
session_uid = request.session.get('user_id', 'LOGIN NO SESSION UID')
# get RefreshToken for user
token_refresh = RefreshToken.for_user(user)
# response dict
data = {'token': str(token_refresh.access_token), 'refresh_token': str(token_refresh)}
# convert to utf-8 byte format for decoding
access_token = bytes(str(token_refresh.access_token), encoding="utf-8")
# decode token to get additional data
valid_data = TokenBackend(algorithm='HS256').decode(access_token, verify=False)
# add additional data to response dict
data['uuid'] = valid_data['user_id']
data['validUntil'] = valid_data['exp'] * 1000
data['clientId'] = 'default'
print(valid_data['user_id'])
return JsonResponse(data, status=status.HTTP_200_OK)
except ValidationError as v:
return HttpResponse(f"Validation error: {v}", status=status.HTTP_400_BAD_REQUEST)
except User.DoesNotExist:
raise HttpResponse("User does not exists", status=status.HTTP_404_NOT_FOUND)
My allauth signals, I always get "User Id not found":
EDITED
#receiver(user_logged_in)
def logged_in(request, user, **kwargs):
print(user) # Here django creates a new user using the chosen gmail account from login popup
request = kwargs['request']
user_id = request.session.get('user_id', 'User Id not found')
print('SESSION UID AUTH FLOW logged_in', user_id)
#receiver(pre_social_login)
def get_data(request, sociallogin, **kwargs):
session_uid = request.session.get("user_id", 'User Id not found')
print('SESSION UID AUTH FLOW get_data', session_uid)
print(request.user) # Here django creates a new user using the chosen gmail account from login popup
My adapter:
Here django creates a new user using the chosen gmail account from login popup
class MySocialAccountAdapter(DefaultSocialAccountAdapter):
def pre_social_login(self, request, sociallogin):
user_request = request.user
print('MySocialAccountAdapter', user_request, user_request.id, user_request.email)
user = sociallogin.user
if user.id:
print('MySocialAccountAdapter', user.id)
print('MySocialAccountAdapter', user.username)
return
if not user.email:
return
try:
user = User.objects.get(email=user.email) # if user exists, connect the account to the existing account and login
print('MySocialAccountAdapter', user)
sociallogin.connect(request, user)
except User.DoesNotExist:
print('User does not exist')
pass
I'm trying to add google auth service to my site, it is working correctly in localhost, but in the server, I've got an error when a new user tries to sign up although the old users can connect their account to google account without any problems
get() returned more than one User -- it returned 2!
there is no duplicated either in the user model or social account
model.
before I added social auth users could only log in or sign in with their email addresses, so now my AUTHENTICATION_BACKENDS is like that:
AUTHENTICATION_BACKENDS = (
'user_profile.backends.EmailBackend',
'allauth.account.auth_backends.AuthenticationBackend',)
and my EmailBackend file in like this:
class EmailBackend(ModelBackend):
def authenticate(self, username=None, password=None, **kwargs):
UserModel = get_user_model()
try:
user = UserModel.objects.get(email=username.lower())
except UserModel.DoesNotExist:
return None
else:
if user.check_password(password):
return user
return None
class MySocialAccountAdapter(DefaultSocialAccountAdapter):
def pre_social_login(self, request, sociallogin):
user = sociallogin.user
if user.id:
return
try:
customer = User.objects.get(email=user.email) # if user exists, connect the account to the existing account and login
sociallogin.state['process'] = 'connect'
perform_login(request, customer, 'none')
except User.DoesNotExist:
pass
and in my settings.py:
SOCIALACCOUNT_ADAPTER = 'user_profile.backends.MySocialAccountAdapter'
thanks a lot, it has been a disaster
I'm using Github-Flask to authenitcate users on my app. I use github.authorize(scope='user:email'). How can I get the logged in user's email?
github = GitHub(app)
user = None
#app.route('/login')
def login():
if user.username:
return redirect(url_for('index'))
return github.authorize(scope='user:email')
#github.access_token_getter
def token_getter():
if user is not None:
return user.github_access_token
#app.route('/github-callback')
#github.authorized_handler
def authorized(oauth_token):
if oauth_token is None:
flask.flash("Authorization failed.")
return redirect(url_for('index'))
global user
user.username = oauth_token
return redirect(url_for('index'))
The login route is only redirecting to GitHub's authorization page, it has no way to access the user data yet because the user isn't logged in. Once you get to the authorized callback, you can make API calls to GitHub.
In the authorized route, use github.get to call the user API endpoint.
data = github.get('user')
email = data['email']
Also, do not use global user to store the logged in user. See Are global variables thread safe in flask? Instead, store the user id in session, and load the user into g, as shown in GitHub-Flask's full example.
I'm using Django 1.8.4 on Python 3, and attempting to create an auth backend which validates a cookie from a legacy ColdFusion web site and create / log the Django user in after checking the value in a database. In settings, I am including the backend:
AUTHENTICATION_BACKENDS = (
'site_classroom.cf_auth_backend.ColdFusionBackend',
)
And the code for the backend itself; SiteCFUser is a model against the SQL Server database user model which contains the active cookie token value:
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from users.models import SiteCFUser
class ColdFusionBackend(ModelBackend):
"""
Authenticates and logs in a Django user if they have a valid ColdFusion created cookie.
ColdFusion sets a cookie called "site_web_auth"
Example cookie: authenticated#site+username+domain+8E375588B1AAA9A13BE03E401A02BC46
We verify this cookie in the MS SQL database 'site', table site_users, column user_last_cookie_token
"""
def authenticate(self, request):
User = get_user_model()
print('Hello!')
token=request.COOKIES.get('site_web_auth', None)
print('Token: ' + token)
cookie_bites = token.split('+')
if cookie_bites[0] != "authenticated#site":
# Reality check: not a valid site auth cookie
return None
username = cookie_bites[1]
cf_token = cookie_bites[3]
try:
site_user = SiteCFUser.objects.using('mssqlsite').filter(cf_username=username)
except:
# No user found; redirect to login page
return None
if site_user[0].cftoken == cf_token:
try:
# Does the user exist in Django?
user = User.objects.get(username=username)
except:
# User does not exist, has a valid cookie, create the User.
user = User(username=username)
user.first_name = site_user[0].cf_first_name
user.last_name = site_user[0].cf_last_name
user.email = site_user[0].cf_email
user.save()
else:
return None
def get_user(self, user_id):
User = get_user_model()
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
The problem is, the backend doesn't seem to be called when hitting a URL with a view with #login_required, or even trying to log in through a form with username and password. If I force an error by changing the name of the class in settings, or change the name of the class in cf_auth_backend.py, I do get an error. However, none of the print statements show up in the console. I'm clearly missing something here: any idea what I'm not doing right?
While the accepted answer might have helped the OP, it's not a general answer to the question's title.
Authentication back ends do work simply by listing them in AUTHENTICATION_BACKENDS. But they may appear to be ignored
for various reasons, e.g.:
urls.py needs to point to something like django.contrib.auth.views.login
url(r'^accounts/login/$', django.contrib.auth.views.login)
if it's pointing to some other authentication app. AUTHENTICATION_BACKENDS
may not work.
the authenticate() method must accept a password keyword, either through
password=None or **kwargs. Probably true for username too. It won't
be called if it doesn't accept that keyword argument.
Authentication backends doesn't work that way. They won't be called on each request or on requests where authentication is required.
If you want to log in user based on some cookie, you should call authentication in middleware.
Here is my middlware:
class BasicAuthMiddleware():
def process_request(self, request):
if request.META.get("HTTP_AUTHORIZATION"):
encoded_auth = request.META.get("HTTP_AUTHORIZATION")
encoded_auth = encoded_auth[6:]
auth = base64.b64decode(encoded_auth)
auth_email = auth.split(":")[0]
auth_password = auth.split(":")[1]
user = authenticate(username=auth_email, password=auth_password)
Here is an example of the rpc/api call we make to login:
#rpc("user", "signin")
#pre("email", validate_email)
def _(request, user, email, password):
user = authenticate(username=email, password=password)
if not user:
raise ApiException(400, "The email or password you entered is incorrect.")
if not user.is_active:
raise ApiException(403, "This account has been deactivated.")
login(request, user)
return user.toDict()
Ihave included the middleware in setting.py
I get this error when using postman trying to an api call to this:
#rpc("reservation", "listCurrent", loggedIn=True)
def _(request, user):
return [dict(r.toDict(), **{ 'cancelationPolicy': evaluated_cancelation_policies(user, r.user, r.resource, r.modality, r.timeFrom, r.timeTo, r.count) }) for r in Reservation.objects.filter(user=user, deleted=False, timeTo__gt=timezone.now()).order_by('-timeFrom', '-timeTo')]
It seems that user variable is equal to none. Since normally it is part of the request but in the middleware process_request can only take 2 arguments like self, request.
I feel like I am not understanding something here. I am pretty new to django so just trying to figure this out. How would I go about using my basic http authentication middleware to authenticate in this case?