Before we begin, let's talk about what a JWT is. A JWT is an encoded string which can be decoded to reveal its JSON contents. For example, here's a JWT:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ5b3VyLXVzZXJuYW1lIiwiZXhwIjoxNjY2NzIwNDkwfQ.K62wERMzH28I5g8IygVB0y-JuYdA0-7wuZkll2AyCNs

If we decode this using a tool such as jwt.io, we can see its contents:

Here you can see the algorithm type is HS256, the type of token is JWT, the sub is your-username, and the expiry time is 1666720490 (or, as I write, in about 29 minutes).

JWTs are used for authentication. Your server can generate a JWT with a given user ID, and give it to your client (e.g. the browser). Then on every request, the client sends your server the JWT, and that means the client is making the request on behalf of the user whose user ID is the sub of the JWT.

But if you can decode JWTs, doesn't that make them unsafe?

Although the user can see the contents of the JWT by decoding it, they can't change the JWT (which would allow them to impersonate other users). Users being able to see their own user IDs is usually not a problem. However, it's important to not store any data in a JWT that you don't want the user to see.

Create a JWT using the python-jose library

Let's create a JWT with Python and the python-jose library:

pip install python-jose

I'll start by defining three variables:

SECRET_KEY = "9b73f2a1bdd7ae163444473d29a6885ffa22ab26117068f72a5a56a74d12d1fc"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

You can generate your own secret key in a few different ways. In MacOS and Linux, run the terminal command openssl rand -hex 32. In Windows you can run this command using a Python interpreter: import secrets; str(secrets.SystemRandom().getrandbits(128)).

Now that you've got these three, generating a JWT is super easy!

from jose import jwt


def create_access_token(id_: str):
    expire = datetime.datetime.utcnow() + datetime.timedelta(
        minutes=ACCESS_TOKEN_EXPIRE_MINUTES
    )
    jwt_data = {"sub": id_, "exp": expire}
    encoded_jwt = jwt.encode(jwt_data, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

That's a function that, given a string, will create a JWT and put said string into the sub key of the JWT. In my code, I tend to put user IDs there.

Create a JWT with Flask and Flask-JWT-Extended

If you are using Flask, there's a very good extension called Flask-JWT-Extended that you can use instead of manually creating the JWTs.

That library already comes with a function create_access_token that you can use. It also supports token refreshing and blacklisting.

First, you should install the extension:

pip install flask-jwt-extended

Then, initialize it when you create your Flask app:

from flask import Flask
from flask_jwt_extended import JWTManager

app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = "9b73f2a1bdd7ae163444473d29a6885ffa22ab26117068f72a5a56a74d12d1fc"
jwt = JWTManager(app)

Then, when you implement a login endpoint for your Flask app, you can simply call create_access_token, like so:

from flask_jwt_extended import create_access_token
# other imports
...


# Endpoint defined using Flask-Smorest
@blp.route("/login")
class UserLogin(MethodView):
    @blp.arguments(UserSchema)
    def post(self, user_data):
        user = UserModel.query.filter(
            UserModel.username == user_data["username"]
        ).first()

        if user and pbkdf2_sha256.verify(user_data["password"], user.password):
            access_token = create_access_token(identity=user.id)
            return {"access_token": access_token}, 200

        abort(401, message="Invalid credentials.")

If you want to develop REST APIs with Flask, we've got a complete course that uses Flask-Smorest, Flask-JWT-Extended, Flask-SQLAlchemy, and a few other extensions to teach you how to build production-ready REST APIs. You can get the course here.

Thanks for reading! If you've got any questions or comments, use the comments section below!