OAuth 2.0 for FastAPI
OAuth 2.0 is now an industry standard means of authentication. It is an authorization framework that allows one application to access resources from another application without knowing the user's password. Instead of the user providing the Google password to the app, the user is redirected to a Google login page, user enters the credentials which are then verified by Google. Post verification, Google returns an access token which the app can use for a limited amount of time.
Core actors in the workflow
There are 4 main actors here:
The Resource Owner (User)
The Client (or the Backend Application)
The Authorization Server (Google)
The Resource Server (Google APIs)
The working model
The entire flow running behind the scenes can be summarized in the following steps:
The user clicks "Continue with Google"
The user is redirected to a Google authentication page
After successful login, Google asks the user to grant permissions to share their information
Once the user allows those permissions, Google returns the backend with an authorization code. This code is temporary and usually valid for minutes.
The backend sends the authorization code to Google which is then verified. If verified, Google returns an "Access Token".
Implementing OAuth 2.0 in a FastAPI backend
First install the authlib library.
uv add authlib
Then, you need to get a Google Client ID and a Google Client Secret from the console under the authentication page. Create a new file, suppose oauth.py and create an OAuth instance.
from authlib.integrations.starlette_client import OAuth
from dotenv import load_dotenv
import os
load_dotenv()
oauth = OAuth()
oauth.register(
name="google",
client_id=os.getenv("GOOGLE_CLIENT_ID"),
client_secret=os.getenv("GOOGLE_CLIENT_SECRET"),
server_metadata_url="https://accounts.google.com/.well-known/openid-configuration",
client_kwargs={"scope": "openid email profile"},
)
This is basic boiler plate code and it'll mostly remain the same during your implementation stage. Then we define the OAuth routes, specifically the login and callback route. These two are enough for both login and register.
from fastapi import APIRouter, Depends, Request, HTTPException, UploadFile, File, Form
from fastapi.responses import RedirectResponse
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.oauth import oauth
from app.db import User, get_async_session
from app.users import get_jwt_strategy
router = APIRouter(
prefix="/auth/google",
tags=["Google OAuth"]
)
@router.get("/login")
async def google_login(request: Request):
redirect_uri = request.url_for("google_callback")
return await oauth.google.authorize_redirect(request, redirect_uri)
@router.get("/callback", name="google_callback")
async def google_callback(request: Request, session: AsyncSession = Depends(get_async_session)):
try:
token = await oauth.google.authorize_access_token(request)
user_info = token.get("userinfo")
if not user_info:
raise HTTPException(status_code=400, detail="Failed to retrieve user info from Google.")
email = user_info.get("email")
google_id = user_info.get("sub")
if not email:
raise HTTPException(status_code=400, detail="Email not found in Google user info.")
result = await session.execute(
select(User).where(User.email == email)
)
user = result.scalars().first()
if user is None:
user = User(
email = email,
hashed_password = "",
is_active = True,
is_verified = True,
is_superuser = False,
google_id = google_id
)
session.add(user)
await session.commit()
await session.refresh(user)
else:
if not getattr(user, "google_id", None):
user.google_id = google_id
await session.commit()
strategy = get_jwt_strategy()
access_token = await strategy.write_token(user)
return RedirectResponse(
url=f"http://localhost:8501/?token={access_token}"
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
Both the routes eventually follow the workflow we discussed initially in the blog post. Next step is to add the routes to the app.py file along with a Session Middleware.
app = FastAPI(lifespan=lifespan)
app.include_router(oauth_router)
app.add_middleware(
SessionMiddleware,
secret_key="some-random-long-secret-key"
)
Another quick step before everything can be up and running is to update the User schema. Along with the other User related stuff in the table or schema, we also want to have the google_id there. So add the below line:
google_id = Column(String, unique=True, nullable=True)
And that's it, the setup is complete. Now your app has OAuth 2.0 based Google authentication instead of fishy third party ones.