DEV Community

Cover image for How to authenticate a Flask API using JWT.
Hesbon limo
Hesbon limo

Posted on

How to authenticate a Flask API using JWT.

Introduction

Security is one of the most important aspects of any organization. API authentication is one way in which APIs are secured, making sure that only authorized users access sensitive data from protected servers.

JSON Web Token (JWT) is a common method of user authentication and authorization standard used to exchange data securely. Made of three components, a header, a payload, and a signature, here are the steps involved in the authorization process:

  • Authentication. The client sends the user credentials to the server, and the server authenticates the client and generates JWT containing the users' information
  • Issuing the token. The JWT created by the server is sent to the client which is stored for future use.
  • Sending the token. When the client wants to access a protected resource it sends the JWT in the authorization header of the http request.
  • Verifying the token. The server receives the request and uses the secret key that was used to sign in to verify the JWT. If the JWT is valid, the server extracts the information contained in it and determines which action is the client authorized to perform.
  • Authorizing the request. If the user is authorized, the server responds with the data requested else it returns an error message.

In this article, we are going to discuss how to authenticate a Flask API step by step.

Steps

Step 1: Installing Flask and JWT

First, let's create a folder in the VS Code code editor as follows:

mkdir flask-auth
Enter fullscreen mode Exit fullscreen mode

Now, let's create a virtual environment and activate it inside this folder as follows:

python3 -m venv env
Enter fullscreen mode Exit fullscreen mode
source env/bin/activate
Enter fullscreen mode Exit fullscreen mode

Next, let's install Flask and JWT using the following commands:

pip install flask
Enter fullscreen mode Exit fullscreen mode
pip install PyJWT
Enter fullscreen mode Exit fullscreen mode

After installing Flask and JWT, let's make a file called app.py using the following command:

touch app.py
Enter fullscreen mode Exit fullscreen mode

Now, let's create a folder that hold our login.html and name it templates as follows:

mkdir templates
Enter fullscreen mode Exit fullscreen mode

Inside the templates folder create a file called login.html as follows:

touch login.html
Enter fullscreen mode Exit fullscreen mode

After completing all these steps, your project folder should look like this:

Screenshot from 2025-01-31 22-29-40

Step 2: Importing necessary libraries

Inside the app.py file we had created in the previous step write the following code:

import jwt
from flask import Flask, request, jsonify, make_response, render_template, session, flash
from datetime import datetime, timedelta
from functools import wraps
Enter fullscreen mode Exit fullscreen mode

Let's discuss the imports above;

  • jwt: It imports PYjwt which is used to encode and decode JSON Web Tokens.
  • Flask: This is the core of the Flask web framework. It is used to create the instance for the web application.
  • request: Used to create HTTPS requests.
  • jsonify: It is used to convert Python dictionaries into JSON responses.
  • make_response: It is used to create custom HTTP responses.
  • render_template: It is used to render HTML templates using jinja2, which is the templating engine that flask uses.
  • session: It allows you to store and access variables of the current user between requests.
  • flash: It is used to display an error or success message to the user.
  • datetime: It is a class that represents date and time.
  • timedelta: Represents the difference between two dates and times. It is mainly used to set the expiration time of Json Web Tokens.
  • wraps: This is used to decorate functions, preserving their metadata (like name, docstring, etc.) when they are wrapped by another function. It’s often used in scenarios like creating custom decorators.

Step 3: Creating Flask instance

Before creating the Flask instance follow these steps to create a secret key.

  • In the terminal, run the following commands:
python3
Enter fullscreen mode Exit fullscreen mode

then:

import uuid
Enter fullscreen mode Exit fullscreen mode

then:

uuid.uuid4().hex
Enter fullscreen mode Exit fullscreen mode

Now copy the secret key and paste it into your app instance. Replace secret with your secret key:

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret'
Enter fullscreen mode Exit fullscreen mode

Step 4: Define the public Route

Let's define a public route that can be accessed by anyone without authorization using the following code:

@app.route('/public')
def public():
    return 'This is a public page'

Enter fullscreen mode Exit fullscreen mode

Run the application and open the following URL in the browser:

http://127.0.0.1:5000/public
Enter fullscreen mode Exit fullscreen mode

It should look like this in your browser;Screenshot from 2025-01-31 23-23-00

Step 5: Define an auth route

Let's define an auth route that can only be accessed by users who are registered on the app. We will also use the token_required decorator to make sure that users with token access the route:

@app.route('/auth')
@token_required
def auth():
    return 'You are verified. Welcome to your dashboard!'
Enter fullscreen mode Exit fullscreen mode

Now let's define the token_required function which will be used for authorized routes.

def token_required(func):
    @wraps(func)
    def decorated(*args, **kwargs):
        token = request.args.get('token')
        if not token:
            return jsonify({'Alert!': 'Token is missing'}), 401
        try:
            payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
        except jwt.ExpiredSignatureError:
            return jsonify({'Alert!': 'Token has expired'}), 401
        except jwt.InvalidTokenError:
            return jsonify({'Alert!': 'Invalid Token'}), 401
        return func(*args, **kwargs)  # Correctly return the wrapped function
    return decorated

Enter fullscreen mode Exit fullscreen mode

Let's breakdown the code above;
The @token_required decorator is applied to the function to make sure that only requests with a valid JWT token can access the route.
In line five we are retrieving the token from the request’s query parameters using request.args.get('token'). If the token is missing the condition becomes true hence, the 401 error is returned.

Inside line seven the code tries to decode the provided token using jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256']). If the token has expired, the code catches the ExpiredSignatureError and returns the 401 unauthorized error.

Line 11 checks if the token is invalid and if the condition is true, it returns the 401 unauthorized error.

Let's run the app on the terminal and enter the following URL in the terminal:

http://127.0.0.1:5000/auth
Enter fullscreen mode Exit fullscreen mode

The output should look like the one below:
image
We are getting the expected output that Token is missing because we are not yet logged in. Let's move to the next step and see how we can get the token.

Step 6: Define the login method

We need to define a login route that we will use to log in to the app and access the token. We will pass the username and the password and if they match the ones in the route the login session variable will be set to True. Here is the code for the login route:

@app.route('/login', methods=['POST'])
def login():
    username = request.form.get('username')
    password = request.form.get('password')

    if username and password == '123456':  # Fix incorrect comparison
        session['logged_in'] = True
        token = jwt.encode({
            'user': username,
            'exp': datetime.utcnow() + timedelta(seconds=150)  # Use 'exp' for expiry
        }, app.config['SECRET_KEY'], algorithm='HS256')

        return jsonify({'token': token})  # No need for `.decode('utf-8')`
    else:
        return make_response('Unable to verify', 403, {
            'WWW-Authenticate': 'Basic realm="Authentication Failed!"'
        })

Enter fullscreen mode Exit fullscreen mode

Let's go ahead and breakdown the code above;

The condition from line sis checks if the username and the password are correct. If the two are correct, the code creates a JWT token and allows the user to log in. If the username and the password are not correct, the code from line fourteen is executed where the system returns a 403 Forbidden error, saying "Unable to verify".
Now let's go ahead and define a login.html template that will display the login form. In the login.html folder that we had created earlier, add this code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login</title>
</head>
<body>
    <form action="/login" method ='POST'>
        <input type="username" name="username" placeholder="username"><br><br>
        <input type="password" name="password" placeholder="password"><br><br>
        <input type="submit" value="Login">
    </form> 

</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Run the app once again and input any username and password as 123456:

http://127.0.0.1:5000
Enter fullscreen mode Exit fullscreen mode

The output after inputting the correct password should look like this:
image
After logging in we get the token as shown above.

Let's use the token to access the data in the private route. Open JWT.io and paste the token as shown below:
image
Now we can access the data as shown above.

Final Thoughts

In this article, we have discussed the importance of API security in your application and how JWT authentication works. We also developed different routes of a Flask API and also learned how to come up with a secret key.

Finally, we used a JWT token to access data on the JWT.io website. In conclusion, I recommend checking out my GitHub link here for the code.

Top comments (0)