Build simple authentication API using FAST API with ES256 encryption in 10 mins

Daniel Chu
8 min readNov 14, 2021

--

Good day to everyone! In this article, we will learn about how to create an authentication API using FAST API with ES256 encryption JWT token.

This article covered the following topics:

  1. Creating a FAST API project and install PyJWT package.
  2. Create and validate ES256 JWT token.
  3. Validation by accessing the protected endpoints with JWT token
  4. Build this project into a container image for microservice.

Technology used

  1. FastAPI for serving authentication API with asynchronous pattern.
  2. PyJWT for generating and validating ES256 JWT token.
  3. JWT dashboard for visualising the token we created.
  4. OpenAPI(previously known as swagger) for documenting API specification.
  5. Docker for building container image.
  6. Postman

Getting Started

If you have used Flask before, you will find ease to work with FAST API because FAST API provide production ready code with minimal amount of code. FAST API also come with automatic interactive documentation with OpenAPI. Apart from good developer experience, the ASGI (Asynchronous Server Gateway Interface) made FAST API become one of the fastest Python frameworks available.

Step 1: Creating a FAST API project and install PyJWT package.

You may follow the tutorial or follow me to create a FAST API project.

Please make sure you have installed python 3 in your workstation. You may install python3 by using command brew install python in MacOS.

# Create new directory for this project
mkdir auth-fastapi
cd auth-fastapi
# Install the fastapi module
pip install fastapi
pip install uvicorn[standard]

Define root endpoint

In the auth-fastapi directory, create a file called main.py and paste the following content in the main.py

from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}

Run the project

You may run the project by the command below.

uvicorn main:app --reload

You can verify the running status by open a new terminal and run the command curl http://127.0.0.1:8000

If you see the similar message above, you have successfully run the FAST API project!

Install PyJWT python module

Now, you can run the following command to install PyJWT.

pip install pyjwt

Step 2: Create and validate ES256 JWT token.

In this article, we will have 2 endpoints:

  1. /login endpoint to login user and generate token for the user.
  2. /secret endpoint, only user with validate token can access this endpoint.

Generate ES256 public and private key set in JSON Web Key(JWK) format

JWK followed RFC7517 standard.

Go to simple JSON Web Key generator and generate the ES256 key set in the following format.

Warning: Use the generated key set only for development/testing.

From https://mkjwk.org/
# example 
{
"keys": [
{
"kty": "EC",
"d": "jwrAjoo8YOk-ryUgQtarw4P-id8c3BD0jOoWJ3PgNyU",
"use": "sig",
"crv": "P-256",
"kid": "LEu_K_tGsARSVoaGxP6nCjhYl9XB6jpuZH0vdpTEDOU",
"x": "o4lf0hDz0oej8fsiPP5UjrO8B0X-96ssgMxh1NVAVzA",
"y": "9LQcldbXC9e8KUzw6pVU-QFY2hVluMHc9-7s08iZEY4",
"alg": "ES256"
}
]
}

You may click the blue button “Copy to Clipboard” to copy the JWK key set and save into a file called jwks-es.json for later usage.

Convert JWK key set into BASE64 and use in environment variables

We adopt the config in the twelve factor app principle and make the JWK key into an environment variable.

Run the following command where your jwks-es.json file is located at.

base64 jwks-es.json > output.txt

Verify the content by running cat output.txt

Now put the base64 value from JWK key set into .env

  1. SECRET_KEY : any value for hashing the password
  2. JWT_ALGO value from JWK key set alg , which is the algorithm we used : ES256.
  3. ES256_KID value from JWK key set kid
  4. ES256_KEY is the entire base64 string converted from the previous step.
# .env example
SECRET_KEY=PJQwH9Tnzx5IYrIFU_C2mEqduuWf257FkUs5NyYl8L0
JWT_ALGO=ES256
ES256_KID=5h_n5-R5z4frNsWmp9JuxIS0_hHJTaI3h1figd17PZI
# base64 jwks-es.json > output.txt
ES256_KEY=ewogICAgImtleXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAia3R5IjogIkVDIiwKICAgICAgICAgICAgImQiOiAiVExqRm9lOGFhRDE4TXJqQ09ndklSQWlYcWR3RzRzdWNUVFRIS2xVTDJQWSIsCiAgICAgICAgICAgICJ1c2UiOiAic2lnIiwKICAgICAgICAgICAgImNydiI6ICJQLTI1NiIsCiAgICAgICAgICAgICJraWQiOiAiNWhfbjUtUjV6NGZyTnNXbXA5SnV4SVMwX2hISlRhSTNoMWZpZ2QxN1BaSSIsCiAgICAgICAgICAgICJ4IjogImVHSlBGeGMyQk80RWVwa0lDa1oxY2R0dVhyQ1ZnTXlPNzNfTjU5dFU0MjQiLAogICAgICAgICAgICAieSI6ICJRU1UzVFF6TjN5emdGRGtfUlZUZ1RnN0cwUTBacnRhTmhKRTBJMW1nVklnIiwKICAgICAgICAgICAgImFsZyI6ICJFUzI1NiIKICAgICAgICB9CiAgICBdCn0=

Read environment variable using python-decouple

To read the value of an environment variable in python, one can simply use os.environ .

I prefer using python-decouple because it provides more flexibility, such as defining comprehensive default values and properly converting values to the correct data type.

Create a new directory called config in the auth-fastapi . Place or create the following files in the config folder.

.
├── __init__.py
├── jwks-es.json
└── output.txt

The content of __init__.py

from pydantic import BaseSettings
from decouple import config
class AuthSettings(BaseSettings):
JWT_SECRET_KEY: str = config('SECRET_KEY')
ES256_KEY: str = config("ES256_KEY")
JWT_ALGO: str = config("JWT_ALGO")
ES256_KID: str = config("ES256_KID")
class Settings( AuthSettings):
pass
settings = Settings()

This __init__.py in the config folder required to make Python treat directories containing the file as packages. We can import the config by from config import settings

Create models to validate user email and password

Now, your auth-fastapi folder should have the following contents.

.
├── auth.py
├── config
│ ├── __init__.py
│ ├── jwks-es.json
│ └── output.txt
├── main.py
├── models
│ ├── __init__.py
│ └── user.py
├── requirements.in
└── requirements.txt

We can utilise pydantic helps in validation.

Create a new directory called models in the auth-fastapi . Create the following files in the models folder.

.
├── __init__.py
└── user.py

The content of __init__.py in models directory

from .user import DB_User_Model, AuthModel_User

The content of user.py in models directory

import re
from typing import Optional
from pydantic import BaseModel, validator
from pydantic.dataclasses import dataclass
@dataclass
class DB_User_Model():
key: str
email: str
hashed_password: str
is_active: Optional[bool]
is_verified: Optional[bool]
is_superuser: Optional[bool]
class AuthModel_User(BaseModel):
email: str
password: str
@validator('password')
def passwords_validation(cls, v):
if not custom_password_validator(v):
raise ValueError("Password must have minimum 8 characters to maximum 20 characters, 1 capital letter, 1 small letter, and 1 special character from '!@#$%^&*()'")
return v
def custom_password_validator(password):
reg = "^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,20}$"
pat = re.compile(reg)
mat = re.search(pat, password)
return mat

The AuthModel_User will be responsible for ensuring the password have 8–20 characters, 1 capital letter, 1 small letter, and 1 special character from !@#$%^&*() .

DB_User_Model can be used in future to save user profile into database.

Code for generating JWT token from JWK key set

Create auth.py under auth-fastapi directory.

Insert the content of auth.py , please refers to the content below.

Append new content in the main.py under auth-fastapi directory.

For new content of main.py , please refers to the content below.

Final step for generating JWT token

We are now ready to generate the JWT token! In order to do that, we need to install all the dependencies we used on the above sections.

All the required dependencies are listed in requirements.in . In order to generate the fresh and updated dependency list, we are using pip-tools to achieve the goals

How pip-tools works, from https://pypi.org/project/pip-tools/

Run pip install pip-tools for installation.

Create a new file, called requirements.in and place the following contents:

fastapi
uvicorn[standard]
pyjwt
cryptography
passlib[bcrypt]
python-decouple

Run pip-compile requirements.in to generate the requirements.txt with up-to-date dependency list!

Lastly, run pip install -r requirements.txt to install all the modules we required to run this project.

Step 3: Validation by accessing the protected endpoints with JWT token

Please remember keep your application open by running unicorn main:app --reload

Open postman application and set to post method, target http://127.0.0.1:8000/login and set the body to rawJSON and paste the content in the blank area

{
"email": "testing@gmail.com",
"password": "!Kingh_a1234"
}

Click the send button to trigger a response.

Below is the desired response! We have generated the JWT token with ES256 encryption!

To validate the token, we can access the protected endpoint secret by using the generated token.

Copy the access_token and open new tabs in the Postman application.

Click the Authorization tabs and select the type to be Bearer Token .

Paste the access_token into the area for Token like the screenshot below.

Click the send button to trigger the response.

You should now be able to see the following content with HTTP status 200 .

"Top Secret data only authorized users can access this info"

HOORAY! 🎉

Step 4: Build this project into container image for microservice.

We have come so far! Let us build this project into container image.

Open docker desktop and wait for it to become ready.

Create a file, called Dockerfile under auth-fastapi directory.

Put the following content in Dockerfile

FROM tiangolo/uvicorn-gunicorn:python3.9-slim
LABEL maintainer="danielchu"
ENV WORKERS_PER_CORE=4
ENV MAX_WORKERS=24
ENV LOG_LEVEL="warning"
ENV TIMEOUT="200"
RUN mkdir /auth
COPY requirements.txt /auth
WORKDIR /auth
RUN pip install -r requirements.txt
COPY . /auth
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Now build the image by running

docker build -t auth-es256:0.0.1 .

If you see the result similar to the screenshot below, the image has been built successfully!

You can run the container image by

docker run -p 8080:8000 auth-es256:0.0.1

Now you may go to the browser and navigate to http://localhost:8080/docs to view the OpenAPI specification for all endpoints.

Summary

In this article, we have created an authentication API endpoint with ES256 encryption. Furthermore, we enforced the password when user submit, need to have 8–20 characters, 1 capital letter, 1 small letter, and 1 special character from !@#$%^&*() . Lastly, we adopt the config from the 12 factors app and make a container image based on this project.

Hope this article can help you quickly get started to build an authentication module. In future, we can extend this project into using a database and a frontend to allow users to interact with.

The entire project is now hosted on Github. You can find the project from here.

Comments are welcomed! If you have a better solution or facing difficulty while running the demo project, feel free to ping me on medium/Github.

--

--