How to Use JSON Web Tokens for Secure Authentication in Flask Applications

Yemi OjedapoYemi Ojedapo
8 min read

Passwords, credit card information, personal identification numbers (PINs) – these are all critical assets used for authorization and authentication. This means they need to be protected from unauthorized users.

As developers, we are tasked with safeguarding these sensitive bits of info, and it's important to implement strong secure measures in our applications.

Now, there are many authentication mechanisms available for securing data, like OAuth, OpenID Connect, and JSON Web Tokens (JWTs).

In this article, I'll show you how to use JWTs when securing information in APIs by integrating JWT-based authentication in a Flask application.

Here's what this article will cover:

Prerequisites

To follow along with this tutorial you will need:

  • An understanding of HTTP Methods
  • An understanding of how to create APIs in Flask
  • VS Code (or other similar) editor
  • A terminal

What is a JSON Web Token?

JSON Web Tokens, or JWTs, are an authentication mechanism used to securely transmit information between a client and a server in JSON format.

This information can be verified and trusted because it is digitally signed with the HMAC algorithm or a public/private key pair using RSA or ECDSA.

The tokens are encoded into three parts, each divided by a period, like this:

Header.Payload.Signature
  • Header: This defines the type of token (JWT) and the signing algorithm used.
  • Payload: This carries user-specific data like user ID, username, roles, and any additional claims you want to include. This payload is encoded in Base64 for maximum security.
  • Signature: This is a hashed combination of the header, the payload, and the server's secret key. It ensures the token's integrity and that any modifications to the token will be detected.

How Do JWTs Work?

To understand how JWTs work, you need to know what the tokens are meant to do. JWTs are not created to hide data but to ensure that the data being sent is authenticated. This is why the JWT is signed and encoded, not encrypted.

A JWT acts as a stateless means of transmitting data from a client to a server. This means that it doesn't store any session object in the browser, so the browser doesn't maintain a session state between requests.

Instead, JWTs use a token that is sent in a request header each time a request is made. This token confirms that the token sent is authenticated and is allowed access to make that request.

Let's look at how this happens:

  1. A user attempts to log in and sends a username and password to be verified by the server.
  2. The verification function carries out a check to see if there's a match in the database.
  3. A JWT is then generated by the server once the user is successfully authenticated (logged in) using their information (payload), such as user ID or username, and signs it using a secret key.
  4. The generated JWT is sent along as a bearer token with every request header to check if the user is authenticated to make that request.

How to Use JSON Web Tokens to Authenticate Flask Applications

To demonstrate how you can implement JWT authentication in Flask, we'll create a simple application that uses JWT for handling login functions and accessing protected routes.

1. Install the dependencies

Run this command to install the dependencies we'll need

pip install flask flask-bcrypt Flask-JWT-Extended

Next, make sure you import the dependencies and initialize your Flask application with this code:

from flask import Flask, jsonify, session, request, redirect, url_for
from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity, get_jwt


app = Flask(__name__)

////WRITE MAIN CODE HERE


if __name__ == "__main__":
    with app.app_context():
        app.run(debug=True)

2. Create a database and User Model

To do this, we'll use SQL-Alchemy, which is a Python SQL toolkit that makes it less complex to use SQL in Python scripts.

To set up SQL Alchemy in your application, follow these steps:

First, open your terminal or command prompt and enter the following command:

pip install sqlalchemy

This command installs SQLAlchemy in your Python environment, making it available in your project directory.

Next, configure your application to make use of your preferred Database Management System (DBMS). This tutorial will use the SQlite3 DBMS as it doesn't require a separate server:

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'

This code snippet instructs Flask-SQLAlchemy to create and use the site.db file in your project directory as the SQLite database for the application.

Then initialize the database in your application:

db = SQLAlchemy(app)

This instance of the database acts as a bridge between the application and the database.

Now create the User Model where we'll store the user's details in this tutorial:

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(20), unique=True, nullable=False)
    password = db.Column(db.String(80), nullable=False)
    is_active = db.Column(db.Boolean(), default=True)
    cart = db.Column(JSON, nullable=True, default=list)  # Make cart nullable

    # Define the relationship between User and CartProducts
    cart_products = relationship('CartProducts', backref="user", lazy="dynamic")
    # Define the relationship between User and Wishlists
    wishlists = db.relationship('Wishlists', backref='user', lazy=True)

    def __repr__(self):
        return f'<User {self.username}>'

Note: You can create other models using the same syntax to represent different data entities in your application.

3. Configure the application for JWT Authentication

To implement JWT authentication in your Flask application, import the necessary libraries and set up the appropriate configurations with this code:

from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy
from flask_jwt_extended import JWTManager, create_access_token, jwt_required

app = Flask(__name__)

# Configuration
app.config['SECRET_KEY'] = 'your_strong_secret_key'
app.config["JWT_SECRET_KEY"] = 'your_jwt_secret_key'
app.config['JWT_TOKEN_LOCATION'] = ['headers']

# Database Initialization
db = SQLAlchemy(app)

# JWT Initialization
jwt = JWTManager(app)

# Rest of the application code (routes, etc.)

This code snippet imports the following components needed for our application:

  • app.config['SECRET_KEY'] sets the Flask application's secret key which is used to securely sign session cookies and other security-related needs.
  • app.config['JWT_SECRET_KEY'] sets the secret key used to encode and decode JWTs in for Flask-JWT operations.
  • app.config['JWT_TOKEN_LOCATION'] specifies where the application should look for the JWT. Here it's set to look in the HTTP headers.

Once we've set this up, we can create the endpoints and routes that we intend to protect.

4. Create protected routes

Protected routes are the pages that we intend to keep hidden from unauthorized users.

For example, let's assume we are trying to get into a venue that's exclusive to members of a society. But a guard is securing the venue from "unauthorized users" like us. In this situation, we are the application's users, the venue is the URL we are protecting, and the guard protecting the venue is a @jwt_required decorator.

The @jwt_required decorator is used to protect specific routes that require authentication. This decorator will confirm that there's a JWT access token in the request headers before allowing access to the page:

@app.route('/get_name', methods=['GET'])
@jwt_required()
def get_name():
    # Extract the user ID from the JWT
    user_id = get_jwt_identity()
    user = User.query.filter_by(id=user_id).first()

    # Check if user exists
    if user:
        return jsonify({'message': 'User found', 'name': user.name})
    else:
        return jsonify({'message': 'User not found'}), 404

In this code snippet, we created a function that returns the name of the user after authentication. If the token is missing, invalid, or expired, the request will be denied, and typically, the server will return an HTTP 401 Unauthorized status.

5. Create a Login Page

In this endpoint, we'll create a function that accepts username and password credentials from the client request (for example, form data) and compares the credentials gotten from the user with the user data in the database. If there's a match, a JWT access token will be generated containing the user's information.

@app.route('/login', methods=['POST'])
def login():
    data = request.get_json()
    username = data['username']
    password = data['password']
    print('Received data:', username , password)

    user = User.query.filter_by(username=username).first()

    if user and bcrypt.check_password_hash(user.password, password):
        access_token = create_access_token(identity=user.id)
        return jsonify({'message': 'Login Success', 'access_token': access_token})
    else:
        return jsonify({'message': 'Login Failed'}), 401

In this example, the function checks the user's credentials against the database using bcrypt for secure password verification when a POST request is received. If the credentials are valid, the server generates a JWT for the user, allowing them to access protected routes.

Here's an example of a React form sending a POST request to the login endpoint:

import React from "react";
import axios from "axios";
import { useState } from "react";
import Footer from "./Footer";
// import "./Login.css";

function Login() {
  const [password, setPassword] = useState("");
  const [username, setUsername] = useState("");

  const handleLogin = async (event) => {
    event.preventDefault();
    const data = {
      username: username,
      Password: password,
    };

    try {
      const response = await axios.post("http://localhost:5000/login", {
        username,
        password,
      });
      localStorage.setItem("access_token", response.data.access_token);
      // Redirect to protected route
      alert("Login successful");
    } catch (error) {
      console.error(error);
      // Display error message to user
    }
  };

  const handleUsernameChange = (event) => {
    setUsername(event.target.value);
  };

  const handlePasswordChange = (event) => {
    setPassword(event.target.value);
  };

  return (
    <div >

          <form method="post" >
              <input
                type="text"
                id=""
                placeholder="Username"
                name="username"
                required
                value={username}
                onChange={handleUsernameChange}
              />
              <input
                type="text"
                id=""
                placeholder="Your email"
              />
              <input
                type="password"
                required
                placeholder="Your Password"
                name="password"
                value={password}
                onChange={handlePasswordChange}
              />
          </form>
          <button type="submit" onClick={handleLogin}>
            Log In
          </button>

    </div>
  );

}

export default Login;

In this React component, we provided a login form that uses Axios to send a POST request to the login endpoint. It manages username and password inputs using React's useState hook and submits these values once the form is submitted.

If the login is successful, it stores a JWT in local Storage. This allows the client-side application to retrieve the token easily when making authenticated requests to the server.

Image

Conclusion

In this article, we've learned how to secure APIs with JSON Web Tokens in Flask. We covered the basics of JWTs, how they work, and provided a step-by-step process for implementing this method of authentication. This included everything from installing necessary dependencies to creating user models and protecting routes.

You can build upon this foundation, such as by adding refresh tokens, integrating with third-party OAuth providers, or handling more complex user permissions.

0
Subscribe to my newsletter

Read articles from Yemi Ojedapo directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Yemi Ojedapo
Yemi Ojedapo