Flask dance example for login with Azure AD - flask

I am trying to implement SSO for one of my applications using flask-login and flask-dance. As a starting point I am using sample code given on Flask Dance website - https://flask-dance.readthedocs.io/en/v1.2.0/quickstarts/sqla-multiuser.html
Only change I did was - I replaced GitHub with my Azure AD credentials
Please find the code below:
import sys
from flask import Flask, redirect, url_for, flash, render_template
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm.exc import NoResultFound
from flask_dance.contrib.github import make_github_blueprint, github
from flask_dance.contrib.azure import make_azure_blueprint, azure
from flask_dance.consumer.storage.sqla import OAuthConsumerMixin, SQLAlchemyStorage
from flask_dance.consumer import oauth_authorized, oauth_error
from flask_login import (
LoginManager, UserMixin, current_user,
login_required, login_user, logout_user
)
# setup Flask application
app = Flask(__name__)
app.secret_key = "XXXXXXXXXXXXXX"
blueprint = make_azure_blueprint(
client_id="XXXXXXXXXXXXXXXXXXXXX",
client_secret="XXXXXXXXXXXXXXXXXXXXXXXX",
tenant="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
)
app.register_blueprint(blueprint, url_prefix="/login")
# setup database models
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///multi.db"
db = SQLAlchemy()
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
# Your User model can include whatever columns you want: Flask-Dance doesn't care.
# Here are a few columns you might find useful, but feel free to modify them
# as your application needs!
username = db.Column(db.String(1028), unique=True)
email = db.Column(db.String(1028), unique=True)
name = db.Column(db.String(1028))
class OAuth(OAuthConsumerMixin, db.Model):
provider_user_id = db.Column(db.String(1028), unique=True)
user_id = db.Column(db.Integer, db.ForeignKey(User.id))
user = db.relationship(User)
# setup login manager
login_manager = LoginManager()
login_manager.login_view = 'azure.login'
#login_manager.user_loader
def load_user(user_id):
#print(User.query.get(int(user_id)))
return User.query.get(int(user_id))
# setup SQLAlchemy backend
blueprint.storage = SQLAlchemyStorage(OAuth, db.session, user=current_user,user_required=False)
# create/login local user on successful OAuth login
#oauth_authorized.connect_via(blueprint)
def azure_logged_in(blueprint, token):
if not token:
#print(token)
flash("Failed to log in with azure.", category="error")
return False
resp = blueprint.session.get("/user")
if not resp.ok:
#print(resp)
msg = "Failed to fetch user info from Azure."
flash(msg, category="error")
return False
azure_info = resp.json()
azure_user_id = str(azure_info["id"])
#print(azure_user_id)
# Find this OAuth token in the database, or create it
query = OAuth.query.filter_by(
provider=blueprint.name,
provider_user_id=azure_user_id,
)
try:
oauth = query.one()
except NoResultFound:
oauth = OAuth(
provider=blueprint.name,
provider_user_id=azure_user_id,
token=token,
)
if oauth.user:
login_user(oauth.user)
flash("Successfully signed in with Azure.")
else:
# Create a new local user account for this user
user = User(
# Remember that `email` can be None, if the user declines
# to publish their email address on GitHub!
email=azure_info["email"],
name=azure_info["name"],
)
# Associate the new local user account with the OAuth token
oauth.user = user
# Save and commit our database models
db.session.add_all([user, oauth])
db.session.commit()
# Log in the new local user account
login_user(user)
flash("Successfully signed in with Azure.")
# Disable Flask-Dance's default behavior for saving the OAuth token
return False
# notify on OAuth provider error
#oauth_error.connect_via(blueprint)
def azure_error(blueprint, error, error_description=None, error_uri=None):
msg = (
"OAuth error from {name}! "
"error={error} description={description} uri={uri}"
).format(
name=blueprint.name,
error=error,
description=error_description,
uri=error_uri,
)
flash(msg, category="error")
#app.route("/logout")
#login_required
def logout():
logout_user()
flash("You have logged out")
return redirect(url_for("index"))
#app.route("/")
def index():
return render_template("home.html")
# hook up extensions to app
db.init_app(app)
login_manager.init_app(app)
if __name__ == "__main__":
if "--setup" in sys.argv:
with app.app_context():
db.create_all()
db.session.commit()
print("Database tables created")
else:
app.run(debug=True,port=5011)
I have also done appropriate changes in HTML file for 'azure.login'.
So after running it as python multi.py --setup database tables are getting created
and after I run python multi.py Oauth dance is actually starting but in the end I am getting error like below:
HTTP Response:
127.0.0.1 - - [28/Oct/2020 10:17:44] "?[32mGET /login/azure/authorized?code=0.<Token>HTTP/1.1?[0m" 302 -
127.0.0.1 - - [28/Oct/2020 10:17:44] "?[37mGET / HTTP/1.1?[0m" 200 -
Am I missing something? Is it a good idea to use Flask Dance and Flask Login to have SSO with Azure AD? Or I should go with MSAL only along with Flask Session?
Kindly give your valuable inputs..

Since you use Azure AD as the Flask dance provider, we need to use Microsoft Graph to get user's information. The URL should be https://graph.microsoft.com/v1.0/me. So please update the code resp = blueprint.session.get("/user") to resp = blueprint.session.get("/v1.0/me") in method azure_logged_in. Besides, please note that the azure ad user's information has different names. We also need to update the code about creating users.
For example
#oauth_authorized.connect_via(blueprint)
def azure_logged_in(blueprint, token):
if not token:
# print(token)
flash("Failed to log in with azure.", category="error")
return False
resp = blueprint.session.get("/v1.0/me")
# azure.get
if not resp.ok:
# print(resp)
msg = "Failed to fetch user info from Azure."
flash(msg, category="error")
return False
azure_info = resp.json()
azure_user_id = str(azure_info["id"])
# print(azure_user_id)
# Find this OAuth token in the database, or create it
query = OAuth.query.filter_by(
provider=blueprint.name,
provider_user_id=azure_user_id,
)
try:
oauth = query.one()
except NoResultFound:
oauth = OAuth(
provider=blueprint.name,
provider_user_id=azure_user_id,
token=token,
)
if oauth.user:
login_user(oauth.user)
flash("Successfully signed in with Azure.")
else:
# Create a new local user account for this user
user = User(
# create user with user information from Microsoft Graph
email=azure_info["mail"],
username=azure_info["displayName"],
name=azure_info["userPrincipalName"]
)
# Associate the new local user account with the OAuth token
oauth.user = user
# Save and commit our database models
db.session.add_all([user, oauth])
db.session.commit()
# Log in the new local user account
login_user(user)
flash("Successfully signed in with Azure.")
# Disable Flask-Dance's default behavior for saving the OAuth token
return False
For more details, please refer to here and here

Related

Flask SocketI) need to detect duplicate connections on opening new tab

I need to detect the duplicate session when user opens a new tab. Since I am using it for survey, I am not having any of the user's data. I am working on anonymous users.
Reading through documentation and various other thread I understood that I need to send client a session data which will be a uuid and check if the user is already authenticated for new connection.
My code is below -
from flask import Flask, render_template, session
from flask_session import Session
from flask_socketio import SocketIO, send, emit
from flask_login import LoginManager, UserMixin, current_user, login_user, logout_user, AnonymousUserMixin
import time, json, uuid, os
app = Flask(__name__)
app.config['SECRET_KEY'] = 'top-secret!'
app.config['SESSION_TYPE'] = 'filesystem'
login_manager = LoginManager(app)
login_manager.init_app(app)
Session(app)
socketio = SocketIO(app, cors_allowed_origins="*", logger=True, manage_session=False)
class User(UserMixin, object):
def __init__(self, id=None):
self.id = id
#login_manager.user_loader
def load_user(user_id):
return User.get(user_id)
time_now = 0
msg = "Hello User. Please wait other users to join. Survey will start once minimum users will join. Max waiting time " \
"is 5 min "
# connected_msg_json = json.dumps(connected_msg, indent=4)
client_count = 0
#socketio.on('message')
def handle_message(msg):
print("Connected with the client with data " + msg)
#socketio.on('connect')
def test_connect():
print("Connected")
f = open('data.json')
data = json.load(f)
minUserCount = data['minimumNoOfUser']
global client_count, time_now
if current_user.is_authenticated:
pass
else:
client_count += 1
login_user(User(id=uuid.uuid1()))
if client_count == 0:
time_now = int(time.time())
print("Total no of connected client " + str(client_count))
print("About to send the time when first user connected " + str(time_now))
send(time_now)
if client_count > minUserCount:
send("Continue", broadcast=True)
#socketio.on('disconnect')
def test_disconnect():
print('Client disconnected')
logout_user()
global client_count
client_count -= 1
print("Total no of connected client " + str(client_count))
Since I need to make sure that survey opens when there are minimum no of unique users, I decided to login the users upon connection. And if the user is already authenticated then I believe it means it's the new connection via tab.
Now I am not sure if my code is okay but the packages are incorrect or both. I have tried to resolved the error but I am stuck with this error -
ImportError: cannot import name 'ContextVar' from 'werkzeug.local' (/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/werkzeug/local.py)
This appears when I put line
app.config['SESSION_TYPE'] = 'filesystem'
else I get different error about secret key not being set.
My requirements.txt are:
Flask==2.0.2
Flask-Cors==3.0.10
Flask-SocketIO==4.3.1
gevent==21.8.0
gevent-websocket==0.10.1
greenlet==1.1.2
gunicorn==20.1.0
python-engineio==3.13.2
python-socketio==4.6.0
simple-websocket==0.5.0
websocket-client==1.2.1
websockets==10.1
Werkzeug==0.14.1
you need to update werkzeug to 2.0 +

POST on /oauth/token always use client_secret_basic

I'm trying to add oauth2 (server) on my flask app and I have some issues with the /oauth/token endpoint with client_secret_post.
My app does POST the following to it as a form:
client_id=XXX
client_secret=YYY
grant_type=client_credentials
token_endpoint_auth_method=client_secret_post
redirect_uri=http://localhost:8081/oauth-callback
And I get in the logs:
DEBUG:authlib.oauth2.rfc6749.authenticate_client:Authenticate None via "client_secret_basic" failed
127.0.0.1 - - [23/Jun/2019 18:05:26] "POST /oauth/token HTTP/1.0" 401 -
The token_endpoint_auth_method doesn't seems to change anything and it always returns {"error": "invalid_client"}.
I have tried adding TOKEN_ENDPOINT_AUTH_METHODS = ['client_secret_post'] to my class AuthorizationCodeGrant(grants.AuthorizationCodeGrant): without effects (also none of the loggers print anything).
What did I missed there ?
I have implemented things in my app more or less like the oauth2 flask example, here is some extracts:
app.py:
from app_oauth import config_oauth
...
def create_app(...):
...
config_oauth(app)
...
app_oauth.py:
from authlib.flask.oauth2 import AuthorizationServer, ResourceProtector
from authlib.flask.oauth2.sqla import (
create_query_client_func,
create_save_token_func,
create_revocation_endpoint,
create_bearer_token_validator,
)
from authlib.oauth2.rfc6749 import grants
from werkzeug.security import gen_salt
from models import db, User
from models import OAuth2Client, OAuth2AuthorizationCode, OAuth2Token
from flask import current_app
class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
def create_authorization_code(self, client, user, request):
current_app.logger.debug("create auth code")
code = gen_salt(48)
item = OAuth2AuthorizationCode(
code=code,
client_id=client.client_id,
redirect_uri=request.redirect_uri,
scope=request.scope,
user_id=user.get_user_id(),
)
db.session.add(item)
db.session.commit()
return code
def parse_authorization_code(self, code, client):
current_app.logger.debug("parse auth code")
item = OAuth2AuthorizationCode.query.filter_by(
code=code, client_id=client.client_id).first()
if item and not item.is_expired():
return item
def delete_authorization_code(self, authorization_code):
current_app.logger.debug("delete auth code")
db.session.delete(authorization_code)
db.session.commit()
def authenticate_user(self, authorization_code):
current_app.logger.debug("auth user")
return User.query.get(authorization_code.user_id)
class PasswordGrant(grants.ResourceOwnerPasswordCredentialsGrant):
def authenticate_user(self, username, password):
current_app.logger.debug("password grant auth user")
user = User.query.filter_by(name=username).first()
if user.check_password(password):
return user
class RefreshTokenGrant(grants.RefreshTokenGrant):
def authenticate_refresh_token(self, refresh_token):
current_app.logger.debug("refresh token grant")
token = OAuth2Token.query.filter_by(refresh_token=refresh_token).first()
if token and not token.revoked and not token.is_refresh_token_expired():
return token
def authenticate_user(self, credential):
current_app.logger.debug("auth user grant user")
return User.query.get(credential.user_id)
query_client = create_query_client_func(db.session, OAuth2Client)
save_token = create_save_token_func(db.session, OAuth2Token)
authorization = AuthorizationServer(
query_client=query_client,
save_token=save_token,
)
require_oauth = ResourceProtector()
def config_oauth(app):
authorization.init_app(app)
# support all grants
authorization.register_grant(grants.ImplicitGrant)
authorization.register_grant(grants.ClientCredentialsGrant)
authorization.register_grant(AuthorizationCodeGrant)
authorization.register_grant(PasswordGrant)
authorization.register_grant(RefreshTokenGrant)
# support revocation
revocation_cls = create_revocation_endpoint(db.session, OAuth2Token)
authorization.register_endpoint(revocation_cls)
# protect resource
bearer_cls = create_bearer_token_validator(db.session, OAuth2Token)
require_oauth.register_token_validator(bearer_cls())
and my blueprint:
from app_oauth import authorization
...
#bp_api_v1_auth.route("/oauth/token", methods=["POST"])
def oauth_token():
return authorization.create_token_response()
Edit: after digging it looks like it is handled by ClientCredentialsGrant which does only client_secret_basic by default, I then added:
class ClientCredentialsGrant(grants.ClientCredentialsGrant):
TOKEN_ENDPOINT_AUTH_METHODS = [
'client_secret_basic', 'client_secret_post'
]
...
authorization.register_grant(ClientCredentialsGrant)
Which now validates but respond with {"error": "unauthorized_client"}
Finally nailed it: my OAuth2Client entry in database had only authorization_code and password, client_credentials was needed to validate.

Zoho CRM Python SDK v2 initialization problem for Django

Im trying to integrate the Zoho CRM v2 SDK with my Django app.
On the Django runserver, im able to get access tokens and using the refresh method and store them in the zcrm_oauthtokens.pkl file. The sdk then automatically refreshes the access token using the refresh token, so no problem here. However on my production server (heroku) im getting this error message:
2019-01-16T11:07:22.314759+00:00 app[web.1]: 2019-01-16 11:07:22,314 - Client_Library_OAUTH - ERROR - Exception occured while fetching oauthtoken from db; Exception Message::'NoneType' object has no attribute 'accessToken'
It seems to me that the tokens are being saved to file, but when the sdk try to access them it is looking for them in a DB and not the file specified in the token_persistence_path.
In my settings.py I have this:
ZOHO_CLIENT_ID = config('ZOHO_CLIENT_ID')
ZOHO_CLIENT_SECRET = config('ZOHO_CLIENT_SECRET')
ZOHO_REDIRECT_URI = config('ZOHO_REDIRECT_URI')
ZOHO_CURRENT_USER_EMAIL = 'jamesalexander#mylastwill.co.uk'
ZOHO_PATH = os.path.join(BASE_DIR, 'wills_online', 'zoho')
zoho_config = {'apiBaseUrl': "https://www.zohoapis.com",
'currentUserEmail': ZOHO_CURRENT_USER_EMAIL,
'client_id': ZOHO_CLIENT_ID,
'client_secret': ZOHO_CLIENT_SECRET,
'redirect_uri': ZOHO_REDIRECT_URI,
'token_persistence_path': ZOHO_PATH}
and in a views file I have this:
from zcrmsdk import *
import logging
from django.shortcuts import HttpResponse
from wills.models import PersonalDetails, ZoHoRecord, WillDocument
from wills_online.decorators import start_new_thread
from wills_online.settings import zoho_config
logger = logging.getLogger(__name__)
class ZohoRunOnce:
def __init__(self):
self.already_run = False
def run_once(self):
if not self.already_run:
print('zoho init run once')
ZCRMRestClient.initialize(zoho_config)
self.already_run = True
zoho_init = ZohoRunOnce()
zoho_init.run_once()
print(zoho_config['token_persistence_path'])
def zoho_callback():
return HttpResponse(200)
#start_new_thread
def zoho_personal_details(request):
""" updates or create a user account on zoho on profile completion """
personal_details_ob = PersonalDetails.objects.get(user=request.user)
zoho_ob = ZoHoRecord.objects.get(user=request.user)
try:
if zoho_ob.account:
record = ZCRMRecord.get_instance('Accounts', zoho_ob.account)
record.set_field_value('Account_Name', request.user.email)
record.set_field_value('Name', personal_details_ob.full_name)
record.set_field_value('Email', request.user.email)
record.set_field_value('Address_Line_1', personal_details_ob.address_line_1)
record.set_field_value('Address_Line_2', personal_details_ob.address_line_2)
record.set_field_value('Post_Town', personal_details_ob.post_town)
record.set_field_value('Post_Code', personal_details_ob.post_code)
record.set_field_value('Dob_Day', personal_details_ob.dob_day)
record.set_field_value('Dob_Month', personal_details_ob.dob_month)
record.set_field_value('Dob_Year', personal_details_ob.dob_year)
record.set_field_value('Gender', personal_details_ob.sex)
record.set_field_value('Marital_Status', personal_details_ob.marital_status)
record.set_field_value('Partner_Name', personal_details_ob.partner_full_name)
record.set_field_value('Partner_Gender', personal_details_ob.partner_gender)
record.set_field_value('Partner_Email', personal_details_ob.partner_email)
record.set_field_value('Children', personal_details_ob.children)
record.set_field_value('Pets', personal_details_ob.pets)
record.update()
else:
user = ZCRMUser.get_instance(name='James Alexander')
record = ZCRMRecord.get_instance('Accounts')
record.set_field_value('Account_Owner', user)
record.set_field_value('Account_Name', request.user.email)
record.set_field_value('Name', personal_details_ob.full_name)
record.set_field_value('Email', request.user.email)
record.set_field_value('Address_Line_1', personal_details_ob.address_line_1)
record.set_field_value('Address_Line_2', personal_details_ob.address_line_2)
record.set_field_value('Post_Town', personal_details_ob.post_town)
record.set_field_value('Post_Code', personal_details_ob.post_code)
record.set_field_value('Dob_Day', personal_details_ob.dob_day)
record.set_field_value('Dob_Month', personal_details_ob.dob_month)
record.set_field_value('Dob_Year', personal_details_ob.dob_year)
record.set_field_value('Gender', personal_details_ob.sex)
record.set_field_value('Marital_Status', personal_details_ob.marital_status)
record.set_field_value('Partner_Name', personal_details_ob.partner_full_name)
record.set_field_value('Partner_Gender', personal_details_ob.partner_gender)
record.set_field_value('Partner_Email', personal_details_ob.partner_email)
record.set_field_value('Children', personal_details_ob.children)
record.set_field_value('Pets', personal_details_ob.pets)
response = record.create()
# save account id to db for future updates
zoho_ob.account = response.details['id']
zoho_ob.save()
except ZCRMException as ex:
logger.log(1, ex.status_code)
logger.log(1, ex.error_message)
logger.log(1, ex.error_details)
logger.log(1, ex.error_content)
print(ex.status_code)
print(ex.error_message)
print(ex.error_content)
print(ex.error_details)
Ive tried running ZCRMRestClient.initialize(zoho_config) in settings.py, with no luck.
My method for getting the access token and refresh token, which seems to work is:
import os
import pprint
from sys import argv
import django
import requests
import zcrmsdk
from django.conf import settings
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'wills_online.settings')
django.setup()
def zoho_refresh_token(code):
""" supply a self client token from the zoho api credentials from web site """
zoho_config = {"apiBaseUrl": "https://www.zohoapis.com",
"currentUserEmail": settings.ZOHO_CURRENT_USER_EMAIL,
"client_id": settings.ZOHO_CLIENT_ID,
"client_secret": settings.ZOHO_CLIENT_SECRET,
"redirect_uri": settings.ZOHO_REDIRECT_URI,
"token_persistence_path": settings.ZOHO_PATH}
pprint.pprint(zoho_config)
print('working')
address = f'https://accounts.zoho.com/oauth/v2/token?code={code}&redirect_uri={settings.ZOHO_REDIRECT_URI}&client_id={settings.ZOHO_CLIENT_ID}&client_secret={settings.ZOHO_CLIENT_SECRET}&grant_type=authorization_code'
response = requests.post(address)
data = response.json()
pprint.pprint(data)
zcrmsdk.ZCRMRestClient.initialize(zoho_config)
oauth_client = zcrmsdk.ZohoOAuth.get_client_instance()
refresh_token = data['refresh_token']
print(type(refresh_token))
oauth_client.generate_access_token_from_refresh_token(refresh_token, settings.ZOHO_CURRENT_USER_EMAIL)
print(refresh_token)
print('finished')
if name == 'main':
zoho_refresh_token(argv[1])
This is driving me mad. Help would be greatly appreciated. This is my first post so go easy, lol.
For future reference, you will need to define persistence_handler_class and persistence_handler_path in your configuration dictionary. You will also need a handler class and a user-defined model to store the results. Sample code follows:
# settings.py
import zcrmsdk
configuration_dictionary = {
'apiBaseUrl': 'https://www.zohoapis.com',
'apiVersion': 'v2',
'currentUserEmail': ZOHO_CURRENT_USER_EMAIL,
'sandbox': 'False',
'applicationLogFilePath': '',
'client_id': ZOHO_CLIENT_ID,
'client_secret': ZOHO_CLIENT_SECRET,
'redirect_uri': ZOHO_REDIRECT_URI,
'accounts_url': 'https://accounts.zoho.com',
'access_type': 'online',
'persistence_handler_class': ZOHO_HANDLER_CLASS,
'persistence_handler_path': ZOHO_HANDLER_PATH,
}
zcrmsdk.ZCRMRestClient.initialize(configuration_dictionary)
# zoho.models.py
from django.db import models
from zcrmsdk.OAuthClient import ZohoOAuthTokens
class ZohoOAuthHandler:
#staticmethod
def get_oauthtokens(email_address):
oauth_model_instance = ZohoOAuth.objects.get(user_email=email_address)
return ZohoOAuthTokens(oauth_model_instance.refresh_token,
oauth_model_instance.access_token,
oauth_model_instance.expiry_time,
user_email=oauth_model_instance.user_email)
#staticmethod
def save_oauthtokens(oauth_token):
defaults = {
'refresh_token': oauth_token.refreshToken,
'access_token': oauth_token.accessToken,
'expiry_time': oauth_token.expiryTime,
}
ZohoOAuth.objects.update_or_create(user_email=oauth_token.userEmail, defaults=defaults)
class ZohoOAuth(models.Model):
refresh_token = models.CharField(max_length=250)
access_token = models.CharField(max_length=250)
expiry_time = models.BigIntegerField()
user_email = models.EmailField()
In this example ZOHO_HANDLER_CLASS = 'ZohoOAuthHandler' and ZOHO_HANDLER_PATH = 'zoho.models'
The first time you go to use this you will need a grant_token from https://accounts.zoho.com/developerconsole. For the scope use aaaserver.profile.READ,ZohoCRM.modules.ALL to start (see https://www.zoho.com/crm/developer/docs/api/oauth-overview.html#scopes)
Before you can use the api you'll need to run the code below in a django shell. This uses a grant token to generate your initial access and refresh tokens. Afterwards, the api should handle refreshing your access token.
grant_token = GRANT_TOKEN
import zcrmsdk
oauth_client = zcrmsdk.ZohoOAuth.get_client_instance()
oauth_tokens = oauth_client.generate_access_token(grant_token)

Why does Flask-Security Cause a new KVSession Record for Each Request?

I'm trying out using Flask-KVSession as an alternative session implementation for a Flask web site. I've created a test website (see Code 1 below). When I run this, I can use the browser to store values into the session by navigating between the various resources in my web browser. This works correctly. Also, when I look at the sessions table in the resulting SQLite database, I see a single record that was being used to store this session the entire time.
Then I try to add Flask-Security to this (see Code 2 below). After running this site (making sure to first delete the existing test.db sqlite file), I am brought to the login prompt and I log in. Then I proceed to do the same thing of jumping back and forth between the resources. I get the same results.
The problem is that when I look in the sqlitebrowser sessions table, there are 8 records. It turns out a new session record was created for EACH request that was made.
Why does a new session record get created for each request when using Flask-Security? Why isn't the existing session updated like it was before?
Code 1 (KVSession without Flask-Security)
import os
from flask import Flask, session
app = Flask(__name__)
app.secret_key = os.urandom(64)
#############
# SQLAlchemy
#############
from flask.ext.sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)
DB_DIR = os.path.dirname(os.path.abspath(__file__))
DB_URI = 'sqlite:////{0}/test.db'.format(DB_DIR)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URI
#app.before_first_request
def create_user():
db.create_all()
############
# KVSession
############
from simplekv.db.sql import SQLAlchemyStore
from flask.ext.kvsession import KVSessionExtension
store = SQLAlchemyStore(db.engine, db.metadata, 'sessions')
kvsession = KVSessionExtension(store, app)
#app.route('/a')
def a():
session['last'] = 'b'
return 'Thank you for visiting A!'
#app.route('/b')
def b():
session['last'] = 'b'
return 'Thank you for visiting B!'
#app.route('/c')
def c():
return 'You last visited "{0}"'.format(session['last'])
app.run(debug=True)
Code 2 (KVSession WITH Flask-Security)
import os
from flask import Flask, session
app = Flask(__name__)
app.secret_key = os.urandom(64)
#############
# SQLAlchemy
#############
from flask.ext.sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)
DB_DIR = os.path.dirname(os.path.abspath(__file__))
DB_URI = 'sqlite:////{0}/test.db'.format(DB_DIR)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URI
###########
# Security
###########
# This import needs to happen after SQLAlchemy db is created above
from flask.ext.security import (
Security, SQLAlchemyUserDatastore, current_user,
UserMixin, RoleMixin, login_required
)
# Define models
roles_users = db.Table('roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
class Role(db.Model, RoleMixin):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
active = db.Column(db.Boolean())
confirmed_at = db.Column(db.DateTime())
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='dynamic'))
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
#app.before_first_request
def create_user():
db.create_all()
user_datastore.create_user(email='test#example.com', password='password')
db.session.commit()
############
# KVSession
############
from simplekv.db.sql import SQLAlchemyStore
from flask.ext.kvsession import KVSessionExtension
store = SQLAlchemyStore(db.engine, db.metadata, 'sessions')
kvsession = KVSessionExtension(store, app)
#app.route('/a')
#login_required
def a():
session['last'] = 'b'
return 'Thank you for visiting A!'
#app.route('/b')
#login_required
def b():
session['last'] = 'b'
return 'Thank you for visiting B!'
#app.route('/c')
#login_required
def c():
return 'You last visited "{0}"'.format(session['last'])
app.run(debug=True)
Version Info
Python 2.7.3
Flask==0.9
Flask==0.9
Flask-KVSession==0.3.2
Flask-Login==0.1.3
Flask-Mail==0.8.2
Flask-Principal==0.3.5
Flask-SQLAlchemy==0.16
Flask-Security==1.6.3
SQLAlchemy==0.8.1
Turns out this is related to a known problem with flask-login (which flask-security uses) when flask-login is used with a session storage library like KVSession.
Basically, KVSession needs to update the database with the new session information whenever data in the session is created or modified. And in the sample above, this happens correctly: the first time I hit a page, the session is created. After that, the existing session is updated.
However, in the background the browser sends a cookie-less request to my web server looking for my favicon. Therefore, flask is handling a request to /favicon.ico. This request (or any other request that would 404) is still handled by flask. This means that flask-login will look at the request and try to do its magic.
It so happens that flask-login doesn't TRY to put anything into the session, but it still LOOKS like the session has been modified as far as KVSession is concerned. Because it LOOKS like the session is modified, KVSession updates the database. The following is code from flask-login:
def _update_remember_cookie(self, response):
operation = session.pop("remember", None)
...
The _update_remember_cookie method is called during the request lifecycle. Although session.pop will not change the session if the session doesn't have the "remember" key (which in this case it doesn't), KVSession still sees a pop and assumes that the session changes.
The issue for flask-login provides the simple bug fix, but it has not been pushed into flask-login. It appears that the maintainer is looking for a complete rewrite, and will implement it there.

How to get logged in user's uid from session in Django?

I have implemented a registration/login/authentication system using this Django guide.
But, how would I access a user's information from my views so I can send the user's information to a template file?
I want to be able to access a user's ID so I can submit a form with the user's ID attached to the form.
In case anyone wants to actually extract a user ID from an actual Session object (for whatever reason - I did!), here's how:
from django.contrib.sessions.models import Session
from django.contrib.auth.models import User
session_key = '8cae76c505f15432b48c8292a7dd0e54'
session = Session.objects.get(session_key=session_key)
session_data = session.get_decoded()
print session_data
uid = session_data.get('_auth_user_id')
user = User.objects.get(id=uid)
Credit should go to Scott Barnham
There is a django.contrib.auth.models.User object attached to the request; you can access it in a view via request.user. You must have the auth middleware installed, though.
This:
def view(request):
if request.user.is_authenticated:
user = request.user
print(user)
# do something with user
An even easier way to do this is to install django-extensions and run the management command print_user_for_session.
And this is how they do it:
https://github.com/django-extensions/django-extensions/blob/master/django_extensions/management/commands/print_user_for_session.py
In case hwjp solution doesn't work for you ("Data is corrupted"), here is another solution:
import base64
import hashlib
import hmac
import json
def session_utoken(msg, secret_key, class_name='SessionStore'):
key_salt = "django.contrib.sessions" + class_name
sha1 = hashlib.sha1((key_salt + secret_key).encode('utf-8')).digest()
utoken = hmac.new(sha1, msg=msg, digestmod=hashlib.sha1).hexdigest()
return utoken
def decode(session_data, secret_key, class_name='SessionStore'):
encoded_data = base64.b64decode(session_data)
utoken, pickled = encoded_data.split(b':', 1)
expected_utoken = session_utoken(pickled, secret_key, class_name)
if utoken.decode() != expected_utoken:
raise BaseException('Session data corrupted "%s" != "%s"',
utoken.decode(),
expected_utoken)
return json.loads(pickled.decode('utf-8'))
s = Session.objects.get(session_key=session_key)
decode(s.session_data, 'YOUR_SECRET_KEY'))
credit to: http://joelinoff.com/blog/?p=920