Adding OAuth2 Authencation In Fast-Api
Basic Fast-Api App
Hi, In this artical i'll show how i implemented OAuth2
with Password and Bearer ( here is docs for this )
well Fast-Api provide many type of authencation see list but for now let's just focus on OAuth2
here is the basic startup template for fast api(or if you have got your own thats fine too)
api/
| __init__.py
| routes.py
| models.py
| helper.py
| auth.py
run.py
Note : For database i'll be using tortoise-orm
. use pip install tortoise-orm
to install
# api/__init__.py
from fastapi import FastAPI
from tortoise.contrib.fastapi import register_tortoise
app = FastAPI(
title='Fast-api User Auth',
description= 'Learing how to `OAuth2` in FastAPI',
version = '1.1.1'
)
register_tortoise(
app,
db_url='sqlite://../database.db',
modules={"models": ["api.models"]},
generate_schemas=True,
add_exception_handlers=True,
)
from api import routes
here is run.py
you probabily won't need this but i just have it
# run.py
import unicorn
uvicorn.run('api:app', reload=True, host='127.0.0.1', port=8000)
now you can either use run.py
to run api or run it from command line
python3 run.py
#------or-------
uvicorn api:app --reload
Note : Remember to run uvicorn from one level up from api folder
When you run it go to localhost:8000/docs
you'll see somrthing like this
Lets create model/database table for user in models.py
# api/models.py
from tortoise.models import Model
from tortoise.fields import IntField, CharField
from tortoise.contrib.pydantic import pydantic_model_creator
from api.hepler import classproperty
class User(Model):
id = IntField(pk=True)
username = CharField(max_length=10)
password = CharField(max_length=20)
@classproperty
def pydantic(cls):
return pydantic_model_creator(cls,
name='User',
exclude=['password']
)
@classproperty
def login_pydantic(cls):
return pydantic_model_creator(cls,
name='Login User'
)
Note : Check out helper.py
in git
Here each user will have a username
and a password
and off course an id
Now lets create a route for user to register
# api/routes.py
from api import app
from api.models import User
@app.post('/register', response_model = User.pydantic, tags=['User'])
async def register(user : User.login_pydantic):
user_obj = await User.create(**user.dict(exclude_unset = True))
return await User.pydantic.from_tortoise_orm(user_obj)
you should see something like Now go ahead and register a user
Adding Authentication
Lets get started with authenticating user and getting access_token
in auth.py
from fastapi.security import OAuth2PasswordBearer
login_schema = OAuth2PasswordBearer(tokenUrl="shild")
token_url
contain route which will retuen access_token
i called it shild
(since i am a marvel fan, You can call it jim_gordon. get the reference😏 )
from fastapi.security import OAuth2PasswordRequestForm
from fastapi import Depends, HTTPException, status
async def authnicate_user(username : str, password : str):
user = await User.get_or_none(username = username)
if not user :
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail = "User Not Found")
if not user.password == password:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail = "User Not Found")
return user
@app.post('/shild', tags=['Authoncation'])
async def get_shild(form_data : OAuth2PasswordRequestForm = Depends()):
user = await authnicate_user(form_data.username, form_data.password)
if user:
user_obj = await User.login_pydantic.from_tortoise_orm(user)
token = user_obj.json()
return {'access_token': token, 'token_type' : 'bearer'}
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail = "User Not Found")
Note : _authnicate_user
is a functation that takes username and password as parameter and return user if exist_
Note : I haven't used any type of password hashing please don't be like me and some sort of hashing to encrypt password
get_shild
function will take the username and password from OAuth2PasswordRequestForm
> check if provided data is correct and return access_token
which here is user object schema in json. If you want and i encourage you to use JWT
or some other format which is more secure
By the way this is how OAuth2PasswordRequestForm
looks like
That's all now you have OAuth2 Authencation with access_token
Don't believe? me go ahead and try /shild
route and see result
Now lets create a route which requires the authencation i.e it will require access_token
that we just created in get_shild
function or in /shild
route
# api/routes.py
from api.auth import get_current_user
@app.get('/user/me', response_model = User.pydantic, tags=['User'])
async def current_user(user : User.login_pydantic = Depends(get_current_user)):
return user
This current_user
function depends upon get_current_user
in auth.py
and it returns login_pydantic
schema of User
# api/auth.py
async def get_current_user(token: str = Depends(login_schema)):
if not token:
raise HTTPException(
status_code=status.HTTP_406_NOT_ACCEPTABLE,
detail = "Invalid Token")
payload = json.loads(token)
user = await authnicate_user(username = payload.get('name'), password = payload.get('password'))
user_obj = await User.pydantic.from_tortoise_orm(user)
return user_obj
Since get_current_user
depends upon the login_schema
it will requier access_token to run this function and, as i said current_user
depends upon get_current_user
when then depends upon login_schema
thus current_user
also depends upon login_schema
its like if a = 5
and b = a
then b = 5
Now when you look at localhost:8000/docs
you will see a lock right side of current_user
route. That means this route is protected and require authentication
the advantage of using get_current_user
is which ever route you want to protect with authebtication you can add user : User.login_pydantic = Depends(get_current_user)
to that function
There you have it now you have successfully have genrated access_token and created a protected route 🥳🥳
Problem I Faced
Obesely i didn't do it in first try. I too face some problem, here are some of them
- return str in
token_url
:- Inauth.py
underget_shild
function i tried to return justaccess_token
just astr
. It turns out it will throw aninter server error
whenever you useget_current_user
(It took 2hrs to debug this🥵😥) - Converting
user_obj
todict
:- Inauth.py
underget_shild
function i tried to assigntoken
touser_obj.dict()
but it give me error saying thatdict
requies an argument but i didn't pass any so i just replaceddict
tojson
and it works. So fine by me
luckly those were the major problems i faced, Other were just some typos
Thats all for this artical. Thanks for reading
Note: or you could use fastapi-login