Search code examples
pythonflaskflask-login

Flask - Simple Login Page - Invalid Hashed Password


I am trying to create a basic flask login page using Flask-Scrypt to store the password securely in the database.

Based on the debugging it prints: Attempting password verification... Checking password for user: admin Hashed password from attempt: b'89ABrlAbxdOeLlup4L96Cv5sqsFL7xYmPNrS/fZ58Po40J4+zK8BqYZTt9Gqkm+/+fRKs/etPSTyJHfeRlFLg==' Stored hashed password: 89ABrlAbxdOeLlup4L96Cv5sqsFL7xYmPNrS/fZ58Po40J4+zK8BqYZTt9Gqkm+/+fRKs/etPSTyJHfeRlFLg== Invalid password Rendering login.html...

Here is the models.py:

# app/models.py

from . import db
from flask_scrypt import generate_random_salt, generate_password_hash, check_password_hash
import os
import base64

class User(db.Model):
    __tablename__ = 'users'
    userID = db.Column('userID', db.Integer, primary_key=True)
    username = db.Column('username', db.String(50), unique=True, nullable=False)
    password_hash = db.Column('password_hash', db.String(200), nullable=False)
    salt = db.Column('salt', db.String(200), nullable=False)

    def __init__(self, username, password):
        self.username = username
        self.set_password(password)

    def set_password(self, password):
        self.salt = generate_random_salt()
        print("Salt before hashing:", self.salt)  # Debugging
        password_bytes = password.encode('utf-8')
        self.password_hash = generate_password_hash(password_bytes + self.salt, salt=self.salt)

    def check_password(self, password):
        print("Checking password for user:", self.username)
        password_bytes = password.encode('utf-8')
        salt_bytes = self.salt.encode('utf-8')
        hashed_password_attempt = generate_password_hash(password_bytes + salt_bytes, salt=salt_bytes)
        print("Hashed password from attempt:", hashed_password_attempt)  # Debugging
        print("Stored hashed password:", self.password_hash)  # Debugging
        return check_password_hash(self.password_hash, password_bytes, salt=salt_bytes)

And here is the routes.py

from flask import Blueprint, render_template, redirect, url_for, flash
from flask_login import login_user
from .forms import LoginForm
from .models import User
from . import db

auth = Blueprint("auth", __name__)
main = Blueprint("main", __name__)
admin = Blueprint("admin", __name__)

@auth.route("/login", methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        # Retrieve user record from the database based on the provided username
        user = User.query.filter_by(username=form.username.data).first()
        print("User:", user)  # Debugging: Print user to check if retrieved
        if user:
            # Verify the password
            print("Attempting password verification...")  # Debugging: Check password verification attempt
            if user.check_password(form.password.data):
                # Password matches, authentication successful
                print("Password verified. Logging in...")  # Debugging: Indicate successful password verification
                login_user(user)
                return redirect(url_for('admin.admin_panel'))
            else:
                # Password doesn't match, render login page with error message
                print("Invalid password")  # Debugging: Indicate invalid password
                flash('Invalid username or password', 'error')
        else:
            # User not found, render login page with error message
            print("User not found")  # Debugging: Indicate user not found
            flash('Invalid username or password', 'error')

    # If GET request or form validation failed, render the login page
    print("Rendering login.html...")  # Debugging: Indicate rendering of login.html
    return render_template('login.html', form=form)

@main.route("/")
def index():
    return render_template("index.html")

@admin.route("/admin")
def admin_panel():
    return render_template("admin.html")

it is looking up the details in a mysql database, and based on the debugging it is finding the correct password hash, but logging this as invalid.

I have tried a number of variations in the check_password function


Solution

  • I think you put hashed and plain password in incorrect place, it should like this.

    from flask_scrypt import (
        generate_random_salt,
        generate_password_hash,
        check_password_hash,
    )
    
    
    def save_password(password: str) -> tuple[str, str]:
        password_salt = generate_random_salt()
        password_hash = generate_password_hash(password, password_salt)
        return password_salt, password_hash.decode()
    
    
    def validate_password(plain_password: str, salt: str, hash_password: bytes) -> bool:
        return check_password_hash(plain_password, hash_password, salt)
    
    
    if __name__ == "__main__":
        user_password = "very_secret_password"
        salt_gen, hash_pass = save_password(user_password)
        is_valid = validate_password(user_password, salt_gen, hash_pass.encode())
        print(is_valid)  # this value is supposed to true
    

    For function check_password_hash(plain_password, hash_password, salt) it should

    • plain_password in the 1st argument
    • hash_password that is stored in db in 2nd argument
    • salt that is stored in db on 3rd argument.

    It should be like this

    return check_password_hash(password_bytes, self.password_hash, salt=salt_bytes)
    

    And the second thing, you don't need to concat generated hash password with that salt again, it's already mixed the password.

    hashed = generate_password_hash(plain, salt) # -> already mixed with the salt
    

    But what is you writing instead

    hashed = generate_password_hash(plain + salt, salt) # this is double salt
    

    You can make it simple like this

    self.password_hash = generate_password_hash(password_bytes, salt=self.salt)