I'm building a very simple Flask application, when I implemented load_user from flask-login, I started having errors because the function was getting None passed to it and was trying to query using a None value.
from flask_login import LoginManager
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
#login_manager.user_loader
def load_user(id):
return User.query.get(id)
Possibly relevant files:
auth/__init__.py:
from flask import Blueprint
auth = Blueprint('auth', __name__, url_prefix='/auth')
from . import views
auth/views.py:
from flask import render_template, redirect, url_for, flash, session
from flask_login import login_user
from app.forms import LoginForm
from app.models import User
from app.queries import get_user
from . import auth
#auth.route('/login', methods=['GET', 'POST'])
def login():
login_form = LoginForm()
context = {
'login_form' : login_form
}
username = login_form.username.data
password = login_form.password.data
if login_form.validate_on_submit():
user_query = get_user(username)
if user_query:
db_password = user_query.password
if password == db_password:
registeredUser = User(username = username,
password = password)
login_user(registeredUser)
flash('Bienvenido de nuevo!', 'alert alert-success alert-dismissible')
return redirect(url_for('hello'))
else:
flash('La información no coincide', 'alert alert-danger')
else:
flash('El usuario no existe.', 'alert alert-danger')
return redirect(url_for('index'))
return render_template('login.html', **context)
I'm not using the username as the primary key, I have a separate id column for that.
Assuming, from context, that user_query = get_user(username) is actually a User object and not a query, making a new User and passing it to login_user before persisting it mean that its id will be be None.
The easy fix would be to replace
if password == db_password:
registeredUser = User(username = username,
password = password)
login_user(registeredUser)
with
if password == user_query.password:
login_user(user_query)
and then to consider renaming user_query to user so that the code is clearer.
Also, give some thought to not storing user password in the clear. The Flask Mega Tutorial has a chapter that'll walk you through a way to store passwords encrypted.
Related
I am trying to build a web app with login and signup in the nav-bar/header. When I open the web app using index.html, the login and signup option shows at the top (in nav-bar) but when I do python -m flask run, it never shows that login/signup option. I will attach a snippet of my app.py
from distutils.log import debug
from operator import imod
from pickle import FALSE
import re
from flask import Flask,render_template,url_for,redirect,flash,request
from flask_sqlalchemy import SQLAlchemy
import os
from flask_login import LoginManager,login_user,login_required,logout_user,current_user
from flask_migrate import Migrate
from forms import RegistrationForm,LoginForm
from models import User
from flask_bcrypt import Bcrypt
from flask_login import LoginManager
app = Flask(__name__)
#* ATTACHING DATABASE TO PROJECT FOR USER CREATION
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///mydb.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
#* ENCRYPTING THE PASSWORD text
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)
# GENERATING SECRET-KEY
SECRET_KEY = os.urandom(32)
app.config['SECRET_KEY'] = SECRET_KEY
# CREATING THE MIGRATIONS
migrate = Migrate(app, db)
# login Manager
login_manager = LoginManager()
login_manager.login_view = 'login'
login_manager.init_app(app)
#login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
#app.route('/',methods=['GET'])
def index():
return render_template("index.html",user=current_user)
#app.route('/how_to_play')
#login_required
def how_to_play():
return render_template("how_to_play.html")
#app.route('/play')
def play():
return render_template("play.html")
#app.route('/register', methods = ['POST','GET'])
def register():
form = RegistrationForm()
if form.validate_on_submit():
hashed_password = bcrypt.generate_password_hash(form.password1.data).decode('utf-8')
user = User(username =form.username.data, email = form.email.data,password=hashed_password)
db.session.add(user)
db.session.commit()
return redirect(url_for('login'))
return render_template('registration/registration.html', form=form)
#app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user and bcrypt.check_password_hash(user.password, form.password.data):
login_user(user)
return render_template("index.html",user=current_user)
flash('Invalid email address or Password.')
return render_template('registration/login.html', form=form)
#app.route("/logout")
def logout():
logout_user()
return render_template("index.html",user=current_user)
#app.route('/share/score/', methods = ['GET'])
def share_score():
return render_template('share_score.html')
if __name__ == "__main__":
app.run(debug=True)
Not sure where I am going wrong. Any idea would be appreciated.
Because Flask template doesn't load navbar. Route in Flask doesn't work like route in HTML. In HTML we only need add route like src="/navbar.html", but if the route isn't added in code in Flask, it does not appear when render template.
So you can follow this step https://www.geeksforgeeks.org/navigation-bars-in-flask/
Below I tried to create a very reductive and simple flask_login implementation. While it works, I just want to make sure I'm doing it the correct way. In particular, is my password authentication method correct? By correct I mean, should I be using another flask_login function to do this check for me? Do I have any unnncessary code here?
Still new to python and flask, any advice/edits would be appreciated.
Username/password check:
if (username, password) in users_db.items():
login_user(User(username))
return redirect(request.args.get("next"))
else:
return abort(401)
else:
return Response('''
<form action="" method="post">
<p><input type=text name=username>
<p><input type=password name=password>
<p><input type=submit value=Login>
</form>
'''
Entire flask_login attempt:
from flask import Flask, jsonify, render_template, request, url_for, redirect, session, abort, Response
from flask_login import LoginManager, UserMixin, login_required, login_user, logout_user
from flask_wtf import Form
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired
#################### Instantiate APP ###################################
'''Application Factory'''
app = Flask(__name__)
app.config['SECRET_KEY'] = 'shhsecret' #make this more random and secret, i recommend using os.urandom(50)
#################### Authentication ####################################
# flask-login
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = "login"
class LoginForm(Form):
username = StringField('Your username', validators=[DataRequired()])
password = PasswordField('Your password', validators=[DataRequired()])
submit = SubmitField('Sign In')
# silly user model
class User(UserMixin):
def __init__(self, username):
self.id = username
self.password = users_db[username]
def __repr__(self):
return "%s/%s" % ( self.id, self.password)
def is_active(self):
return True
#users database (used dictionary just as an example)
users_db = { 'bill':'password1'
,'jondoe': 'password2'
,'elonmusk' : 'passwordtesla'
}
# create users from our users database above
users_activated = {User(key) for (key,value) in users_db.items()}
# some protected url
#app.route('/protectedurl')
#login_required
def protectedurl_func():
return Response("Hello World!")
# somewhere to login
#app.route("/login", methods=["GET", "POST"])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
if (username, password) in users_db.items():
login_user(User(username))
return redirect(request.args.get("next"))
else:
return abort(401)
else:
return Response('''
<form action="" method="post">
<p><input type=text name=username>
<p><input type=password name=password>
<p><input type=submit value=Login>
</form>
''')
# somewhere to logout
#app.route("/logout")
#login_required
def logout():
logout_user()
return Response('<p>Logged out</p>')
# handle login failed
#app.errorhandler(401)
def page_not_found(e):
return Response('<p>Login failed</p>')
# callback to reload the user object
#login_manager.user_loader
def load_user(userid):
return User(userid)
if __name__ == '__main__':
app.run(debug=True, use_reloader=True)
Everything works. However, there are a few principles you should consider when you are handling user data:
Passwords should never be stored in the database as was given by the user
For code re-usability, consider separation of concerns
Werkzeug is normally used to do password hashing. When a password has been “hashed” it means it has been turned into a scrambled representation of itself.
This is how it works in a python shell:
>>> from werkzeug.security import generate_password_hash
>>> hash = generate_password_hash('my_password')
>>> hash
'pbkdf2:sha256:150000$aRIbsDyl$90ae44b1a5c679e08685c75ff0750df7c6670582a5839072d35a713316816760'
>>>
my_password has been transformed into a long encoded string through a series of cryptographic operations that have no known reverse operation, which means that a person that obtains the hashed password will be unable to use it to obtain the original password.
To verify a user's password, you can do:
>>> from werkzeug.security import check_password_hash
>>> check_password_hash(hash, 'my_password')
True
>>> check_password_hash(hash, 'another_password')
False
>>>
This password hash is what you should store in your database:
from app import db
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
password_hash = db.Column(db.String(128))
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
You can now create the login logic in your routes:
#app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user is None or not user.check_password(form.password.data):
flash('Invalid username or password')
return redirect(url_for('login'))
login_user(user, remember=form.remember_me.data)
return redirect(url_for('index'))
return render_template('login.html', title='Sign In', form=form)
The validation checks basically try to find out if the user already exists in the database. If they do, and their username and password is correct, then they are logged in. Otherwise, they are redirected to try logging in again.
As far as separation of concerns goes, what you want to do is you ensure that your application is built using modules. I mean, there is a module that handles database issues, another that handles views, another errors et cetera.
I've created a Python Flask site with a login form and a signup form. Both of these are working and when a user signs up, their email, name and password (sha256 hashed) are stored in a sqlite database. I now need to use the flask_change_password library to create a form that will allow users to change their password and I'm just struggling on this.
First, I'm using PyCharm and installed flask-change-password for my env but when I add this line from flask_change_password import ChangePassword, I get:
from flask_change_password import ChangePassword
ModuleNotFoundError: No module named 'flask_change_password'
I don't understand this because I did install it in my env. I also tried installing with pip pip install flask-change-password to resolve the error without success.
My second problem is that I don't know where I should implement or how to implement the change_password form or how to change a certain password for a specific user.
This is my auth code for signup and login:
from flask import Blueprint, render_template, redirect, url_for, request, flash
from flask_change_password import ChangePasswordForm
from flask_login import login_user, login_required, logout_user
from sqlalchemy.sql.functions import current_user
from werkzeug.security import generate_password_hash, check_password_hash
from .models import User
from . import db
auth = Blueprint('auth', __name__)
#auth.route('/login')
def login():
return render_template('login.html')
#auth.route('/signup')
def signup():
return render_template('signup.html')
#auth.route('/signup', methods=['POST'])
def signup_post():
email = request.form.get('email')
name = request.form.get('name')
password = request.form.get('password')
user = User.query.filter_by(email=email).first() # check to see if user already exists
if user: # if a user is found, we want to redirect back to signup page so user can try again
flash('email address already exists. please login with your email.')
return redirect(url_for('auth.signup'))
new_user = User(email=email, name=name, password=generate_password_hash(password, method='sha256'))
# add the new user to the database
db.session.add(new_user)
db.session.commit()
return redirect(url_for('auth.login'))
#auth.route('/login', methods=['POST'])
def login_post():
email = request.form.get('email')
password = request.form.get('password')
remember = True if request.form.get('remember') else False
user = User.query.filter_by(email=email).first()
if not user or not check_password_hash(user.password, password):
flash('Please check your login details and try again.')
return redirect(url_for('auth.login')) # if the user doesn't exist or password is wrong, reload the page
login_user(user, remember=remember)
return redirect(url_for('main.profile'))
#auth.route('/logout')
#login_required
def logout():
logout_user()
return render_template('goodbye.html')
My init code:
from flask_sqlalchemy import SQLAlchemy
from flask_change_password import ChangePassword
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
app.config['SECRET_KEY'] = 'UMGC-SDEV300-Key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'
# app.secret_key = os.urandom(20)
# flask_change_password = ChangePassword(min_password_length=10, rules=dict(long_password_override=2))
# flask_change_password.init_app(app)
db.init_app(app)
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
login_manager.init_app(app)
from .models import User
#login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
from .auth import auth as auth_blueprint
app.register_blueprint(auth_blueprint)
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
return app
As you can see, I'm importing the flask_change_password module ChangePasswordForm but It fails to import??
I ended up fixing this issue by removing flask from the default instance of pip3. I also removed flask-security from the default instance of pip3. I then reinstalled each of these modules in the venv and everything worked.
I'm coding a simple flask app and I have already done all the login process. I'm not getting errors but the problem appears when I try to log in, somehow the program doesn't recognize me as a user, although I had already sign up correctly and my user data is in the database. So what ends happening is me trying to access to app_home route and, because it is a login_required route and the login doesn't save my data, I fall into a loop in the login page without being able to do anything.
Here is all the code:
server.py:
from flask import Flask
from flask_mongoengine import MongoEngine
from flask_login import LoginManager
from models import User
app = Flask(__name__)
...
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
login_manager.init_app(app)
#login_manager.user_loader
def load_user(email):
return User.objects(email=email).first()
if __name__ == '__main__':
app.run(debug=True)
models.py:
from flask_login import UserMixin
from mongo_setup import db
class User(UserMixin, db.Document):
email = db.StringField()
password = db.StringField()
auth.py:
from flask import Blueprint, render_template, redirect, url_for, request, flash
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import login_user, logout_user, login_required
auth = Blueprint('auth', __name__)
from mongo_setup import db
from models import User
...
#auth.route('/signup', methods=['GET', 'POST'])
def signup():
if request.method == 'POST':
email = request.form.get('email')
password = request.form.get('password')
user_email = User.objects(email=email).first()
if user_email:
flash("Email address already exists")
return redirect(url_for('auth.signup'))
User(email=email,
password=generate_password_hash(password, method='sha256')
).save()
return render_template('signup.html')
#auth.route('/login')
def login_get():
return render_template('login.html')
#auth.route('/login', methods=['POST'])
def login():
email = request.form.get("email")
password = request.form.get("address")
remember = True if request.form.get('remember') else False
user = User.objects(email=email).first()
if not user or not check_password_hash(user.password, password):
flash('Please check your login details and try again.')
return redirect(url_for('auth.login'))
login_user(user, remember=remember)
return redirect(url_for('auth.app_home'))
#auth.route('/app/<address>/<data>/<delete>', methods=['GET', 'POST'])
#login_required
def app_home(address, data, delete):
return render_template('app.html')
#login_manager.user_loader
def load_user(id):
return User.query.get(int(id))
You need to either change your load_user method to find the User object by id or overload the get_id() method on your User model to match your load_user() method to uniquely identify the user by email.
See: https://flask-login.readthedocs.io/en/latest/#how-it-works
I'm new to flask and all that backend thing, so i decided to start with flask. I found Miguel Grinberg's tutorial most beginner-friendly, so I started to follow it. But when tried to login the user with login_user it still didn't logged me in. The usernmame and the password are correct. I have a page protected with #login_required and when I pass the credentials, I still see the "Please log in to" access this page" message. Also the login_user function returns True and that's really strange. After that I copy-pasted the route function from the tutorial and it still didn't worked. Can you, guys, say what I'm doing wrong?
Here's the route functions:
from app import app, db
from flask import render_template, flash, redirect, url_for, render_template, request
from app.forms import LoginForm, RegisterForm
from flask_login import login_user, current_user, logout_user, login_required
from app.models import User
from werkzeug.urls import url_parse
#app.route('/')
#app.route('/index')
#login_required
def index():
posts = [
{
'author': {'username': 'Susan'},
'body': 'Beautiful day in Portland'
},
{
'author': {'username': 'Andy'},
'body': 'The Avengers film was awesome!'
}
]
return render_template('index.html',title='Home',posts=posts)
#app.route('/login', methods=['GET','POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('index'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user is None or not user.check_pwd(form.password.data):
flash('Invalid username or password')
return redirect(url_for('login'))
login_user(user,remember=form.remember_me.data)
next_page = request.args.get('next')
if not next_page or url_parse(next_page).netloc != '':
next_page = url_for('index')
return redirect(next_page)
return render_template('login.html', title='Sign In', form=form)
#app.route('/logout')
def logout():
logout_user()
return redirect(url_for('index'))
#app.route('/register',methods=['GET','POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('index'))
form = RegisterForm()
if form.validate_on_submit():
user = User(username=form.username.data,email=form.email.data)
user.set_pwd(form.password.data)
db.session.add(user)
db.session.commit()
flash('Congratulations, you\'re now a registered user!')
login_user(user)
return redirect(url_for('index'))
return render_template('register.html', title='Register', form=form)
Don't pay attention to the posts list
Here's the User model:
from app import db
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin
from app import login
class User(UserMixin,db.Model):
id = db.Column(db.Integer,primary_key=True)
username = db.Column(db.String(64),index=True,unique=True)
email = db.Column(db.String(120),index=True,unique=True)
password_hash = db.Column(db.String(128))
posts = db.relationship('Post',backref='author',lazy='dynamic')
def __repr__(self):
return '<User {}>'.format(self.username)
def set_pwd(self,pwd):
self.password_hash = generate_password_hash(pwd,'pbkdf2:sha1:10',60)
def check_pwd(self,pwd):
return check_password_hash(self.password_hash,pwd)
if I need to add something here for you to understand, write in the comments
This is happening because, on login, the User must have the is_active attribute and its value should be True.
To force login, you can pass argument force=True, like this login_user(user,remember=form.remember_me.data, force=True).
Or, you can add the is_active attribute on your User's model is_active = db.Column(db.Boolean(), default=True).
Or, you can inherit from class flask_login.UserMixin, which provides default implementations for all of the properties and methods expected from flask login. I recommend it this way.
from flask_login import UserMixin
class User(UserMixin):
...
You can look at the documentation here or here to see how it works.