I am facing an odd behaviour where I try to authenticate a user over JWT on a secure page link, which prompts the user to fill a form.
When the user submits name and password, I log:
127.0.0.1 - - [05/Oct/2023 12:15:08] "POST /authenticate HTTP/1.1" 200 -
127.0.0.1 - - [05/Oct/2023 12:15:08] "GET /fanbase HTTP/1.1" 401 -
Console seems to blink rapidly showing token being generated, stored, but somehow it disappears and is not passed to requested link.
And the browser screen then shows:
{
msg: "Missing Authorization Header"
}
Then I refresh home and click again on the same page link, and it works, being passed at header.
Token in incoming request: Bearer eyJhbGciOiJIUzI1DSMFNFDHN79878CI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTY5NjUxODkwOCwianRpIjoidufghfuighsigudfggsdufgsihgItY2UxZDAyOWNmYTlmIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6InRlc3RAZ21haWwuY29tIiwibmJmIjoxNjk2NTE4OTA4LCJleHAiOjE2OTY1MTk4MDh9.PC4TdX_XAgl91w1YQ8ZbDj98hfNgNZSHXsue0HphnVY
127.0.0.1 - - [05/Oct/2023 12:20:35] "GET /fanbase HTTP/1.1" 200 -
This is my server-side code:
import os
from flask import Flask, render_template, jsonify, request
from flask_jwt_extended import JWTManager, jwt_required, create_access_token, get_jwt_identity
# Initialize Flask app
app = Flask(__name__)
app.config['SECRET_KEY'] = 'supersecretkey'
app.config['JWT_SECRET_KEY'] = 'your-jwt-secret' # replace this with your JWT secret key
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///your_database.db'
# Import after app is initialized
from models import db, User, Role
# Initialize Database
db.init_app(app)
# This is important
with app.app_context():
db.create_all()
# Initialize JWT Manager
jwt = JWTManager(app)
@app.route('/')
def home():
image_files = [f for f in os.listdir('static/images/') if f.endswith('.jpeg')]
return render_template('index.html', image_files=image_files)
@app.route('/login', methods=['GET'])
def login():
return render_template('login.html')
@app.route('/verify_token', methods=['POST'])
@jwt_required()
def verify_token():
auth_header = request.headers.get('Authorization')
print(f"Received auth header: {auth_header}")
headers = dict(request.headers)
print("Headers in verify_token:", headers) # Debug print
identity = get_jwt_identity() # Get identity from token
print("Identity:", identity) # Debug print
return jsonify({"message": "Token is valid"}), 200
@app.route('/authenticate', methods=['POST'])
def authenticate():
data = request.json
username = data.get('username')
password = data.get('password')
user = User.query.filter_by(username=username).first()
# If the user exists, verify the password
if user:
if user.password == password:
access_token = create_access_token(identity=username) # instead of identity={'username': username}
return jsonify(access_token=access_token), 200
elif not user:
# Create a new user if not found
new_user = User(username=username, password=password)
db.session.add(new_user)
db.session.commit()
# Issue an access token for the new user
access_token = create_access_token(identity=username)
return jsonify(access_token=access_token), 200
else:
return jsonify({"authenticated": False}), 401
@app.route('/fanbase', methods=['GET'])
@jwt_required()
def fanbase():
print("Token in incoming request:", request.headers.get("Authorization"))
username = get_jwt_identity()
return render_template('fanbase.html', username=username)
if __name__ == '__main__':
app.run(debug=True)
And these my client side templates:
Index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Music Project</title>
<link rel="stylesheet" type="text/css" href="/static/css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script type="module">
import jwtDecode from 'https://cdn.jsdelivr.net/npm/jwt-decode@3.1.2/+esm';
window.jwtDecode = jwtDecode;
</script>
<script>
console.log('jwt_decode should be loaded now', window.jwt_decode);
</script>
<script src="/static/js/p5.min.js"></script>
<script src="/static/js/sketch.js" defer></script>
<script src="/static/js/script.js" defer></script>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function() {
try {
var image_files = {{ image_files|tojson }};
var random_index = Math.floor(Math.random() * image_files.length);
var random_image = "/static/images/" + image_files[random_index];
document.getElementById("main-container").style.backgroundImage = "url('" + random_image + "')";
} catch (error) {
console.error("Error occurred:", error);
}
});
</script>
</head>
<body>
<div id="p5-container"></div>
<div id="main-container">
<!-- Image will be set as the background -->
<div id="social-icons">
<a href="https://www.facebook.com/yourpage"><i class="fab fa-facebook-f"></i></a>
<a href="https://www.instagram.com/super.condutores"><i class="fab fa-instagram"></i></a>
<a href="https://spotify.link/fJMjUIapADb"><i class="fab fa-spotify"></i></a>
<a href="https://www.youtube.com/super.condutores"><i class="fab fa-youtube"></i></a>
<a href="https://www.discord.com/super.condutores"><i class="fab fa-discord"></i></a>
</div>
<nav id="main-nav">
<ul>
<li><a href="#musica">Música</a></li>
<li><a href="#videos">Vídeos</a></li>
<li><a href="#fotos">Fotos</a></li>
<li><a href="#shows">Shows</a></li>
<li><a href="#fanbase">Fanbase</a></li>
</ul>
</nav>
</div>
<script>
$(document).ready(function() {
$("li > a[href='#fanbase']").on("click", function(e) {
e.preventDefault();
var token = localStorage.getItem('access_token');
console.log("Retrieved Token:", token);
if (!token) {
console.log('No token found, redirecting to login');
window.location.href = "/login";
return;
}
var headers = {'Authorization': 'Bearer ' + token};
console.log("Headers:", headers);
$.ajax({
beforeSend: function(xhr) {
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
},
url: '/fanbase',
method: 'GET',
success: function(response) {
console.log('Successfully fetched fanbase', response);
},
error: function() {
console.log('Failed to fetch fanbase');
}
});
});
});
</script>
</body>
</html>
and login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
<link rel="stylesheet" type="text/css" href="/static/css/style.css">
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function() {
var image_files = ['website.002.jpeg', 'website.012.jpeg', 'website.014.jpeg','website.018.jpeg','website.060.jpeg','website.062.jpeg','website.068.jpeg'];
var random_index = Math.floor(Math.random() * image_files.length);
var random_image = "/static/images/" + image_files[random_index];
document.body.style.backgroundImage = "url('" + random_image + "')";
});
</script>
</head>
<body class="login-page">
<div class="form-container">
<form id="login-form">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required><br>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required><br>
<input type="submit" value="Login">
</form>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
var form = document.getElementById('login-form');
form.addEventListener('submit', function(event) {
event.preventDefault();
var username = document.getElementById('username').value;
var password = document.getElementById('password').value;
fetch('/authenticate', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: username,
password: password
})
})
.then(response => {
if (response.status === 200) {
return response.json();
} else {
throw new Error('Failed to authenticate');
}
})
.then(data => {
if (data.access_token) {
localStorage.setItem('access_token', data.access_token);
window.location.href = '/fanbase'; // Comment this out
console.log('Token stored but not redirecting'); // Add this line for debug
} else {
alert('Authentication failed');
}
})
.catch(error => {
console.log('Error:', error);
alert('Authentication failed');
});
});
});
</script>
</body>
</html>
Why is it not working at first try?!
Any help fixing this would be much appreciated, thanks in advance.
>Solution :
localStorage.setItem('access_token', data.access_token);
You are storing the token in the browser’s local storage where it accessible only to client-side JavaScript.
Then you are navigating to /fanbase
.
window.location.href = '/fanbase';
/fanbase
is looking in the HTTP request headers for the token and you’ve done nothing to put it there.
Thus you get the error.
Elsewhere in your code, you have a whole mass of code to make an Ajax request to the same URL where you do include that header.
$.ajax({ beforeSend: function(xhr) { xhr.setRequestHeader('Authorization', 'Bearer ' + token); }, url: '/fanbase',
… but you aren’t using that when you just navigate there with location.href
.
(Note that there is no way to set custom HTTP request headers when navigating so you’d need to take a different approach, such as using a Cookie instead of an Authorization header).