Search code examples
javascripthtmljqueryflaskflask-login

Error message not displaying on the login screen using Python Flask


I am working on a login screen using Bootstrap and jQuery keyframes shaking effect. The backend is handled by Flask. However, I'm facing an issue where the error message "Wrong Username or Password. Please try again" is not displayed in the footer of the login box when an incorrect username or password is entered.

I have tried various methods, but I have been unsuccessful in displaying the error message. Could someone please help me with this issue? I have provided both the .py and .html code below.

Thank you for your help.

Python Flask (main.py):

from flask import Flask, redirect, render_template, g, request
import os

# Create a new Flask app
app = Flask(__name__, template_folder="web", static_folder="web")
app.config["TEMPLATES_AUTO_RELOAD"] = True


# Define a global version variable for cache busting
@app.before_request
def before_request():
    g.version = os.environ.get("VERSION", "1.0.0")


# Define a route
@app.route("/")
def content():
    # Render the login form template
    return render_template("login.html")


@app.route("/login", methods=["GET", "POST"])
@app.route("/login", methods=["GET", "POST"])
def login():
    error = None
    if request.method == "POST":
        # Check if the username and password are correct
        username = request.form["username"]
        password = request.form["password"]
        if username == "admin" and password == "pass1":
            # If the username and password are correct, redirect to the index page
            return redirect("/index")
        else:
            # If the username and password are incorrect, show an error message
            error = "Invalid username or password. Please try again."
    # Render the login page with the error message (if any)
    return render_template("login.html", error=error)


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


# Run the Flask app
if __name__ == "__main__":
    app.run(debug=True)

HTML(login.html):

<!DOCTYPE html>
<html lang="en-pl">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Playground Window</title>

    <!-- Include Bootstrap CSS -->
    <link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}?v={{ g.version }}">

    <!-- Include Custom CSS -->
    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}?v={{ g.version }}">

    <!-- Include jQuery -->
    <script src="{{ url_for('static', filename='js/jquery-3.6.4.min.js') }}?v={{ g.version }}"></script>

    <!-- Include Popper.js -->
    <script src="{{ url_for('static', filename='js/popper.min.js') }}?v={{ g.version }}"></script>

    <!-- Include Bootstrap JS -->
    <script src="{{ url_for('static', filename='js/bootstrap.min.js') }}?v={{ g.version }}"></script>

    <!-- Include jQuery Keyframes JS -->
    <script src="{{ url_for('static', filename='js/jquery.keyframes.min.js') }}?v={{ g.version }}"></script>

</head>

<body>
    <!-- Login Button -->
    <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#loginModal">
        Login
    </button>

    <!-- Login Modal -->
    <div class="modal fade" id="loginModal" tabindex="-1" aria-labelledby="loginModalLabel" aria-hidden="true"
        aria-modal="true">
        <div class="modal-dialog">
            <div class="modal-content">
                <!-- Modal Body -->
                <div class="modal-body">
                    <!-- Logo -->
                    <div class="logo">
                        <img src="{{ url_for('static', filename='images/logo.png') }}" alt="Logo" class="img-fluid"
                            data-bs-dismiss="modal" style="max-width: 200px;">
                    </div>
                    <form method="POST" action="/login">
                        <div class="mb-3">
                            <label for="username" class="form-label">Username</label>
                            <input type="text" class="form-control" id="username" name="username">
                        </div>
                        <div class="mb-3">
                            <label for="password" class="form-label">Password</label>
                            <input type="password" class="form-control" id="password" name="password">
                            {% if error %}
                            <div id="login-error" class="text-danger">{{ error }}</div>
                            {% endif %}
                        </div>
                </div>

                <!-- Modal Footer -->
                <div class="modal-footer">
                    {% if error %}
                    <div class="text-danger">{{ error }}</div>
                    {% endif %}
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
                    <button type="submit" class="btn btn-primary">Login</button>
                </div>

            </div>
        </div>
    </div>

    <script>
        $(document).ready(function () {
            // Include jQuery Keyframes library
            $.keyframe.define([{
                name: 'shake',
                '0%': { transform: 'translateX(0)' },
                '10%': { transform: 'translateX(-20px)' },
                '20%': { transform: 'translateX(20px)' },
                '30%': { transform: 'translateX(-20px)' },
                '40%': { transform: 'translateX(20px)' },
                '50%': { transform: 'translateX(-20px)' },
                '60%': { transform: 'translateX(20px)' },
                '70%': { transform: 'translateX(-20px)' },
                '80%': { transform: 'translateX(20px)' },
                '90%': { transform: 'translateX(-20px)' },
                '100%': { transform: 'translateX(0)' },
            }]);

            // Define the shaking effect
            function shakeModal(errorMsg) {
                $('#loginModal .modal-dialog').playKeyframe({
                    name: 'shake', // Name of the keyframe defined above
                    duration: '400ms', // Duration of the shake animation
                    timingFunction: 'linear', // Timing function for the shake animation
                    iterationCount: 1, // Number of times the animation should play
                    complete: function () { // Code to run after the animation completes
                        $('#login-error').text(errorMsg).show(); // Show the error message and set its text to the errorMsg parameter
                    }
                });
            }

            // Hide the error message initially
            $('#login-error').hide();

            // Trigger the shaking effect when the wrong input is detected
            $('form').submit(function (event) {
                var username = $('#username').val();
                var password = $('#password').val();

                // Replace with your actual input validation logic
                var inputIsValid = (username === 'admin' && password === 'pass1');
                var errorMsg = '{{ error }}'; // Get the Flask error message from the template

                if (!inputIsValid) {
                    shakeModal(errorMsg); // Pass the Flask error message to the shakeModal function
                    event.preventDefault(); // Prevent the form from being submitted
                } else {
                    $('#login-error').hide(); // Hide the error message
                }
            });

        });
    </script>
</body>

</html>

Dear Community,

I am working on a login screen using Bootstrap and jQuery keyframes shaking effect. The backend is handled by Flask. However, I'm facing an issue where the error message "Wrong Username or Password. Please try again" is not displayed in the footer of the login box when an incorrect username or password is entered.

I have tried various methods, but I have been unsuccessful in displaying the error message. Could someone please help me with this issue? I have provided both the .py and .html code below.

Thank you for your help.

Python Flask (main.py):

from flask import Flask, redirect, render_template, g, request
import os

# Create a new Flask app
app = Flask(__name__, template_folder="web", static_folder="web")
app.config["TEMPLATES_AUTO_RELOAD"] = True


# Define a global version variable for cache busting
@app.before_request
def before_request():
    g.version = os.environ.get("VERSION", "1.0.0")


# Define a route
@app.route("/")
def content():
    # Render the login form template
    return render_template("login.html")


@app.route("/login", methods=["GET", "POST"])
@app.route("/login", methods=["GET", "POST"])
def login():
    error = None
    if request.method == "POST":
        # Check if the username and password are correct
        username = request.form["username"]
        password = request.form["password"]
        if username == "admin" and password == "pass1":
            # If the username and password are correct, redirect to the index page
            return redirect("/index")
        else:
            # If the username and password are incorrect, show an error message
            error = "Invalid username or password. Please try again."
    # Render the login page with the error message (if any)
    return render_template("login.html", error=error)


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


# Run the Flask app
if __name__ == "__main__":
    app.run(debug=True)

HTML(login.html):

<!DOCTYPE html>
<html lang="en-pl">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Playground Window</title>

    <!-- Include Bootstrap CSS -->
    <link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}?v={{ g.version }}">

    <!-- Include Custom CSS -->
    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}?v={{ g.version }}">

    <!-- Include jQuery -->
    <script src="{{ url_for('static', filename='js/jquery-3.6.4.min.js') }}?v={{ g.version }}"></script>

    <!-- Include Popper.js -->
    <script src="{{ url_for('static', filename='js/popper.min.js') }}?v={{ g.version }}"></script>

    <!-- Include Bootstrap JS -->
    <script src="{{ url_for('static', filename='js/bootstrap.min.js') }}?v={{ g.version }}"></script>

    <!-- Include jQuery Keyframes JS -->
    <script src="{{ url_for('static', filename='js/jquery.keyframes.min.js') }}?v={{ g.version }}"></script>

</head>

<body>
    <!-- Login Button -->
    <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#loginModal">
        Login
    </button>

    <!-- Login Modal -->
    <div class="modal fade" id="loginModal" tabindex="-1" aria-labelledby="loginModalLabel" aria-hidden="true"
        aria-modal="true">
        <div class="modal-dialog">
            <div class="modal-content">
                <!-- Modal Body -->
                <div class="modal-body">
                    <!-- Logo -->
                    <div class="logo">
                        <img src="{{ url_for('static', filename='images/logo.png') }}" alt="Logo" class="img-fluid"
                            data-bs-dismiss="modal" style="max-width: 200px;">
                    </div>
                    <form method="POST" action="/login">
                        <div class="mb-3">
                            <label for="username" class="form-label">Username</label>
                            <input type="text" class="form-control" id="username" name="username">
                        </div>
                        <div class="mb-3">
                            <label for="password" class="form-label">Password</label>
                            <input type="password" class="form-control" id="password" name="password">
                            {% if error %}
                            <div id="login-error" class="text-danger">{{ error }}</div>
                            {% endif %}
                        </div>
                </div>

                <!-- Modal Footer -->
                <div class="modal-footer">
                    {% if error %}
                    <div class="text-danger">{{ error }}</div>
                    {% endif %}
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
                    <button type="submit" class="btn btn-primary">Login</button>
                </div>

            </div>
        </div>
    </div>

    <script>
        $(document).ready(function () {
            // Include jQuery Keyframes library
            $.keyframe.define([{
                name: 'shake',
                '0%': { transform: 'translateX(0)' },
                '10%': { transform: 'translateX(-20px)' },
                '20%': { transform: 'translateX(20px)' },
                '30%': { transform: 'translateX(-20px)' },
                '40%': { transform: 'translateX(20px)' },
                '50%': { transform: 'translateX(-20px)' },
                '60%': { transform: 'translateX(20px)' },
                '70%': { transform: 'translateX(-20px)' },
                '80%': { transform: 'translateX(20px)' },
                '90%': { transform: 'translateX(-20px)' },
                '100%': { transform: 'translateX(0)' },
            }]);

            // Define the shaking effect
            function shakeModal(errorMsg) {
                $('#loginModal .modal-dialog').playKeyframe({
                    name: 'shake', // Name of the keyframe defined above
                    duration: '400ms', // Duration of the shake animation
                    timingFunction: 'linear', // Timing function for the shake animation
                    iterationCount: 1, // Number of times the animation should play
                    complete: function () { // Code to run after the animation completes
                        $('#login-error').text(errorMsg).show(); // Show the error message and set its text to the errorMsg parameter
                    }
                });
            }

            // Hide the error message initially
            $('#login-error').hide();

            // Trigger the shaking effect when the wrong input is detected
            $('form').submit(function (event) {
                var username = $('#username').val();
                var password = $('#password').val();

                // Replace with your actual input validation logic
                var inputIsValid = (username === 'admin' && password === 'pass1');
                var errorMsg = '{{ error }}'; // Get the Flask error message from the template

                if (!inputIsValid) {
                    shakeModal(errorMsg); // Pass the Flask error message to the shakeModal function
                    event.preventDefault(); // Prevent the form from being submitted
                } else {
                    $('#login-error').hide(); // Hide the error message
                }
            });

        });
    </script>
</body>

</html>

Solution

  • Since you suppress the submission of the form data with preventDefault(), the page will not reload and the error message will not appear because it is not created. The server is never contacted if incorrect entries are made.

    My suggestion is the following. You validate the credentials on the server side after sending them via AJAX. The response is in JSON format and contains either nothing or an error message, which you can display as usual. If the login data is correct, you will be redirected to the next page. The example uses a decorator to ensure that secured pages can only be accessed when the user is logged in. After a long period of inactivity, the user is automatically logged out. This is done based on the lifetime of the session.

    from datetime import timedelta
    from flask import (
        Flask, 
        jsonify, 
        redirect, 
        render_template, 
        request, 
        session, 
        url_for 
    )
    from functools import wraps
    
    app = Flask(__name__)
    app.secret_key = 'your secret here'
    
    def is_authenticated():
        username = session.get('user')
        return username and len(username.strip()) > 0
    
    def login_required(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            if not is_authenticated():
                return redirect(url_for('login'))
            return f(*args, **kwargs)
        return wrapper
    
    @app.before_request
    def before_request():
        session.permanent = True
        app.permanent_session_lifetime = timedelta(minutes=1)
    
    @app.route('/')
    @login_required
    def index():
        return render_template('index.html')
    
    @app.route('/login')
    def login():
        return render_template('login.html')
    
    @app.post('/login')
    def verify_login():
        username = request.form.get('username')
        password = request.form.get('password')
        if username == 'admin' and password == 'pass':
            session['user'] = username
            return jsonify()
        return jsonify(error='Invalid username or password. Pease try again.'), 422
    
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Login</title>
        <link 
            rel="stylesheet" 
            href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" 
            integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" 
            crossorigin="anonymous">
        <style type="text/css">
    
            html,
            body {
                height: 100%;
            }
    
            body {
                display: flex;
                align-items: center;
                padding-top: 40px;
                padding-bottom: 40px;
                background-color: #f5f5f5;
            }
    
            .form-signin {
                width: 100%;
                max-width: 330px;
                padding: 15px;
                margin: auto;
            }
    
            .form-signin .form-floating:focus-within {
                z-index: 2;
            }
    
            .form-signin input[type="text"] {
                margin-bottom: -1px;
                border-bottom-right-radius: 0;
                border-bottom-left-radius: 0;
            }
    
            .form-signin input[type="password"] {
                margin-bottom: 10px;
                border-top-left-radius: 0;
                border-top-right-radius: 0;
            }
    
            .login-error {
                font-size: 0.85em;
            }
    
        </style>
    </head>
    <body>
        <main class="form-signin">
            <form name="form-login">
                <div class="form-floating">
                    <input type="text" name="username" id="username" class="form-control" />
                    <label for="username">Username</label>
                </div>
                <div class="form-floating">
                    <input type="password" name="password" id="password" class="form-control" />
                    <label for="password">Password</label>
                </div>
                <div class="login-error text-center text-danger mb-3"></div>
                <button type="submit" class="w-100 btn btn-lg btn-primary">Login</button>
            </form>
        </main>
    
        <script 
            src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" 
            integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe" 
            crossorigin="anonymous"></script>
        <script 
            src="https://code.jquery.com/jquery-3.6.4.min.js" 
            integrity="sha256-oP6HI9z1XaZNBrJURtCoUT5SUnxFr8s3BzRl+cbzUq8=" 
            crossorigin="anonymous"></script>
        <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.keyframes.min.js"></script>
        <script type="text/javascript">
            $(function() {
    
                $.keyframe.define([{
                    name: 'shake',
                    '0%': { transform: 'translateX(0)' },
                    '10%': { transform: 'translateX(-8px)' },
                    '20%': { transform: 'translateX(8px)' },
                    '30%': { transform: 'translateX(-8px)' },
                    '40%': { transform: 'translateX(8px)' },
                    '50%': { transform: 'translateX(-8px)' },
                    '60%': { transform: 'translateX(8px)' },
                    '70%': { transform: 'translateX(-8px)' },
                    '80%': { transform: 'translateX(8px)' },
                    '90%': { transform: 'translateX(-8px)' },
                    '100%': { transform: 'translateX(0)' },
                }]);
    
                $('form[name="form-login"]').submit(function(event) {
                    event.preventDefault();
                    $.ajax({
                        method: 'POST', 
                        url: {{ url_for('login') | tojson }}, 
                        data: $(this).serialize(), 
                        cache: false, 
                        statusCode: {
                            422: function(jqXHR){
                                $('.form-signin').playKeyframe({
                                    name: 'shake', 
                                    duration: '400ms', 
                                    timingFunction: 'linear', 
                                    iterationCount: 1, 
                                    complete: function () { 
                                        $('.login-error').text(jqXHR.responseJSON.error);
                                    }
                            });
                            }
                        }
                    }).done(function() {
                        window.location.replace({{ url_for('index') | tojson }});
                    });
                });
            });
        </script>
    </body>
    </html>