skip to Main Content

The Issue

I work for a couple of simple volunteering organizations helping with management and running campaigns, but we needed a system to track peoples hours for honorarium and productivity tracking. Thought it’d be a great opportunity to learn some web dev and started on a flask app since it was apparently simpler then django and I don’t know javascript. Deploy the app fix the bugs etc, but now someone come back after a log in with this picture after they submit the login form.

"Bad Request CSRF token is missing".

"Bad Request CSRF token does not match".

"Bad Request CSRF token has expired".

These are the error messages I’ve managed to get and unless I’m implementing it wrong, no solution online fixes them for me. I don’t even get an error message in my gunicorn logs I just get that image loaded up.

I’m so frustrated after debugging for so long and getting it to work and presenting it proudly and now it does this, so maybe its those biased glasses of frustration that is stopping me from intelligently coming to a solution

The Code

I have my hidden tag in place so csrf shouldn’t be missing
/webapp/website/templates/login.html

<body>
    {%extends 'base.html'%}
    {% block content %}
    <div class="shadow p-3 mb-5 bg-body rounded">
    <h3 align="center">Login</h3>
    <form method="POST">
        {{ form.hidden_tag() }}
        
        {{ form.email.label(class='form-label')}}
        {{ form.email(class='form-control')}}
        <br/>
        {{ form.password.label(class='form-label')}}
        {{ form.password(class='form-control')}}
        <br/>
        {{ form.submit(class='btn btn-danger')}}
    </form>
    </div>
    {% endblock %}
</body>
</html>

.env

SECRET_KEY='iremovedtheactualone'
DEV_DATABASE_URI='mysql+pymysql://root:root@localhost/main'
PROD_DATABASE_URI='mysql+pymysql://user:password@ip/db'
SERVER_NAME='websitename.com'

www to non www nginx redirect not working which my app doesn’t work on www is a whole other issue a friend of mine outlined here

/webapp/website/config.py

from os import environ, path
from dotenv import load_dotenv

DB_NAME = "main"

class Config:
    """Base config."""
    #SESSION_COOKIE_NAME = environ.get('SESSION_COOKIE_NAME')
    MAX_CONTENT_LENGTH = 16*1000*1000
    RECEIPT_FOLDER = '..\assets\receipts'
    UPLOAD_EXTENSIONS = ['.jpg', '.png', '.pdf']
    STATIC_FOLDER = 'static'
    TEMPLATES_FOLDER = 'templates'
    SESSION_COOKIE_SECURE = True

class ProdConfig(Config):
    basedir = path.abspath(path.dirname(__file__))
    load_dotenv('/home/sai/.env')
    env_dict = dict(environ)
    FLASK_ENV = 'production'
    DEBUG = False
    TESTING = False
    SQLALCHEMY_DATABASE_URI = environ.get('PROD_DATABASE_URI')
    SECRET_KEY = environ.get('SECRET_KEY')
    SERVER_NAME = environ.get('SERVER_NAME')
    ...

/webapp/website.__init__.py

def create_app(name):
    #Flask Instance
    app = Flask(__name__)
    app.config.from_object(config.ProdConfig)

    if name  != '__main__':
        gunicorn_logger=logging.getLogger('gunicorn.error')
        app.logger.handlers = gunicorn_logger.handlers
        app.logger.setLevel(gunicorn_logger.level)

    db.init_app(app)
    migrate.init_app(app, db)
    csrf.init_app(app)
    ...

/webapp/website.auth.py

@auth.route('/login', methods=['GET', 'POST'])
def login():
    current_app.logger.info('enter login')
    ...
    if form.validate_on_submit():
        current_app.logger.info('enter submit')
        ...
        user = Users.query.filter_by(email=email).first()
        if user:
            current_app.logger.info('enter user')
            if check_password_hash(user.password_hash, password):
                current_app.logger.info('enter password')

                flash('Logged in successfully!', category='success')
                login_user(user, remember=True)
                form.email.data = ''
                form.password.data = ''
                
                #Default
                return redirect('/shift_add')

                # Method Two
                next = request.args.get('next')
                #current_app.logger.info(next)
                #if not is_safe_url(next):
                #    return abort(400)
                #else:
                #    return redirect(next or url_for('views.home'))

                # Method Three
                #next_url = request.form.get("next")
                #if next_url:
                #    return redirect(next_url)
                #return redirect(url_for('views.home') or next)

                # Method Four
                # return redirect(str(request.args.get("next")) or "/shift_add")
            else:
                ...
        else:
            ...

        ...
    else:
        current_app.logger.info('errors: ')
        current_app.logger.info(form.errors)

    return render_template('/user/login.html', form=form)

The Main Question

How do I resolve the issues of the CSRF being missing sometimes or being expired or not matching. None of these errors raise anything in my logs unless I’m not displaying that correctly, but I can’t figure out why each one happens. Right now my chrome browser gives me token missing and firefox gives me expired and safari on iphone gives me doesn’t match.

Just as compelling, how am I configuring CSRF wrong? What am I missing? I would love to learn not just how to fix these issues but why they’re happening and the best practices to move forward with. Thanks!

Attempted Solutions

Clearing cookies to solve CSRF token do not match. I tried running the site in incognito on chrome and I get the CSRF token is missing which was my chrome issue. Firefox (my issue is that it says expired) but in incognito also says the token is missing.

Apparently a bug in webkit browsers I’m not running a docker and maybe this solution is just going over my head.

Too large of info being sent through isn’t the case since I have successfully logged in before and I’m not sending anything that large through.

2

Answers


  1. Chosen as BEST ANSWER

    This answer saved my life and I found it only here after DAYS of researching.


  2. You can always just use csrf_exempt decorator.

    Not a good practice tho

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search