I am working to try and build a login method for a while now. I am running a Flask app and have it working well. It all runs locally on my machine. Currently, I am using pymongo
and MongoClient
to make my connection to the DB. This is all working well and I would like to not change this if possible.
I am trying to use Flask-Login
to create a users
class using usermixin
. This is where I have been grossly unsuccessful. I have tried a few different things and my issue is how to I pull the data from my DB. I have done this previously with an SQL DB but for this project I expressly want to use MongoDB. This is the tutorial I was attempting to follow but I am having difficulty understanding everything because it is not explained well what every line is doing.
https://medium.com/@dmitryrastorguev/basic-user-authentication-login-for-flask-using-mongoengine-and-wtforms-922e64ef87fe
This is my connection to my DB:
client = MongoClient('mongodb://localhost:27017')
and this is my current users class that I don't have working and where I need the help.
class User(UserMixin):
def __init__(self, username, password_hash):
self.username = username
self.password_hash = password_hash
def check_password(self, password):
return check_password_hash(self.password_hash, password)
def get_id(self):
return self.username
@login_manager.user_loader
def load_user(user_id):
return User.objects(pk=user_id).first()
Then my last part is my login form:
@app.route('/login', methods=["GET" , "POST"])
def login():
if request.method == "GET":
return render_template("login.html", error=False)
if request.method == "POST":
check_user = request.form["username"]
if check_user:
if check_password_hash(check_user['password'], request.form["password"]):
login_user(check_user)
return redirect(url_for('index'))
I am aware that this tutorial uses MongoEngine
which I am not using, or not yet but some help here either how to get this code above to work or how to adapt it would be great. When I run this code I am not getting any errors it just doens't work. My test is I try to login and then I try to go to the logout page which is loaded with the following code:
@app.route("/logout")
@login_required
def logout():
logout_user()
return redirect(url_for('index'))
When I do it doesn't load the page and I get and Unauthorized Page notice. Thus I know that my code is not working. Lastly, I have all of templates in a static file location.
Thanks in advance for the help and please if anything is not clear ask and I will try to add more details. The more specific the better I will be able to help.
UPDATE:
I realized that it is also probably important to describe how my DB is structured to make sure that I am accessing it properly because that is a major point where I am having issues. I have a DB with my collection called Users and it is structured with each document being a different user record, like this:
{
"_id" : 1,
"Reset" : false,
"FirstName" : "John",
"LastName" : "Doe",
"Email" : "[email protected]",
"Username" : "",
"admin" : false,
"Pass" : "[hashed_password]"
}
{
"_id" : 2,
"Reset" : true,
"FirstName" : "Jane",
"LastName" : "Smith",
"Email" : "[email protected]",
"Username" : "Jane",
"admin" : false,
"Pass" : "[hashed_password]"
}
{
"_id" : 3,
"Reset" : true,
"FirstName" : "Gary",
"LastName" : "Bettman",
"Email" : "[email protected]",
"Username" : "HockeyGuy",
"admin" : false,
"Pass" : "[hashed_password]"
}
What you need to know about Flask-login: this extension works with the application's user model, and expects certain properties and methods to be implemented in it. (source : https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-v-user-logins).
The four required items are listed below:
is_authenticated
: a property that is True if the user has valid credentials or False otherwise.
is_active
: a property that is True if the user's account is active or False otherwise.
is_anonymous
: a property that is False for regular users, and True for a special, anonymous user.
get_id()
: a method that returns a unique identifier for the user as a string
Unfortunately all the examples in the official documentation and on Miguel Grinberg's excellent blog use SQLAlchemy. Good news, it is possible to implement it with Pymongo...
THE SOLUTION
routes.py
from flask import Flask
from flask_pymongo import PyMongo
from flask_login import LoginManager
from flask import render_template, url_for, request, flash
from app.forms import Login
from flask import request
from werkzeug.urls import url_parse
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import current_user, login_user, logout_user, login_required
mongo = PyMongo(app)
login = LoginManager(app)
login.login_view = 'login'
class User:
def __init__(self, username):
self.username = username
@staticmethod
def is_authenticated():
return True
@staticmethod
def is_active():
return True
@staticmethod
def is_anonymous():
return False
def get_id(self):
return self.username
@staticmethod
def check_password(password_hash, password):
return check_password_hash(password_hash, password)
@login.user_loader
def load_user(username):
u = mongo.db.Users.find_one({"Name": username})
if not u:
return None
return User(username=u['Name'])
@app.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('index'))
form = Login()
if form.validate_on_submit():
user = mongo.db.Users.find_one({"Name": form.name.data})
if user and User.check_password(user['Password'], form.password.data):
user_obj = User(username=user['Name'])
login_user(user_obj)
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)
else:
flash("Invalid username or password")
return render_template('login.html', title='Sign In', form=form)
@app.route('/logout')
def logout():
logout_user()
return redirect(url_for('login'))
form.py
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, PasswordField
from wtforms.validators import DataRequired
class Login(FlaskForm):
name = StringField('name' validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
login = SubmitField('Login')
Assuming we have, on the side of Mongodb, a collection (Users) that contains some login information. For example:
{
Name: [username],
Password: [hashed_password]
}
For further explanation on what each line of code does, I recommend you to consult the following links:
I found the following works with flask-login
, UserMixin
, pymongo
Here is User model
import datetime
import uuid
from depo import bcrypt, login_manager
from flask import session, flash
from depo.common.database import Database
from depo.models.blog import Blog
from flask_login import UserMixin
class User(UserMixin):
def __init__(self, username, email, password, _id=None):
self.username = username
self.email = email
self.password = password
self._id = uuid.uuid4().hex if _id is None else _id
def is_authenticated(self):
return True
def is_active(self):
return True
def is_anonymous(self):
return False
def get_id(self):
return self._id
@classmethod
def get_by_username(cls, username):
data = Database.find_one("users", {"username": username})
if data is not None:
return cls(**data)
@classmethod
def get_by_email(cls, email):
data = Database.find_one("users", {"email": email})
if data is not None:
return cls(**data)
@classmethod
def get_by_id(cls, _id):
data = Database.find_one("users", {"_id": _id})
if data is not None:
return cls(**data)
@staticmethod
def login_valid(email, password):
verify_user = User.get_by_email(email)
if verify_user is not None:
return bcrypt.check_password_hash(verify_user.password, password)
return False
@classmethod
def register(cls, username, email, password):
user = cls.get_by_email(email)
if user is None:
new_user = cls( username, email, password)
new_user.save_to_mongo()
session['email'] = email
return True
else:
return False
def json(self):
return {
"username": self.username,
"email": self.email,
"_id": self._id,
"password": self.password
}
def save_to_mongo(self):
Database.insert("users", self.json())
Here is routes
from flask import flash, render_template, request, session, make_response, redirect, url_for
from depo import app, bcrypt, login_manager
from depo.models.blog import Blog
from depo.models.post import Post
from depo.models.user import User
from depo.common.database import Database
from depo.usercon.forms import RegistrationForm, LoginForm
from flask_login import login_user
@app.before_first_request
def initialize_database():
Database.initialize()
@app.route("/register", methods=['GET', 'POST'])
def register():
form = RegistrationForm()
if form.validate_on_submit():
if request.method == 'POST':
username = request.form["username"]
email = request.form["email"]
password = bcrypt.generate_password_hash(request.form["password"])
.decode('utf-8')
find_user = User.get_by_email(email)
if find_user is None:
User.register(username, email, password)
flash(f'Account created for {form.username.data}!', 'success')
return redirect(url_for('home'))
else:
flash(f'Account already exists for {form.username.data}!', 'success')
return render_template('register.html', title='Register', form=form)
@app.route("/login", methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
email = request.form["email"]
password = request.form["password"]
find_user = Database.find_one("users", {"email": email})
if User.login_valid(email, password):
loguser = User(find_user["_id"], find_user["email"], find_user["password"])
login_user(loguser, remember=form.remember.data)
flash('You have been logged in!', 'success')
return redirect(url_for('home'))
else:
flash('Login Unsuccessful. Please check email and password', 'danger')
return render_template('login.html', title='Login', form=form)
@login_manager.user_loader
def load_user(user_id):
user =User.get_by_id(user_id)
if user is not None:
return User(user["_id"])
else:
return None
May be needed some codes for app
also if already not initialized
from flask_login import LoginManager
login_manager = LoginManager(app)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With