Current Status
This is a rough article. Once I have free time, I will rewrite the entire article to use the proper technologies I used.
Preamble
I'm currently working on a project. Ideally, the model of user authentication should be the traditional username and password. However, I'm a lazy dev, and I do not want to have to deal with the complexity of that model.
I have used applications that employ the Single Sign On model, and I find it fascinating. Interestingly, I don't really have an idea of how it worked then. I thought of a simple way, and I think it's sufficient.
That said, I'll be writing a concise article on how I built an SSO model for a FastAPI Application in the future.
Working Principle
For this model, a token will be generated by a function, tied to the requested user and then registered in the database.
The purpose of saving this in the database is to act as a point of validation when the sign-in link is used. That is, whenever a sign-in link is used, the application checks if it is saved in the database before proceeding to give a response.
I have tried to demonstrate this in the image below:
I must apologize ahead: In this article, I used an in-memory database. However, the concept is the same as when applied to a real database.
Prerequisite
- FastAPI
Scaffold your application
The first step is to create an empty directory and scaffold an application:
$ mkdir sso-app && cd sso-app
$ python3 -m venv
Activate the virtual environment and install the application dependencies:
$ . venv/bin/activate
(venv)$ pip install fastapi uvicorn
(venv)$ pip freeze > requirements.txt
Create the routing file, and utilities file:
(venv)$ touch {routes,utilities}.py
Write the routes
I'll be having a route to:
- Add a new user
- Request for a sign-in link
- Sign a user in
In routes.py
:
from fastapi import FastAPI
from utilities import generate_token
users = {}
login_tokens = []
router = FastAPI(
title="SSO Application",
description="Simple application demo on how SSO works"
)
@router.post("/new/")
async def new_user(user: dict):
user_id = len(users) + 1
users[user_id] = user
return {
"message": "User data successfully added"
}
@router.post("/sso")
async def get_sso_link(user_id: int):
if users[user_id]:
token = await generate_token(user_id)
login_tokens.append(token)
return token
return {
"error": "User with supplied ID doesn't exist"
}
@router.get("/sso/")
async def sign_in(token: str):
if token in login_tokens:
return {
"message": "User logged in successfully."
}
return {
"error": "Invalid token passed"
}
Write the utility function
The utility function is named generate_token
. It is responsible for generating identifiers.
In utilities.py
:
import random
import string
async def generate_token(id: str):
length_of_token = 12
identifier = ""
for i in range(length_of_token):
identifier += random.choice(string.ascii_letters)
return identifier
Start your application
(venv)$ uvicorn routes:router --host 0.0.0.0 --port 8080
Testing
- Add a new user:
$ curl -X 'POST' \
'http://0.0.0.0:8080/new/' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"username": "Abdulazeez"
}'
- Get an SSO token
curl -X 'POST' \
'http://0.0.0.0:8080/sso?user_id=1' \
-H 'accept: application/json' \
-d ''
The token returned is: KDZfHjQAdiFl
- Sign In
curl -X 'GET' \
'http://0.0.0.0:8080/sso/?token=KDZfHjQAdiFl' \
-H 'accept: application/json'
Response:
{
"message": "User logged in successfully."
}
- Using a wrong token:
curl -X 'GET' \
'http://0.0.0.0:8080/sso/?token=fKDZfHjQAdiFl' \
-H 'accept: application/json'
Response:
{
"error": "Invalid token passed"
}
Conclusion
A proper conclusion will be written once I have completed this draft. Until then, I'll say check back frequently.
References
- My brain
- Shortener inspiration: https://dev.to/lordghostx/build-and-deploy-a-serverless-url-shortener-with-python-and-fauna-3077