Hire the author: Bijita K

Introduction

In this article, we will learn how to build a token validation decorator in Flask. We will use the decorator to take Okta access tokens, verify their validity, and extract user-related information from them. My motivation for creating this project was to find a method to authenticate users on my Flask APIs using Okta access tokens.

Prerequisites

This tutorial assumes that you are familiar with:

  • Basic understanding of Flask
  • Basic understanding of Docker

Here is the link to the GitHub repository of the project.

Glossary

Decorator: A decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it.

Step-by-step Procedure

Step 1: Setting up the initial Flask project structure

We will begin by setting up the initial Flask project structure. I have created a starter branch in the tutorial’s GitHub repository for this purpose. Let’s start by cloning the branch using the command below:

git clone https://github.com/ldtalent/bijitak-how-to-build-token-introspection-decorator-in-flask-for-okta.git

After cloning the repository, create a file named .env to store the environment variables. Copy the contents from the env_template file to the .env file. For now, we will skip the Okta-related variables and comment them out. Add values to the other variables as shown below:

FLASK_APP=core/run.py
FLASK_CONFIG=development

Step 2: Setting up the Okta app integration

Now, we’ll set up our Okta app integration required for the token validation decorator. To do this, go to your Okta account and create a new app integration as follows:

Here, I named it LD_app and added the required configurations. After this, you can click save to create the app integration. Now, let’s add the values for the Okta-related environment variables to our .env file, which we skipped in Step 1.

The value of the OKTA_CLIENT_ID variable can be found on the app integration page. Similarly, you can find the values for the rest of the variables by following the steps below:

  1. Go to the security tab and choose the API option.
  2. Under the authorization servers tab, select your authorization server.
  3. Then, under the settings tab, you will find the metadata URI link which will contain the values of the variables.

Step 3: Adding the token validation decorator

Once you have set up the Okta app integration, you can set up the decorator. For this, create a new file named decorators.py inside the core folder. Copy the content from below and add it to the decorators.py file:


import os
import jwt
import json
import requests
from core import cache
from functools import wraps
from flask import request, abort, g
def login_required(func):
"""
Decorator to check the validity of the Okta JWT
JWT are also decoded to get user data which is set in g object
"""
@wraps(func)
def wrap(*args, **kwargs):
JWT_ISSUER = os.environ.get('OKTA_ISSUER')
CLIENT_ID = os.environ.get('OKTA_CLIENT_ID')
authorization = request.headers.get("authorization", None)
if not authorization:
abort(403)
try:
token = authorization.split(' ')[1]
key_id = jwt.get_unverified_header(token)['kid']
jwk = get_jwk(key_id, cache=cache)
token_data = jwt.decode(
token, jwk, verify=True, issuer=JWT_ISSUER,
audience=os.environ.get('OKTA_AUDIENCE'), algorithms=['RS256']
)
except Exception as e:
print(e, "exception occured.", flush=True)
abort(403)
if token_data['cid'] == CLIENT_ID:
g.user = token_data['sub']
g.user_id = token_data['uid']
else:
abort(401)
return func(*args, **kwargs)
return wrap
def get_jwk(kid, cache=None):
"""
Gets JWK with key id
"""
# if cache exists then load from cache
key = None
if cache:
key = cache.get(kid)
if key is None:
keys = get_jwks()
for k in keys:
if cache:
cache.set(k['kid'], k)
if k['kid'] == kid:
key = k
if key:
return jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(key))
raise Exception
def get_jwks():
"""
Get JWKs from OpenID Provider
"""
jwks_uri = os.environ.get('OKTA_JWKS_URI')
jwks = requests.get(jwks_uri)
return jwks.json().get('keys')

view raw

decorators.py

hosted with ❤ by GitHub

Now, let’s discuss what the above code does and how it works step by step:

  1. Initially, the login decorator takes in the request and extracts the auth token from the header.
  2. It uses the auth token to get the key id. The key id is a unique identifier for the key used to sign the token. It will further be used to retrieve the public key from a JSON web key (JWK) set.
  3. Then, the get_jwk function is called.
  4. The get_jwk function receives the key id from the decorator function and searches the cache to see if anything is present regarding the key id. If present, it uses that to find the related JSON web key.
  5. If not, it proceeds to call the get_jwks function. This function uses issuer, client_id, and jwks_uri to get the JSON web key set and return it back to the get_jwk function.
  6. The get_jwk function uses the received JSON web key set to find the related JSON web key and also stores it in the cache.
  7. Subsequently, the key is returned to the login decorator function, which is finally used to decode the token.
  8. Once decoded, we can now extract information as needed from the token. In the code, the value of sub from the token data is extracted and stored as a global variable user.
  9. The final step is to compare the client id cid extracted from the token data to the client id of your Okta application. This is done to ensure that the Flask app is giving access only to the correct users.

Step 4: Implementing the decorator

To implement the login_required token validation decorator we will update our auth_app/routes.py app to add it to our existing endpoint. You can copy the updated code as shown below:


from flask import g
from core.auth_app import bp
from core.decorators import login_required
@bp.route('/test', methods=['GET'])
@login_required
def test():
context = {
"user_id": g.user_id,
"email": g.user
}
return context, 200

view raw

routes.py

hosted with ❤ by GitHub

The decorator will check the token sent in the request to the endpoint. If the token is valid, the endpoint will return the user’s id and email extracted from the token data.

Step 5: Running the project

Finally, we can run our project. We will use Docker to run the project. Use the commands below to build the docker image using our docker-compose file and run the container.

docker compose build
docker compose up -d

Here, you may come across an issue where the compose command may not be recognized. In this case, you will need to replace docker compose with docker-compose. This depends on which version you have installed in your system.
Now, the project will be ready and running.

Learning Tools

To understand this tutorial, you must understand the basics of Flask. Therefore, I recommend starting with the official documentation of Flask if needed. Along with that, you can also use the Okta documentation to learn more about the app integration process. Similarly, understanding how to work with docker will help make the app setup and running process much smoother and more efficient. For this, you can use the official docker documentation. Moreover, you can also choose to learn from the many videos and blogs available online.

Learning Strategy

While creating the token validation decorator for this project, StackOverflow was quite helpful. You can also use the site JSON Web Tokens – jwt.io to decode the Okta tokens to see what parts the token consists of. It can help to clarify what exactly we are decoding and how a JWT token can store the information.

Reflective Analysis

Working on this project to create a token validation decorator has been quite beneficial for me to understand the process of how a JWT token works and is decoded. There exist some packages that can be used for the same purpose, but the above tutorial makes the steps that are taking place quite clear. The process of decoding any other JWT token is similar. Therefore, it has been helpful not just for creating an Okta token validation decorator but for others as well.

Conclusion

In conclusion, I hope the above tutorial will be helpful to anyone attempting to create a similar project or just to understand the process of how the token validation decorator works. Additionally, this project can also be applied to decode other tokens like Auth0 by making a few customizations on the information extraction step. You can check out the GitHub repository of this project.

If you want to learn more about Docker you can check out this article on how to dockerize a NodeJS application.

Hire the author: Bijita K

Highly committed and reliable software developer with good experience in backend development.