I'm new to Python and Flask.
I'm following this tutorial http://douglasstarnes.com/index.php/2015/05/27/easy-authentication-with-flask-login/ to have registration and login pages and have slightly modified it to hash the passwords on registration and to verify the password against the hash on login.
Initial password registration hashing works, but verifying the hashed password stored in the database against the one given in plain text via a login form does not.
The error I am receiving is against the following line on /login page and for the following reason:
if user.count() == 1 and check_password_hash(user.password, password) == True:
AttributeError: 'BaseQuery' object has no attribute 'password'
I cannot work out why I'm receiving this error. The user gets successfully queried from the database, and the user has it's hashed password within the password column.
The query I'm using and method of returning data from the password column are similar to that included in the documentation http://flask-sqlalchemy.pocoo.org/2.1/queries/#querying-records
This is my views.py (/login and the erroring line are towards the bottom)
from flask import Flask, render_template, request, abort, redirect, url_for, flash
from flask.ext.login import LoginManager, UserMixin, login_user, logout_user, login_required
from flask.ext.sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
from app import app
import os
login_manager = LoginManager(app)
login_manager.init_app(app)
login_manager.login_view = 'login'
login_manager.session_protection = "strong"
db = SQLAlchemy(app)
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String)
password = db.Column(db.String)
#login_manager.user_loader
def user_loader(user_id):
user = User.query.filter_by(id=user_id)
if user.count() == 1:
return user.one()
return None
#app.before_first_request
def init_request():
db.create_all()
#app.route('/secret')
#login_required
def secret():
return render_template('secret.html')
#app.route('/logout')
def logout():
logout_user()
return redirect(url_for('index'))
#app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'GET':
return render_template('register.html')
elif request.method == 'POST':
username = request.form['txtUsername']
password = request.form['txtPassword']
user = User.query.filter_by(username=username)
if user.count() == 0:
hashed_password = generate_password_hash(password)
user = User(username=username, password=hashed_password)
db.session.add(user)
db.session.commit()
flash('You have registered the username {0}. Please login'.format(username))
return redirect(url_for('login'))
else:
flash('The username {0} is already in use. Please try a new username.'.format(username))
return redirect(url_for('register'))
else:
abort(405)
#app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html', next=request.args.get('next'))
elif request.method == 'POST':
username = request.form['txtUsername']
password = request.form['txtPassword']
user = User.query.filter_by(username=username)
if user.count() == 1 and check_password_hash(user.password, password) == True:
login_user(user.one(), remember=True)
flash('Welcome back {0}'.format(username))
try:
next = request.form['next']
return redirect(next)
except:
return redirect(url_for('index'))
else:
flash('Invalid login')
return redirect(url_for('login'))
else:
return abort(405)
#app.route('/')
def index():
return render_template('index.html')
Does anyone know why I cannot access the password column for the user, or in fact any column? I've tried all 3 within the database, ID, username and password.
#login_manager.user_loader
def user_loader(user_id):
user = User.query.filter_by(id=user_id).first()
if user:
return user
return None
Applying the .first() executes the query, instead of storing the Query object
it will return only the first result.
.all() returns all
edit:
or you could use user = User.query.get(user_id) assuming user_id is defined as PK
In your Login Function it should be
user = User.query.filter_by(username=username).first()
if user and check_password_hash(user.password, password) == True:
login_user(user)
Now user references a real User object, as opposed to a stored query. you cannot access a User Object attribute (password) from a Query Object
Don't miss .first() or .all() after the SQLQLCHAMY ORM query.
This is cause of such error always.
Related
i am programming by flask and i have a problem with flask_login
login and authentication will complete when i enter username and password and i can get my user data in login page but after redirecting to other pages the user become anonymous! it seems that flask_login or UserMixin module does not work correctly. Please help me to solve this problem in my code
app = Flask(__name__)
app.secret_key = os.urandom(12)
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'
#app.before_request
def before_request():
g.db = Models.DATABASE
g.db.connect()
g.user=current_user
#login_manager.user_loader
def load_user(useremail):
try:
return Models.User.select().where(
Models.User.email == str(useremail)
).get()
except Models.DoesNotExist:
return None
#app.after_request
def after_request(response):
g.db.close()
return response
#app.route('/login', methods=('GET','POST'))
def login():
form=forms.Signinform()
if form.validate_on_submit():
try:
user = Models.User.get(
Models.User.email == form.email.data
)
if (user.password==hashmypassword(form.password.data)):
login_user(user)
flash("You're now logged in!")
print(user.join_date)
return redirect(url_for('home'))
else:
flash("No user with that email/password combo")
print(hashmypassword(form.password.data))
print(form.password.data)
return redirect(url_for('register'))
except Models.DoesNotExist:
flash("No user with that email/password combo")
except:
print(user.password)
flash ("incorrect")
return render_template('login.html',form=form)
#app.route('/')
#app.route('/home')
def home():
if current_user.is_authenticated:
print(current_user.email)
else:
print("No AAA")
"""Renders the home page."""
return render_template(
'index.html',
title='Home Page',
year=datetime.datetime.now().year,
)
i am authenticated in login page but not authenticated in home page!
Use the tag #login_required in your other functions in routes like this,
#app.route('/login', methods=('GET','POST'))
#login_required
def login():
form=forms.Signinform()
if form.validate_on_submit():
try:
user = Models.User.get(
Models.User.email == form.email.data
)
if (user.password==hashmypassword(form.password.data)):
login_user(user)
flash("You're now logged in!")
print(user.join_date)
return redirect(url_for('home'))
else:
flash("No user with that email/password combo")
print(hashmypassword(form.password.data))
print(form.password.data)
return redirect(url_for('register'))
except Models.DoesNotExist:
flash("No user with that email/password combo")
except:
print(user.password)
flash ("incorrect")
return render_template('login.html',form=form)
What do you mean by storing authentication?
By your question, if you want to identify logged in users, using flask-session is a good option. You can find the documentation here: https://flask-session.readthedocs.io/en/latest/
When a user logs in, you may create session variables as:
session['id'] = user_id
session['username'] = username
session['loggedin'] = true
Then you may specifically include in your routes what do you want for logged in users:
if 'loggedin' in session:
do this
else:
do that
You may also access these session variables in any route.
my problem solved using these functions in my Model class
def is_active(self):
"""True, as all users are active."""
return True
def get_id(self):
"""Return the email address to satisfy Flask-Login's requirements."""
return self.email
def is_authenticated(self):
"""Return True if the user is authenticated."""
return self.authenticated
def is_anonymous(self):
"""False, as anonymous users aren't supported."""
return False
i thought that def is_authenticated(self) is enough but all of these functions was necessary
I need to implement a simple login functionality to one of the applications that I a working on using flask-login. The authentication checking is hardcoded if condition and it is validating whether username password equals to a string given. The code is like the following:
#auth_bp.route('/login', methods=["POST"])
def loginHandler():
username = request.form.get('username')
password = request.form.get('password')
if username != 'admin' and password != 'Admin#123':
flash('Please check your login details and try again.')
return redirect(url_for('auth_bp.show_login'))
login_user(username, False)
# if the above check passes, then we know the user has the right credentials
return redirect(url_for('migration_bp.list'))
and in app.py file, I have the following code:
from flask import Flask
from flask_login import LoginManager, login_manager
from auth.auth import auth_bp
from environments.environments import environments_bp
from migration.migration import migration_bp
from logs.logs import logs_bp
UPLOAD_FOLDER = 'static/uploads'
#login_manager.user_loader
def user_loader():
# since the user_id is just the primary key of our user table, use it in the query for the user
return 1
def create_app():
app = Flask(__name__)
login_manager_copy = LoginManager()
login_manager_copy.login_view = 'auth.login'
login_manager_copy.init_app(app)
app.register_blueprint(auth_bp, url_prefix='/auth')
app.register_blueprint(environments_bp, url_prefix='/environments')
app.register_blueprint(migration_bp, url_prefix='/migration')
app.register_blueprint(logs_bp, url_prefix='/logs')
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
return app
if __name__ == '__main__':
create_app().run(debug=True)
But this is showing an error as follows:
AttributeError: module 'flask_login.login_manager' has no attribute 'user_loader'
As I don't need database, I think the User modal given in the examples are not needed. But it is giving errors without it. How can I fix this ?
EDIT:
As per the suggestion below, I have added a User class as shown below:
from flask_login import UserMixin, LoginManager, login_manager
class User(UserMixin):
def is_authenticated(self):
return True
def is_active(self):
return True
def is_anonymous(self):
return False
def get_id(self):
return int(self.id)
def __repr__(self):
return f"User('{self.username}', '{self.email}', '{self.powerlevel}')"
and added following code to app.py file:
#login_manager.user_loader
def load_user(user_id):
# since the user_id is just the primary key of our user table, use it in the query for the user
return User.query.get(int(user_id))
But it is still showing the same error.
app.py you will have this:
from flask_login import UserMixin, login_user, LoginManager, login_required, current_user, logout_user
app = Flask(__name__)
login_manager = LoginManager()
login_manager.init_app(app) #defined above app=Flask(__name__)
login_manager.login_view = 'login' #logged out users will be redirected to this route if attempting to view a route that requires login
#login_manager.user_loader
def load_user(id):
# This sets the callback for reloading a user from the session.
# The function you set should take a user ID and return a user object, or None if the user does not exist.
# id is str or int if the keys in data.USERS are str or int
# "123123" or 123123 (see next section)
try:
return data.USERS.get(str(id))
except:
return None
#app.route("/", methods=["GET","POST"])
def login():
if request.method == "POST":
if request.form["login"]:
id = request.form["id"]
password = request.form["password"]
response = data.confirmUserLogin(id, password)
if response["status"] == True:
# login the user
# Login user, You should pass the actual user object to this.
# If the user’s is_active property is False, they will not be logged in unless force is True
login_user(data.USERS[id])
flash(response["message"])
return redirect(url_for('index'))
else:
flash(response["message"])
return redirect(url_for('login'))
elif request.method == "GET":
return render_template("login.html")
data.py:
#you can use: class User(Usermixin)
"""
UserMixin pre-defines these attributes for your User class:
def is_authenticated():
...
def is_active():
...
def is_anonymous():
...
def get_id():
...
"""
# or you can just define these attributes yourself
class User():
def __init__(self, id, username, active=True):
self.id = id
self.username = username
self.active = active
def is_active(self):
# Here you should write whatever the code is
# that checks the database if your user is active
# return self.active
# for demo i just return True
return True
def is_authenticated(self):
# for demo i just return True
return True
def get_id(self):
# if you do not use Usermixin, this is important
# user_loader load_user(id) uses this get_id attribute to load the id
return self.id
# create local database of sample users
# Key are user id's : Value are User objects
USERS = {
"123123": User("123123", "user1"),
"456456": User("456456", "user2"),
"789789": User("789789", "user3", False),
}
def confirmUserLogin(id, password):
# check local db USERS for the id
if USERS.get(sso):
# get the user object (key's value)
user = USERS.get(id)
# check password
if user.password == password:
# entered password matches database password
response = {"status":True, "message":"Login Successfull!"}
return response
else:
# entered password DOES NOT match database password
response = {"status":False, "message":"Wrong password, please try again."}
return response
else:
# user does not exist
response = {"status":False, "message":"User does not exist, please try again."}
return response
I am quite sure that Flask-Login will not work without a class representing the data storage, because at some point it needs to access the database to know if a user has logged in already and other auth requirements.
Clearly as it does not impose a storage restriction, I think you can cheat the system without a database.
First create the User class as specified in the docs, https://flask-login.readthedocs.io/en/latest/
Define a class as so - all these methods are necessary.
class User:
def is_authenticated():
...
def is_active():
...
def is_anonymous():
...
def get_id():
...
Create a global variable that is of this user type and log users in and do whatever else. You do not necessarily need a database in which this class represents information in.
Let me know if you understood in the comments. Happy to help :)
I am new to Flask and I built a basic web app using Flask. This app will be used by a single user. The user must be connected in order to access any routes. What would be the easiest and most secure way to create new routes on my app and make sure that the user is logged in before they are able to access the page?
I added this route and I am able to access the page even if I am logged in.
#login_required
#app.route('/secret')
def secret():
return "hello world"
app.py
from flask import Flask, render_template, url_for, request, session, redirect
from flask_pymongo import PyMongo
import bcrypt
app = Flask(__name__)
app.config['MONGO_DBNAME'] = xxx'
app.config['MONGO_URI'] = 'xxxx'
mongo = PyMongo(app)
#app.route('/')
def index():
if 'username' in session:
return 'You are logged in as ' + session['username']
return render_template('index.html')
#app.route('/login', methods=['POST'])
def login():
users = mongo.db.users
login_user = users.find_one({'name' : request.form['username']})
if login_user:
if bcrypt.hashpw(request.form['pass'].encode('utf-8'), login_user['password']) == login_user['password']:
session['username'] = request.form['username']
return redirect(url_for('index'))
return 'Invalid username/password combination'
#app.route('/register', methods=['POST', 'GET'])
def register():
if request.method == 'POST':
users = mongo.db.users
existing_user = users.find_one({'name' : request.form['username']})
if existing_user is None:
hashpass = bcrypt.hashpw(request.form['pass'].encode('utf-8'), bcrypt.gensalt())
users.insert({'name' : request.form['username'], 'password' : hashpass})
session['username'] = request.form['username']
return redirect(url_for('index'))
return 'That username already exists!'
return render_template('register.html')
#app.route('/logout')
def logout():
session.pop('username', None)
return render_template('index.html')
if __name__ == '__main__':
app.secret_key = 'mysecret'
app.run(debug=True, port='3500')
You can use the flask-login module for this. It handles user authentication, allowing routes to be protected with the #login_required decorator.
I recommend reading the documentation, as you need to provide a class with certain properties to represent your users and you need to implement a method that returns a user based on a given identifier.
When a user is logged in, the templates can access the current_user variable and it's properties (as defined by you; name, email etc).
Here is a simple example of the python code (I have not included the static files or templates).
from flask import Flask, render_template, request, url_for, request, redirect, abort
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
from flask_pymongo import PyMongo
from flask_bcrypt import Bcrypt
from urllib.parse import urlparse, urljoin
import sys
# Import User classes
from user import User, Anonymous
# Create app
app = Flask(__name__)
# Configuration
app.config['MONGO_DBNAME'] = 'database_name'
app.config['MONGO_URI'] = 'mongo_database_uri'
app.secret_key = 'change this before production'
# Create Pymongo
mongo = PyMongo(app)
# Create Bcrypt
bc = Bcrypt(app)
# Create login manager
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.anonymous_user = Anonymous
login_manager.login_view = "login"
# ROUTES
#app.route('/')
def index():
return render_template('index.html')
#app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
if current_user.is_authenticated:
return redirect(url_for('/index'))
return render_template('login.html')
users = mongo.db.users
user_data = users.find_one({'email': request.form['email']}, {'_id' : 0 })
if user_data:
if bc.check_password_hash(user_data['password'], request.form['pass']):
user = User(user_data['title'], user_data['first_name'], user_data['last_name'], user_data['email'], user_data['password'], user_data['id'])
login_user(user)
#Check for next argument (direct user to protected page they wanted)
next = request.args.get('next')
if not is_safe_url(next):
return abort(400)
return redirect(next or url_for('profile'))
return 'Invalid email or password'
#app.route('/register', methods=['POST', 'GET'])
def register():
if request.method == 'POST':
users = mongo.db.users
existing_user = users.find_one({'email' : request.form['email']}, {'_id' : 0 })
if existing_user is None:
logout_user()
hashpass = bc.generate_password_hash(request.form['pass']).decode('utf-8')
new_user = User(request.form['title'], request.form['first_name'], request.form['last_name'], request.form['email'], hashpass)
login_user(new_user)
users.insert_one(new_user.dict())
return redirect(url_for('profile'))
return 'That email already exists!'
return render_template('register.html')
#app.route('/profile', methods=['GET'])
#login_required
def profile():
return render_template('profile.html')
#app.route('/list', methods=['GET'])
#login_required
def list():
#if current_user.id:
log(current_user.is_authenticated)
all_users = mongo.db.users.find({}, {'_id' : 0 })
return render_template('list.html', users = all_users)
#app.route('/logout', methods=['GET'])
#login_required
def logout():
logout_user()
return redirect(url_for('index'))
# Login Manager requirements
#login_manager.user_loader
def load_user(userid):
# Return user object or none
users = mongo.db.users
user = users.find_one({'id': userid}, {'_id' : 0 })
if user:
log(user)
return User(user['title'], user['first_name'], user['last_name'], user['email'], user['password'], user['id'])
return None
# Safe URL
def is_safe_url(target):
ref_url = urlparse(request.host_url)
test_url = urlparse(urljoin(request.host_url, target))
return test_url.scheme in ('http', 'https') and \
ref_url.netloc == test_url.netloc
# Run app
if __name__ == '__main__':
app.run(debug=True)
I have a functioning basic template app using flask, flask-login and flask-pymongo at https://github.com/chriswilson1982/flask-mongo-app
I currently have a simple Flask application that authenticates registered users with flask_login and the #login_required decorator. So certain pages are accessible only to registered users.
However, I want to create another layer of authentication where only admin users have access to the admin dashboard and admin login pages.
Here is my views.py file.
from flask import (render_template, flash, url_for, redirect)
from models import (db, app, User)
from forms import (RegisterForm, LoginForm)
from flask_login import (LoginManager, logout_user, login_user, login_required,
current_user)
login_manager = LoginManager(app)
login_manager.login_view = "login"
#login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
#app.route("/")
#app.route("/home")
def home():
return render_template("home.html", title="home")
#app.route("/register", methods = ["GET", "POST"])
def register():
form = RegisterForm()
if form.validate_on_submit():
username = form.username.data
email = form.email.data
password = form.password.data
confirm_password = form.confirm_password.data
user = User(username, email, password, confirm_password)
db.session.add(user)
db.session.commit()
flash("Thanks for registering, {}".format(username.capitalize()))
return redirect(url_for("login"))
return render_template("register.html", title="register",
form=form)
#app.route("/login", methods = ["GET", "POST"])
def login():
form = LoginForm()
users = User.query.all()
if form.validate_on_submit() and users != []:
email = form.email.data
password = form.password.data
user = User.query.filter_by(email=email).first()
if user.checking_password(password) and user is not None:
login_user(user)
flash("Thanks for logging in!")
return redirect(url_for("profile"))
return render_template("login.html", title="login",
form = form)
if __name__ == "__main__":
app.run(debug=True)
I tried reusing flask_login imports by renaming them with an admin_ prefix, and applying them to an admin view function, but it didn't seem to work.
from flask_login import (LoginManager as AdminManager,
logout_user as admin_logout_user,
login_user as admin_login_user,
login_required as admin_login_required,
current_user as admin_user)
admin_manager = AdminManager(app)
admin_manager.login_view = "admin_login"
#admin_manager.user_loader
def load_admin(admin_id):
return Admin.query.get(int(admin_id))
If anyone knows how I can do this, I'd be very grateful.
This is a lot easier to do in Django as an admin user page is generated out of the box, so to speak.
Typically you don’t have a separate login for admin users. Rather, you have roles on each user. So if a user has an admin role, they are able to see certain pages. Then you control access to specific routes using flask-user and the #roles_required decorator.
I have simple python flask app with flask-login implemented. I have two functions to load template called first.html and second.html both of them are protected by a #login_required decorator. Below is the code I have written
#!/usr/bin/python
from app import app
from flask import render_template,request,redirect,abort
from flask_login import login_required,url_for,login_user,logout_user,LoginManager
from app.models.models import Users
import md5
from itsdangerous import URLSafeTimedSerializer
loginManager = LoginManager()
loginManager.init_app(app)
loginManager.login_view = 'signin'
loginManager.login_message = 'Please log in'
#loginManager.user_loader
def load_user(name):
user = Users.query.get(name)
return user
#loginManager.token_loader
def load_token(token):
print 'In Token Loader'
serializer = URLSafeTimedSerializer(app.secret_key)
data = serializer.loads(token)
username = data[0]
user = Users.query.get(name)
print user
return user
#app.route('/logout')
def logout():
print 'executing logout'
logout_user()
return render_template('login.html')
#app.route('/login',methods=['GET','POST'])
def signin():
if request.method == 'GET':
return render_template('login.html')
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
salted_password = password + app.secret_key
hashedpassword = md5.new(salted_password).hexdigest()
user = Users.query.filter(Users.username == username).first()
if user == None:
return render_template('login.html')
storedPass = user.password
if storedPass == hashedpassword:
login_user(user, remember=True)
nex = request.args.get('next')
return redirect(nex or url_for('first'))
return render_template('login.html')
#app.route('/first')
#login_required
def first():
return render_template('first.html')
#app.route('/second')
#login_required
def second():
return render_template('second.html')
Now I access http://locahost:5000/login and login view is loaded. I entered the valid user and password which is authenticated successfully and gets a redirect to url_for('first')
Since first function is decorated with #login_required I am getting the login page again. As per my understanding from documentation, #token_loader called will be called and it handles the token and loads the first.html but this is not happening and login view is loaded again.
If i remove the #login_required decorator from first function, the the first.html is loaded.
I am missing something in invoking the token validation in my code and I am not sure what it is.
My User data model class looks like below
#!/usr/bin/python
from app import db,app
from itsdangerous import URLSafeTimedSerializer
class Users(db.Document):
uid = db.StringField()
username = db.StringField()
password = db.StringField()
def is_authenticated(self):
return True
def is_active(self):
return True
def is_anonymous(self):
return False
def get_id(self):
return unicode(self.uid)
def get_auth_token(self):
serializer = URLSafeTimedSerializer(app.secret_key)
return serializer.dumps([self.username,self.password])
Thanks
I changed the user_loader callback to
#loginManager.user_loader
def load_user(userid)
user = Users.query.filter(Users.uid == userid).first()
return user
and this solves the issue I am facing and it is generation a cookie called 'REMEMBER_ME' in my browser. But still I am finding a way to called the token_loader callback and when it would be called.