Authentication – 401 followed by 200 status code

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

Leave a Reply