This is a Python microservice created using FastAPI that provides user authentication against an LDAP server and returns a JSON Web Token (JWT).
This microservice requires an LDAP server to run against.
- Docker and Docker Compose installed (if you want to run the microservice inside Docker)
- Python 3.12 installed on your machine (if you are not using Docker)
- LDAP server to connect to
- CA certificate PEM file containing all the trusted CA certificates (if LDAP certificate validation is enabled which is strongly recommended to be in production)
- Private and public key pair (must be OpenSSH encoded) for encrypting and decrypting the JWTs
- A list of active usernames, defining who can use this service
- This repository cloned
Ensure that Docker is installed and running on your machine before proceeding.
-
Create a
.env
file alongside the.env.example
file. Use the example file as a reference and modify the values accordingly.cp ldap_jwt_auth/.env.example ldap_jwt_auth/.env
-
Create a
logging.ini
file alongside thelogging.example.ini
file. Use the example file as a reference and modify it accordingly:cp ldap_jwt_auth/logging.example.ini ldap_jwt_auth/logging.ini
-
Navigate to the
keys
directory in the root of the project directory, and generate OpenSSH encoded private and public key pair:ssh-keygen -b 2048 -t rsa -f keys/jwt-key -q -N "" -C ""
-
(If LDAP certificate validation is enabled) Copy the
cacert.pem
file that contains all the trusted CA certificates to theldap_server_certs
directory in the root of the project. -
Create a
active_usernames.txt
file alongside theactive_usernames.example.txt
file and add all the usernames that can use this system. The usernames are the Federal IDs and each one should be stored on a separate line.cp active_usernames.example.txt active_usernames.txt
The easiest way to run the application with Docker for local development is using the docker-compose.yml
file. It is
configured to start the application in a reload mode using the Dockerfile
.
- Build and start the Docker container:
The microservice should now be running inside Docker at http://localhost:8000 and its Swagger UI could be accessed at http://localhost:8000/docs.
docker-compose up
Use the Dockerfile
to run just the application itself in a container. Use this only for local development (not
production)!
-
Build an image using the
Dockerfile
from the root of the project directory:docker build -f Dockerfile -t ldap_jwt_auth_api_image .
-
Start the container using the image built and map it to port
8000
locally:docker run -p 8000:8000 --name ldap_jwt_auth_api_container -v ./keys/jwt-key:/ldap-jwt-auth-run/keys/jwt-key -v ./keys/jwt-key.pub:/ldap-jwt-auth-run/keys/jwt-key.pub -v ./ldap_server_certs/cacert.pem:/ldap-jwt-auth-run/ldap_server_certs/cacert.pem -v ./active_usernames.txt:/ldap-jwt-auth-run/active_usernames.txt ldap_jwt_auth_api_image
or with values for the environment variables:
docker run -p 8000:8000 --name ldap_jwt_auth_api_container --env AUTHENTICATION__ACCESS_TOKEN_VALIDITY_MINUTES=10 -v ./keys/jwt-key:/ldap-jwt-auth-run/keys/jwt-key -v ./keys/jwt-key.pub:/ldap-jwt-auth-run/keys/jwt-key.pub -v ./ldap_server_certs/cacert.pem:/ldap-jwt-auth-run/ldap_server_certs/cacert.pem -v ./active_usernames.txt:/ldap-jwt-auth-run/active_usernames.txt ldap_jwt_auth_api_image
The microservice should now be running inside Docker at http://localhost:8000 and its Swagger UI could be accessed at http://localhost:8000/docs.
Use the Dockerfile.prod
to run just the application itself in a container. This can be used for production.
-
Private keys are only readable by the owner. Given that the private key is generated on the host machine and the container runs with a different user, it means that the key is not readable by the user in the container because the ownership belongs to the user on the host. This can be solved by transferring the ownership to the user in the container and setting the permissions.
sudo chown 500:500 keys/jwt-key sudo chmod 0400 keys/jwt-key
-
Build an image using the
Dockerfile.prod
from the root of the project directory:docker build -f Dockerfile.prod -t ldap_jwt_auth_api_image .
-
Start the container using the image built and map it to port
8000
locally:docker run -p 8000:8000 --name ldap_jwt_auth_api_container -v ./keys/jwt-key:/ldap-jwt-auth-run/keys/jwt-key -v ./keys/jwt-key.pub:/ldap-jwt-auth-run/keys/jwt-key.pub -v ./ldap_server_certs/cacert.pem:/ldap-jwt-auth-run/ldap_server_certs/cacert.pem -v ./active_usernames.txt:/ldap-jwt-auth-run/active_usernames.txt ldap_jwt_auth_api_image
or with values for the environment variables:
docker run -p 8000:8000 --name ldap_jwt_auth_api_container --env AUTHENTICATION__ACCESS_TOKEN_VALIDITY_MINUTES=10 -v ./keys/jwt-key:/ldap-jwt-auth-run/keys/jwt-key -v ./keys/jwt-key.pub:/ldap-jwt-auth-run/keys/jwt-key.pub -v ./ldap_server_certs/cacert.pem:/ldap-jwt-auth-run/ldap_server_certs/cacert.pem -v ./active_usernames.txt:/ldap-jwt-auth-run/active_usernames.txt ldap_jwt_auth_api_image
The microservice should now be running inside Docker at http://localhost:8000 and its Swagger UI could be accessed at http://localhost:8000/docs.
Ensure that Python is installed on your machine before proceeding.
-
Create a Python virtual environment and activate it in the root of the project directory:
python -m venv venv source venv/bin/activate
-
Install the software packages required to build
python-ldap
on your local system, more info here. -
Install the required dependencies using pip:
pip install .[dev]
-
Create a
.env
file alongside the.env.example
file. Use the example file as a reference and modify the values accordingly.cp ldap_jwt_auth/.env.example ldap_jwt_auth/.env
-
Create a
logging.ini
file alongside thelogging.example.ini
file. Use the example file as a reference and modify it accordingly:cp ldap_jwt_auth/logging.example.ini ldap_jwt_auth/logging.ini
-
Navigate to the
keys
directory in the root of the project directory, and generate OpenSSH encoded private and public key pair:ssh-keygen -b 2048 -t rsa -f keys/jwt-key -q -N "" -C ""
-
(If LDAP certificate validation is enabled) Copy the
cacert.pem
file that contains all the trusted CA certificates to theldap_server_certs
directory in the root of the project. -
Create a
active_usernames.txt
file alongside theactive_usernames.example.txt
file and add all the usernames that can use this system. The usernames are the Federal IDs and each one should be stored on a separate line.cp active_usernames.example.txt active_usernames.txt
-
Start the microservice using FastAPI's CLI:
fastapi dev ldap_jwt_auth/main.py --log-config ldap_jwt_auth/logging.ini
The microservice should now be running locally at http://localhost:8000. The Swagger UI could be accessed at http://localhost:8000/docs.
-
To run the unit tests, run:
pytest -c test/pytest.ini test/unit/
The configuration for the application is handled
using Pydantic Settings. It allows for loading config
values from environment variables or the .env
file. Please note that even when using the .env
file, Pydantic will
still read environment variables as well as the .env
file, environment variables will always take priority over
values loaded from the .env
file.
Listed below are the environment variables supported by the application.
Environment Variable | Description | Mandatory | Default Value |
---|---|---|---|
API__TITLE |
The title of the API which is added to the generated OpenAPI. | No | LDAP-JWT Authentication Service API |
API__DESCRIPTION |
The description of the API which is added to the generated OpenAPI. | No | This is the API for the LDAP-JWT Authentication Service |
API__ROOT_PATH |
(If using a proxy) The path prefix handled by a proxy that is not seen by the app. | No | |
API__ALLOWED_CORS_HEADERS |
The list of headers that are allowed to be included in cross-origin requests. | Yes | |
API__ALLOWED_CORS_ORIGINS |
The list of origins (domains) that are allowed to make cross-origin requests. | Yes | |
API__ALLOWED_CORS_METHODS |
The list of methods that are allowed to be used to make cross-origin requests. | Yes | |
AUTHENTICATION__PRIVATE_KEY_PATH |
The path to the private key to be used for encoding JWT access and refresh tokens. | Yes | |
AUTHENTICATION__PUBLIC_KEY_PATH |
The path to the public key to be used for decoding JWT access and refresh tokens signed by the corresponding private key. | Yes | |
AUTHENTICATION__JWT_ALGORITHM |
The algorithm to use to decode the JWT access and refresh tokens. | Yes | |
AUTHENTICATION__ACCESS_TOKEN_VALIDITY_MINUTES |
Minutes after which the JWT access token expires. | Yes | |
AUTHENTICATION__REFRESH_TOKEN_VALIDITY_DAYS |
Days after which the JWT refresh token expires. | Yes | |
AUTHENTICATION__ACTIVE_USERNAMES_PATH |
The path to the txt file containing the active usernames and defining who can use this service. |
Yes | |
MAINTENANCE__MAINTENANCE_PATH |
The path to the json file containing the maintenance state. |
Yes | |
MAINTENANCE__SCHEDULED_MAINTENANCE_PATH |
The path to the json file containing the scheduled maintenance state. |
Yes | |
LDAP_SERVER__URL |
The URL to the LDAP server to connect to. | Yes | |
LDAP_SERVER__REALM |
The realm for the LDAP server. | Yes |
The active_usernames.txt
file at the root of the project directory contains the Federal IDs of the users with access
to the system. This means that you can add or remove a user from the system by adding or removing their Federal ID in
the active_usernames.txt
file.
PLEASE NOTE Changes made to the active_usernames.txt
file using vim do not get synced in the Docker container
because it changes the inode index number of the file. A workaround is to create a new file using
the active_usernames.txt
file, apply your changes in the new file, and then overwrite the active_usernames.txt
file
with the content of the new file, see below.
cp active_usernames.txt new_active_usernames.txt
vim new_active_usernames.txt
cat new_active_usernames.txt > active_usernames.txt
rm new_active_usernames.txt
The maintenance
folder at the root of the project directory contains two json files which return the appropriate state
of the system. This means that you can edit the values in the files in accordance with the desired state of the system.
PLEASE NOTE Changes made to maintenance.json
and scheduled_maintenance.json
file using vim do not get synced
in the Docker container because it changes the inode index number of the file. A workaround is to create a new file
using the maintenance.json
or scheduled_maintenance.json
file, apply your changes in the new file, and then
overwrite the maintenance.json
/ scheduled_maintenance.json
file with the content of the new file, see below an
example for maintenance.json
file.
cp maintenance/maintenance.json new_maintenance.json
vim new_maintenance.json
cat new_maintenance.json > maintenance/maintenance.json
rm new_maintenance.json