Flask session resets after redirect in Replit

94 Views Asked by At

I'm running the Flask app below in Replit. The session is resetting after a redirect() when I run it in MS Edge or Chrome. It works when I run it in Firefox. Here's the code:

from flask import Flask, render_template, request, redirect, url_for, session
import secrets

app = Flask(__name__)
app.secret_key = secrets.token_hex(16)

users = {
    'user1': {'username': 'user1', 'password': 'password1'},
    'user2': {'username': 'user2', 'password': 'password2'}
}

@app.route('/')
def home():
    print("loading home", session)
    return render_template('index.html')

@app.route('/signup', methods=['GET', 'POST'])
def signup():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        users[username] = {'username': username, 'password': password}
        return redirect(url_for('home'))
    return render_template('signup.html')

@app.route('/login', methods=['GET', 'POST'])
def login():
    print("loading login", session)
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        user = users.get(username)
        if user and user['password'] == password:
            session['username'] = username
            print("session set", session)
            return redirect(url_for('home'))
    return render_template('login.html')

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080, debug=True)

This is the console output:

172.31.196.1 - - [21/Dec/2023 23:03:48] "OPTIONS * HTTP/1.1" 404 -
loading home <SecureCookieSession {}>
172.31.196.1 - - [21/Dec/2023 23:03:48] "GET / HTTP/1.1" 200 -
loading login <SecureCookieSession {}>
172.31.196.1 - - [21/Dec/2023 23:03:51] "GET /login HTTP/1.1" 200 -
loading login <SecureCookieSession {}>
172.31.196.1 - - [21/Dec/2023 23:03:51] "GET /login HTTP/1.1" 200 -
loading login <SecureCookieSession {}>
session set <SecureCookieSession {'username': 'user1'}>
172.31.196.1 - - [21/Dec/2023 23:04:06] "POST /login HTTP/1.1" 302 -
loading home <SecureCookieSession {}>
172.31.196.1 - - [21/Dec/2023 23:04:06] "GET / HTTP/1.1" 200 -
loading home <SecureCookieSession {}>
172.31.196.1 - - [21/Dec/2023 23:04:06] "GET / HTTP/1.1" 200 -

I've verified that cookies are enabled.

2

There are 2 best solutions below

2
On

I have updated your code to have a functional user registration process in Flask. Current code uses users from the dictionary, I assume you will use some database to store the users information.

I have added the following:

  • Set username in session after signup process.
  • Added a check in the signup process for existing users with same username.
  • Set necessary flash messages in the app.py and showed them in the templates.
  • Added logout functionality.

requirements.txt:

blinker==1.7.0
click==8.1.7
Flask==3.0.0
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.3
Werkzeug==3.0.1

app.py:

from flask import Flask, render_template, request, redirect, url_for, session, flash
import secrets

app = Flask(__name__)
app.secret_key = secrets.token_hex(16)

users = {
    'user1': {'username': 'user1', 'password': 'password1'},
    'user2': {'username': 'user2', 'password': 'password2'}
}


@app.route('/')
def home():
    username = session.get("username")
    return render_template('index.html', username=username)


@app.route('/logout')
def logout():
    session.pop('username', None)
    flash('You have been logged out.', 'info')
    return redirect(url_for('login'))


@app.route('/signup', methods=['GET', 'POST'])
def signup():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        if username not in users:
            flash('Account created successfully.', 'success')
            users[username] = {'username': username, 'password': password}
            session['username'] = username
            return redirect(url_for('home'))
        else:
            flash('Username already exists. Choose a different one.', 'error')
            print(f"Username {username} exists")
            return redirect(url_for('signup'))
    return render_template('signup.html')


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        user = users.get(username)
        if user and user['password'] == password:
            session['username'] = username
            flash('Logged in successfully.', 'success')
            return redirect(url_for('home'))
        else:
            flash('User not found. Try again.', 'error')
    return render_template('login.html')


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080, debug=True)

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Home</title>
    <style>
        .error {
            color: red;
        }

        .success {
            color: green;
        }
    </style>
</head>
<body>
{% with messages = get_flashed_messages(with_categories=true) %}
    {% if messages %}
        <ul>
            {% for category, message in messages %}
                <li class="{{ category }}">{{ message }}</li>
            {% endfor %}
        </ul>
    {% endif %}
{% endwith %}

{% if username %}
    <p>Welcome, {{ username }}!</p>
    <a href="{{ url_for('logout') }}">Logout</a>
{% else %}
    <p>Not logged in. Please sign up or log in:</p>
    <a href="{{ url_for('signup') }}">Sign Up</a> | <a href="{{ url_for('login') }}">Login</a>
{% endif %}
</body>
</html>

signup.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Sign Up</title>
    <style>
        .error {
            color: red;
        }

        .success {
            color: green;
        }
    </style>

</head>
<body>
{% with messages = get_flashed_messages(with_categories=true) %}
    {% if messages %}
        <ul>
            {% for category, message in messages %}
                <li class="{{ category }}">{{ message }}</li>
            {% endfor %}
        </ul>
    {% endif %}
{% endwith %}

<h2>Sign Up</h2>
<form method="post" action="{{ url_for('signup') }}">
    <label for="username">Username:</label>
    <input type="text" name="username" required><br>
    <label for="password">Password:</label>
    <input type="password" name="password" required><br>
    <input type="submit" value="Sign Up">
</form>
<p>Already have an account?
    <a href="{{ url_for('login') }}">Login</a>
</p>

</body>
</html>

login.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login</title>
    <style>
        .error {
            color: red;
        }

        .success {
            color: green;
        }
    </style>

</head>
<body>
{% with messages = get_flashed_messages(with_categories=true) %}
    {% if messages %}
        <ul>
            {% for category, message in messages %}
                <li class="{{ category }}">{{ message }}</li>
            {% endfor %}
        </ul>
    {% endif %}
{% endwith %}
<h2>Login</h2>
<form method="post" action="{{ url_for('login') }}">
    <label for="username">Username:</label>
    <input type="text" name="username" required><br>
    <label for="password">Password:</label>
    <input type="password" name="password" required><br>
    <input type="submit" value="Login">
</form>
<p>No account?
    <a href="{{ url_for('signup') }}">Sign Up</a>
</p>
</body>
</html>

Demo

Homepage:

Homepage

Login:

Login

After Login Homepage:

after login homepage

References

0
On

Solved. The problem is with how Chromium-based browsers implement RFC 2109 (thanks to @davidism for the explanation). The fix is to set the SameSite and Secure attributes of the Set-Cookie header using this code:

app.config['SESSION_COOKIE_SAMESITE'] = 'None'
app.config['SESSION_COOKIE_SECURE'] = True