0
votes

After trying for a week to fix the issue I decided to post the question here.

I migrated my application (python+flask) from cloud foundry to Azure. After that suddenly my user login and registration functionalities stopped working. After checking a bit more, at first, it was displaying an error message that my CSRF token was invalid. I disabled CRSF globally to test. The registration function was working again, however, the login functionality was still odd. I showed 4 kinds of behavior:

1 - accessing the Login page directly and inserting the right email and password redirects you to index, as intended, but current_user.is_authenticated = false;

2 - accessing the Login page and inputting the wrong password or email returns you to the login page but with no alert that password or email was wrong;

3- accessing a page behind login requirement and using the right email and password will just reload the login page with the redirect in the URL and do nothing;

4 - same as 3 but with wrong password or email results in the same thing;

After a while, I enabled the CSRF again and now the error message changed to CSRF token missing.

Maybe it is not related, but the issues began after I migrated to Azure (it worked in the beginning, but it stopped working after a while...I can't remember if it was before or after I started enforcing HTTPS).

Here is my code:

init.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
import os
from flask_login import LoginManager
from itsdangerous import URLSafeTimedSerializer

app = Flask(__name__)
app.config['MAIL_SERVER'] = os.getenv('MAIL_SERVER')
app.config['MAIL_PORT'] = os.getenv('MAIL_PORT')
app.config['MAIL_USERNAME'] = os.getenv('MAIL_USER')
app.config['MAIL_PASSWORD'] = os.getenv('MAIL_PASSWORD')
app.config['MAIL_USE_TLS'] = False
app.config['MAIL_USE_SSL'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('MYSQL_URI')
app.config['SECRET_KEY'] = os.urandom(32)
app.config['SERVER_NAME'] = os.getenv('SERVER_NAME')
db = SQLAlchemy(app)
db.init_app(app)
migrate.init_app(app, db)
manager = Manager(app)
migrate = Migrate(app, db)
manager.add_command('db', MigrateCommand)
login = LoginManager()
login.init_app(app)
login.session_protection = "strong"
login_serializer = URLSafeTimedSerializer(app.secret_key)

from app import views, models

if __name__ == '__main__':
    app.run(debug=True)

on models.py:

@login.user_loader
def load_user(user_id):
    print("User loader called with id %s" % user_id)
    return User.query.get(int(user_id))
class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(120), index=True, unique=True)
    password_hash = db.Column(db.String(128))

    def __repr__(self):
        return '<User {}>'.format(self.username)

    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)

my forms on views.py:

class LoginForm(FlaskForm):
    email = StringField('Email', validators=[DataRequired(), Email()])
    password = PasswordField('Password', validators=[DataRequired()])
    remember_me = BooleanField('Remember Me')
    submit = SubmitField('Sign In')


class RegistrationForm(FlaskForm):
    email = StringField('Email', validators=[DataRequired(), Email()])
    password = PasswordField('Password', validators=[DataRequired()])
    password2 = PasswordField(
        'Repeat Password', validators=[DataRequired(), EqualTo('password')])
    submit = SubmitField('Register')

    def validate_email(self, email):
        user = User.query.filter_by(email=email.data).first()
        if user is not None:
            raise ValidationError('Please use a different email address.')

my login, register, and logout functions on views.:

@app.route('/login', methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('index'))
    form = LoginForm()
    print(form.errors)
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.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)
        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)
    flash('Login Failed')
    return render_template('login.html', title='Sign In', form=form, is_auth=current_user.is_authenticated)


@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 = RegistrationForm()
    print(form.errors)
    if form.validate_on_submit():
        user = User(email=form.email.data)
        user.set_password(form.password.data)
        db.session.add(user)
        db.session.commit()
        flash('Congratulations, you are now a registered user!')
        return redirect(url_for('login'))
    return render_template('register.html', title='Register', form=form)

my login template:

    {% extends "base.html" %}
    
    {% block content %}
    {% endblock %}
    
    {% block body %}
        <h1>Sign In</h1>
        <div class="box box-default">
            <div class="box-body">
                {{ form.hidden_tag() }}
                    <form action="" method="post">
                    <p>
                        {{ form.email.label }}<br>
                        {{ form.email(size=64) }}<br>
                        {% for error in form.email.errors %}
                        <span style="color: red;">[{{ error }}]</span>
                        {% endfor %}
                        {% if form.errors %}
                        {{ form.errors }}
                        {% endif %}
                    </p>
                    <p>
                        {{ form.password.label }}<br>
                        {{ form.password(size=32) }}<br>
                        {% for error in form.password.errors %}
                        <span style="color: red;">[{{ error }}]</span>
                        {% endfor %}
                        {% if form.errors %}
                        {{ form.errors }}
                        {% endif %}
                    </p>
                    <p>
                        <p>{{ form.remember_me() }}
                           {{ form.remember_me.label }}</p>
                        <p>{{ form.submit() }}</p>
                    </form>
                <p>New User? <a href="{{ url_for('register') }}">Click to Register!</a></p>
            </div>
        </div>
    {% endblock %}

my register template:

{% extends "base.html" %}

{% block content %}
{% endblock %}
{% block body %}
    <h1>Register</h1>
    <div class="box box-default">
        <div class="box-header with-border">
            <form action="" method="post">
                {{ form.hidden_tag() }}
                <p>
                    {{ form.email.label }}<br>
                    {{ form.email(size=64) }}<br>
                    {% for error in form.email.errors %}
                    <span style="color: red;">[{{ error }}]</span>
                    {% endfor %}
                    {% if form.errors %}
                    {{ form.errors }}
                    {% endif %}
                </p>
                <p>
                    {{ form.password.label }}<br>
                    {{ form.password(size=32) }}<br>
                    {% for error in form.password.errors %}
                    <span style="color: red;">[{{ error }}]</span>
                    {% endfor %}
                    {% if form.errors %}
                    {{ form.errors }}
                    {% endif %}
                </p>
                <p>
                    {{ form.password2.label }}<br>
                    {{ form.password2(size=32) }}<br>
                    {% for error in form.password2.errors %}
                    <span style="color: red;">[{{ error }}]</span>
                    {% endfor %}
                    {% if form.errors %}
                    {{ form.errors }}
                    {% endif %}
                </p>
                <p>{{ form.submit() }}</p>
            </form>
        </div>
    </div>
{% endblock %}

I apologize in advance if the code doesn't look professional, I'm still a junior and a bit new with flask.

Does anyone see what might be causing the described issue? I'm using python 3.8, flask 1.1.2, flask-wtf 0.14.3, and flask-login 0.5.0.

I tried everything from the other similar questions on StackOverflow and from this blog, but nothing worked so far or seems to be related to my issue (so please consider this before marking this question as a duplicate).

Thank you very much in advance for your help.

edit: grammar

1
how is a database ?Thanh Nguyen Van
The database is a mysql db for Azure. The table for the user data has 3 columns: Id, email, password_hash.Thalles Machado
did you use domain to access login page or ip address ?Thanh Nguyen Van
Domain, if I understood the question correctly.Thalles Machado

1 Answers

0
votes

I talked with Miguel Grinberg about the issue and he pointed out the problem was with:

 app.config['SECRET_KEY'] = os.urandom(32)

This part of the code was making so, every time the app was restarted the tokens would be invalidated. I just changed it to a static environment variable and it is now working fine again.