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>
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>