0

I am having an issue when verifying a password in my mongodb.

from what i saw in other posts, it appears that I am using different salts to hash the password. but I am not sure how to fix it.

this is the sign up and log in controller::




import bcrypt
from flask import Blueprint, request, jsonify
from models.user import User
from services.cors_handler import CorsHandlerUtil
from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity
from flask_bcrypt import check_password_hash

user_bp = Blueprint("user_routes", __name__, url_prefix="/api/user")

@user_bp.route("signup", methods=["POST","OPTIONS"])
def signup_controller():   
    print('signup_controller') 
    data = request.get_json()
    if not data:
        print('NO DATA')
        return jsonify({'message': 'Invalid request'}), 400

    if data.get('name') is None or data.get('email') is None or data.get('password') is None:
        print('MISSING PARAMS')
        return jsonify({"error": "Missing params"}), 400

    name = data.get('name')
    email = data.get('email')
    password = data.get('password')
    print(f'password being send in on signup::    {password}')
    

    # Check if the email already exists
    if User.objects(email=email).first():
        print('USER EXISTS ALREADY')
        return jsonify({"error": "Email already exists"}), 400

    # Create a new user document
    user = User(email=email, password=password, name=name)
    print('Hashed Password:', user.password)  # Add this line


    try:
        print('USER SAVED')
        user.save()
        access_token = create_access_token(identity=str(user.id))
        return jsonify({"message":"User created",  "token": access_token}), 200

    except ValueError as e:
        print(f'errro1: {str(e)}')
        return jsonify({"error": str(e)}), 400
    except Exception as e:
        print(f'errro2: {str(e)}')
        return jsonify({"error": "Internal server error"}), 500



@user_bp.route("/login", methods=["POST", "OPTIONS"])
def login_controller():
    data = request.get_json()
    if not data:
        return jsonify({'error': 'Invalid request'}), 400

    if data.get('email') is None or data.get('password') is None:
        return jsonify({"error": "Missing params"}), 400

    email = data.get('email')
    password = data.get('password')
    print('Password being send on login:', password)  # Add this line

    # Check if the user with the provided email exists
    user = User.objects(email=email).first()
    if not user:
        print('USER NOT FOUND')
        return jsonify({"error": "User not found"}), 404

    print('Hashed Password in Database:', user.password)  # Add this line

    # Compare the provided password with the hashed password in the database
    if not user.check_password(password):
        print('INVALID CREDENTIALS')
        return jsonify({"error": "Invalid credentials"}), 401

    # If the provided credentials are correct, create an access token
    access_token = create_access_token(identity=str(user.id))
    return jsonify({"message": "Login successful", "token": access_token}), 200

this is the user model from mongodb::


from flask_mongoengine import MongoEngine
from mongoengine.errors import NotUniqueError
from flask_bcrypt import Bcrypt

db = MongoEngine()
bcrypt = Bcrypt()

class User(db.Document):
    email = db.StringField(required=True, unique=True)
    password = db.StringField(required=True)
    name = db.StringField(required=True)

    def __init__(self, email, password, name, *args, **kwargs):
        super(User, self).__init__(*args, **kwargs)
        self.email = email
        self.password = bcrypt.generate_password_hash(password).decode('utf-8')
        self.name = name
        
    def check_password(self, password):
        return bcrypt.check_password_hash(self.password, password)

        # return bcrypt.check_password_hash(db_password, password)
        # return True

    
    def save(self, *args, **kwargs):
        try:
            return super(User, self).save(*args, **kwargs)
        except NotUniqueError:
            raise ValueError("Email already exists")



I found a post in a different laguage that appears to be what my issue is::

BCrypt Verify stored password hash

but I am not sure how to fix it

when i try to login I see the following logs::

signup_route()
signup_controller
password being send in on signup::    password
Hashed Password: $2b$12$hwfW5kuncMBHE0Z1B94I5OILc43RkMyuuMJKSw8HIAHzWn1iAy1hu
USER SAVED
127.0.0.1 - - [24/Jul/2023 19:13:55] "POST /api/user/signup HTTP/1.1" 200 -
loginup_route()
Password being send on login: password
Hashed Password in Database: $2b$12$W/H5KNBE3FWuadKDbuVkke6zE1iHnYDAgrRKtMypaZn6YFIZDYihW
INVALID CREDENTIALS
127.0.0.1 - - [24/Jul/2023 19:14:09] "POST /api/user/login HTTP/1.1" 401 -

Juan Casas
  • 268
  • 2
  • 13
  • 1
    Shouldn't `check_password` just accept one parameter, and compare it against `self.password`? – Tim Roberts Jul 24 '23 at 22:56
  • I don't think you have the right password. That string does not match "password" when I test it in Python. The salt is stored in the encrypted password. The format is `'$' (algorithm) '$' (rounds) '$' (salt) '/' (password)`. – Tim Roberts Jul 24 '23 at 23:06
  • I updated the code so that I would only pass one password but it still didnt work. – Juan Casas Jul 25 '23 at 00:17
  • I added print statments to verify that the password going in for login was the same as for the sign up @TimRoberts thank you for your help! the passwords appear to be the same but its not workng – Juan Casas Jul 25 '23 at 00:18
  • In your updated transcript, do you notice that "hashed password" is not the same as "hashed password in database"? – Tim Roberts Jul 25 '23 at 01:34
  • @TimRoberts yea that is the issue. Im not sure why i am not getting the same hashed password – Juan Casas Jul 25 '23 at 01:46

1 Answers1

1

Here's what I think is going on. I'm writing it as an answer even though I'm not 100% confident, because there's a lot of text.

You are doing the encryption in the User constructor. What you have forgotten is that Flask has to create User objects itself. Every time it reads a record from the database, it creates a User object to wrap that data, and your constructor is going to RE-encrypt it.

The solution is to separate the encryption process from the creation process:

class User(db.Document):
    email = db.StringField(required=True, unique=True)
    password = db.StringField(required=True)
    name = db.StringField(required=True)

    def __init__(self, email, password, name, *args, **kwargs):
        super(User, self).__init__(*args, **kwargs)
        self.email = email
        self.password = password
        self.name = name

    def encrypt_pw(self):
        self.password = bcrypt.generate_password_hash(self.password).decode('utf-8') 

    def check_password(self, password):
        return bcrypt.check_password_hash(self.password, password)

(With that design, I think you can actually remove the __init__ function altogether. I think it already does what you're doing.)

Your creation then becomes a two-step process:

    user = User(email=email, password=password, name=name)
    user.encrypt_pw();

Alternatively, you could add another optional parameter to the constructor specifying whether to do the encryption. I'm not sure I like that as well.

Tim Roberts
  • 48,973
  • 4
  • 21
  • 30
  • Do you have an article, or some reference as to how i should be doing this authentication process. thank you for taking the time to answer my questions!! – Juan Casas Jul 25 '23 at 19:13
  • I don't think there's anything fundamentally wrong with your approach, you just have a simple bug. – Tim Roberts Jul 25 '23 at 19:25
  • you were right! can we connect on linkedin :: https://www.linkedin.com/in/juanfcasas/ how did you know that?!?! how did you arrive at that conclusion?!?! thanks a billion! – Juan Casas Jul 26 '23 at 02:55
  • 1
    The fact that the password coming from the database didn't match the password you put into the database told me that someone was changing it. That pointed to the problem. I've been programming for 50 years -- I've made all the mistakes myself by now. – Tim Roberts Jul 26 '23 at 04:54