Without a working token url endpoint authentication is incomplete, let's build the other half of the equation - the login endpoint that actually creates those JWT tokens! This is where users trade their username and password for a new access token.
The User Lookup Function
Before we move on to create a login/tokenUrl endpoint, let's first create a utility function which will help us identify whether an user exist with the entered email of not.
def get_user_by_email(email: str, db: Session = Depends(get_db)) -> User:
statement = select(User).where(User.email == email)
result = db.exec(statement)
return result.first()
This function does exactly what it says - finds a user by their email address. Notice how we're using SQLModel's select()
syntax instead of raw SQL or even SQLAlchemy.
The -> User
return type annotation is super helpful here. Your IDE knows exactly what you're getting back, and if the user doesn't exist, result.first()
returns None
.
The Token Creation Endpoint
@api_router.post("/token")
def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
user = get_user_by_email(form_data.username, db)
if not user:
raise HTTPException(status_code=401, detail="Invalid credentials")
if not form_data.password == user.password:
raise HTTPException(status_code=401, detail="Invalid credentials")
access_token = create_access_token(user.email)
return {"access_token": access_token, "token_type": "bearer"}
OAuth2PasswordRequestForm Magic
form_data: OAuth2PasswordRequestForm = Depends()
OAuth2PasswordRequestForm
is FastAPI's built-in way to handle login forms. When someone hits this endpoint, they send:
username
(which we're treating as email)password
- Optional
scope
and other OAuth2 fields
FastAPI automatically parses the form data and gives you a clean object to work with. No manual form parsing needed! 🙌
Let us understand the Authentication flow
Step 1: Find the User
user = get_user_by_email(form_data.username, db)
if not user:
raise HTTPException(status_code=401, detail="Invalid credentials")
We look up the user by their email (which comes in as form_data.username
). If no user exists, return a 401. Notice we're saying "Invalid credentials" instead of "User not found" - this prevents attackers from figuring out which emails exist in your system.
Step 2: Verify Password
if not form_data.password == user.password:
raise HTTPException(status_code=401, detail="Invalid credentials")
Simple password check! In production, you'd want to hash your passwords and use something like bcrypt
for comparison, but this shows the core logic. We would try to implement hashing in the upcoming security section.
Step 3: Create the Token
access_token = create_access_token(user.email)
return {"access_token": access_token, "token_type": "bearer"}
If everything checks out, we create a JWT token with the user's email as the subject and return it in the exact format that OAuth2 expects.
Router Organization
Just building this function for login is not sufficient. We must register this router in our FastAPI app or the api_router. So, let's add the below information in apis/main.py
from apis.v1.user import router as user_router
from apis.v1.blog import router as blog_router
from apis.v1.auth import api_router as auth_router
api_router = APIRouter()
api_router.include_router(user_router, prefix="/api/v1", tags=["users"])
api_router.include_router(blog_router, prefix="/api/v1", tags=["blogs"])
api_router.include_router(auth_router, prefix="/api/v1", tags=["auth"])
Prefixes: All routes get /api/v1
prepended automatically. So your /token
endpoint becomes /api/v1/token
.
Tags: These show up in your API documentation, grouping related endpoints together. Users, blogs, and auth each get their own section.
Clean Imports: Notice how we import routers with descriptive aliases? router as user_router
makes it crystal clear what each one does.
Testing it out:
When you hit /docs
now, you'll see:
- A nice "Auth" section with your
/token
endpoint - Proper OAuth2 form with username/password fields
- The "Authorize" button that uses your token endpoint automatically