Fast & Safe

Umut Boz
KoçSistem
Published in
5 min readNov 8, 2022

We have been developing APIs for the needs in our own mobile framework solutions for a long time. After a running research show up there as a fact that it was the shortest and easiest way to write an API.

FastAPI framework, high performance, easy to learn, fast to code, ready for production. Of course, safety is also important.

Fast

In a previous study I wrote, I made an introduction to the Fast API section.

☝️ You can take a look at the speed section above.

Safe

There are many ways to manage security, authentication, and authorization.

I also developed for security and authentication for APIs in different frameworks and technologies. I can clearly say that FASTAPI is a much more practical and easy way to achieve these.

FastAPI provides several tools to help you deal with Security easily, rapidly, in a standard way, without having to study and learn all the security specifications.

Security Methods

  • OAuth2 : That’s what all the systems with “login with Facebook, Google, Twitter, GitHub” use underneath. It includes ways to authenticate using a “third party”.
  • OpenID Connect: OpenID Connect is another specification, based on OAuth2. OpenID Connect allows clients of all types, including Web-based, mobile, and JavaScript clients, to request and receive information about authenticated sessions and end-users.
  • OpenAPI : (previously known as Swagger) is the open specification for building APIs (now part of the Linux Foundation). FastAPI is based on OpenAPI.

OpenAPI Security Schemes

  • http can be used with Query Params, Cookie, Header
  • apiKey can be used with Bearer Token, Http Basic Authentication
  • oauth2 can be used with clientCredentials

Practice

We will apply authenticate with the backend, using a username and password.

We will use bearer token with OAuth2 and JWT. And we will use Bcrypt for encryption.

Bcrypt is a password-hashing Algorithm.

Development Environment

OS: MacOSX

RunTime: Python 3.9, Virtual Environment

Package Dependency : PyPi

Environment List :

Scenario

We have a two functions in API.

@app.get("/")
def root():
return {"message": "Welcome to Test App for Security"}


@app.get("/search")
def search(query:str):
if query == "apple":
return {"message": "found apple in search"}
else:
return {"message": query + " not found in search"}

The above two functions are currently accessible from outside.

if valid token exists, We will make some editing to make search function accessible. I am sharing the security Python file that I have prepared for this.

We will use some variables in the security file I shared in our working file.

References from security file to main.py:

from security import authenticate_user, auth_users_db, ACCESS_TOKEN_EXPIRE_MINUTES
from security import Token, User, create_access_token, get_current_active_user

All references found in main.py

from datetime import datetime, timedelta
from fastapi import FastAPI, Header, HTTPException, Depends, status
from fastapi.security import OAuth2PasswordRequestForm
from starlette.responses import Response
from security import authenticate_user, auth_users_db, ACCESS_TOKEN_EXPIRE_MINUTES
from security import Token, User, create_access_token, get_current_active_user

And our tester user who will receive the token

auth_users_db = {
"test": {
"username": "tester",
"full_name": "Tester",
"email": "tester@gmail.com",
"hashed_password": "$2y$10$RvXxgJ2EBCGnGCiDnWIpmePpBVEXwwzdeQNSQ497Y4.S5t3O0WrDO",
"disabled": False
}
}

You can generate your own hashed_password with bcrypt.

Or you can verify.

we will do this with code.

def verify_password(plain_password, hashed_password):
print("enter verify_password")
try:
pwd_verify_context = pwd_context.verify(plain_password, hashed_password)
print("pwd_verify_context: ", pwd_verify_context)
return pwd_context.verify(plain_password, hashed_password)
except Exception as e:
print("pwd_verify_context ", e)
return None

Okay. ⚠️ Now let’s open our search function to a user who only knows us.⚠️

@app.get("/search")
def search(query:str, current_user: User = Depends(get_current_active_user)):
if query == "apple":
return {"message": "found apple in search"}
else:
return {"message": query + " not found in search"}

Now we need an api endpoint function that generates tokens

@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
if not form_data:
print("?",form_data.username, form_data.password)
response = Response(headers={"WWW-Authenticate": "Basic"}, status_code=401)
return response
try:
print("!",form_data.username, form_data.password)
user = authenticate_user(auth_users_db, form_data.username, form_data.password)
if not user:
print("!",form_data.username, form_data.password)
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}

except:
print("! exception", form_data.username, form_data.password)
response = Response(headers={"WWW-Authenticate": "Basic"}, status_code=401)
return response
uvicorn main:app — reload

We launch the developed Api on localhost with the code above.

With http://127.0.0.1:8000/docs And We Authorize via Swagger.

And we can use this token for 30 minutes.

# from security.py
ACCESS_TOKEN_EXPIRE_MINUTES = 30

Test On Postman

{“access_token”: “our_token”,“token_type”: “bearer”}

Now, We have a key for accessing closed api endpoint.

{"message": "found apple in search"}

if we had used an expired or wrong token, our security would have kicked in.

Nice, everything is ok!

Finally

It’s really fast and safe too

I hope this post will help and becomes an example people who want to implement Authentication in FastAPI.

source → https://github.com/umutboz/fast-and-safe

Resources

--

--