Build simple authentication API using FAST API with ES256 encryption in 10 mins
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:
- Creating a FAST API project and install PyJWT package.
- Create and validate ES256 JWT token.
- Validation by accessing the protected endpoints with JWT token
- Build this project into a container image for microservice.
Technology used
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:
/login
endpoint to login user and generate token for the user./secret
endpoint, only user with validate token can access this endpoint.
Generate ES256 public and private key set in JSON Web Key(JWK) format
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.
# 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
SECRET_KEY
: any value for hashing the passwordJWT_ALGO
value from JWK key setalg
, which is the algorithm we used : ES256.ES256_KID
value from JWK key setkid
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 configclass 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):
passsettings = Settings()
This
__init__.py
in theconfig
folder required to make Python treat directories containing the file as packages. We can import the config byfrom 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 vdef 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
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 raw
→ JSON
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 8000CMD ["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.