FastAPI JWT: A Simple Example
FastAPI JWT: A Simple Example
What’s up, coding crew! Today, we’re diving deep into the awesome world of FastAPI and how to implement JWT (JSON Web Tokens) for secure API authentication. You know, those little tokens that let users prove who they are without having to log in every single time they hit an endpoint? Yeah, those! We’re going to break down a simple, yet effective, FastAPI JWT example so you can get this security feature up and running in your own projects. Whether you’re building a microservice, a web app backend, or just experimenting, understanding JWTs is a super valuable skill in the modern development landscape. So, grab your favorite beverage, settle in, and let’s get this authentication party started!
Table of Contents
Understanding JWTs: The Basics
Alright guys, before we jump into the
FastAPI JWT example
code, let’s quickly get on the same page about what JWTs actually are.
JWT
stands for
JSON Web Token
. At its core, it’s a compact, URL-safe way of representing claims to be transferred between two parties. Think of it as a digital passport for your users. It’s composed of three parts, separated by dots (
.
): a header, a payload, and a signature. The header typically contains information about the token itself, like the type of token (JWT) and the signing algorithm being used (e.g., HS256). The payload is where the real good stuff is – it contains the ‘claims’, which are statements about an entity (usually the user) and additional data. This can include things like user ID, roles, expiration time, and more. It’s important to remember that while the payload is
encoded
(usually in Base64Url), it’s
not encrypted
by default, so you shouldn’t put super sensitive information in there unless you plan to encrypt the whole token. Finally, the signature is used to verify that the sender of the JWT is who it says it is and to ensure that the message wasn’t changed along the way. It’s created by taking the encoded header, the encoded payload, a secret (which only the server knows), and the algorithm specified in the header, and then signing it. This whole process makes JWTs a really robust and secure way to handle authentication and authorization in web applications. They are stateless, meaning the server doesn’t need to store session information for each user, which is a huge win for scalability!
Setting Up Your FastAPI Project
Now, let’s get our
FastAPI
project set up for this
FastAPI JWT example
. First things first, you’ll need Python installed, obviously. Then, we’ll use
pip
to install the necessary libraries. The main players here are
fastapi
itself,
uvicorn
to run our server, and
python-jose
for handling the JWT operations. If you’re feeling fancy, you might also want
passlib
for password hashing, although for this basic example, we’ll keep it simple.
Open up your terminal and run these commands:
pip install fastapi uvicorn python-jose[cryptography]
We’re installing
fastapi
for our web framework,
uvicorn
as our ASGI server, and
python-jose[cryptography]
which is a fantastic library that implements JOSE (JSON Object Signing and Encryption) specifications, including JWT. The
[cryptography]
part ensures we have the necessary underlying cryptographic libraries.
Next, create a main Python file for your application, let’s call it
main.py
. Inside this file, we’ll start building our FastAPI app. We’ll need to import
FastAPI
and also set up some basic configuration. For JWT, you’ll need a secret key.
Never, ever hardcode your secret key directly in your code for production environments!
Use environment variables or a configuration management system. For this example, we’ll define it right there, but remember to change it for real-world applications. A good secret key is long, random, and unpredictable. Think of it as the master key to your authentication kingdom!
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel
from jose import JWTError, jwt
from datetime import datetime, timedelta
app = FastAPI()
# --- JWT Configuration ---
SECRET_KEY = "your-super-secret-key-change-me" # !! Change this for production !!
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# --- Pydantic Models ---
class TokenData(BaseModel):
username: str | None = None
class UserCreate(BaseModel):
username: str
password: str
class User(BaseModel):
username: str
class Token(BaseModel):
access_token: str
token_type: str
In this snippet, we’ve imported all the necessary modules. We’ve initialized our
FastAPI
app. We’ve defined our
SECRET_KEY
,
ALGORITHM
, and
ACCESS_TOKEN_EXPIRE_MINUTES
. These are crucial for generating and validating our JWTs.
SECRET_KEY
is the secret sauce that keeps your tokens secure; if someone gets hold of it, they can forge tokens.
ALGORITHM
specifies how the token is signed.
ACCESS_TOKEN_EXPIRE_MINUTES
is a good practice to limit the lifespan of a token, enhancing security. We’ve also defined some Pydantic models:
TokenData
to hold the decoded token information,
UserCreate
for user registration (though we won’t implement full registration logic here),
User
for representing a user, and
Token
to structure our response when a token is issued. This setup is the foundation for our JWT authentication.
Creating the Token Generation Endpoint
Now for the exciting part: creating an endpoint where users can actually get a JWT! This is typically done through a login process. For our
FastAPI JWT example
, we’ll create a
/token
endpoint. This endpoint will accept a username and password, validate them (in a real app, you’d check against a database), and if valid, return a JWT. Let’s add this to our
main.py
file.
First, we need a way to simulate user authentication. In a real application, you’d query a database here. For this example, we’ll use a dummy user and a dummy password.
# Dummy user data (replace with database lookup in a real app)
FAKE_USERS = {
"john_doe": {"username": "john_doe", "hashed_password": "fake_hashed_password"}
}
def authenticate_user(username: str, password: str) -> User | None:
user_data = FAKE_USERS.get(username)
if user_data and password == "password123": # Insecure! Replace with actual password verification
return User(username=username)
return None
def create_access_token(data: dict, expires_delta: timedelta | None = None) -> str:
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire, "sub": data.get("username")}) # 'sub' is standard for subject
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()): # Using OAuth2PasswordRequestForm for convenience
user = authenticate_user(form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=401,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"username": user.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
Let’s break this down, guys. We first defined
authenticate_user
and
create_access_token
. The
authenticate_user
function is a placeholder; in a real app, this is where you’d check the provided username and password against your user database. We’re using a simple dictionary lookup and a hardcoded password for demonstration, which is
highly insecure
and should be replaced with proper password hashing and verification. The
create_access_token
function is the core of our token generation. It takes a dictionary of data (which must include the username for our
TokenData
model later) and an optional expiry time. It adds an
exp
(expiration) claim and a
sub
(subject) claim, which standardizes the token’s content, and then uses
jwt.encode
from
python-jose
to create the signed JWT string using our
SECRET_KEY
and
ALGORITHM
.
The
@app.post("/token", response_model=Token)
endpoint is where the magic happens. We’re using
OAuth2PasswordRequestForm
from
fastapi.security
to easily handle
username
and
password
from form data. When a POST request comes to
/token
, it calls
authenticate_user
. If the authentication fails, it throws a
401 Unauthorized
error. If successful, it calls
create_access_token
with the user’s username and an expiration time, then returns a JSON response containing the
access_token
and its
token_type
(which is typically ‘bearer’ for JWT).
Protecting Your API Endpoints
Okay, so we’ve got tokens being generated. Awesome! But what’s the point if anyone can access our protected resources? We need to make sure only users with a valid JWT can access certain endpoints. This is where dependency injection in FastAPI really shines. We’ll create a dependency that checks for a valid
Authorization
header containing our Bearer token.
Add this function to your
main.py
:
from fastapi.security import OAuth2PasswordBearer
# Re-using the models defined earlier
# SECRET_KEY, ALGORITHM, TokenData, etc.
# Using OAuth2PasswordBearer to handle the Authorization header
# 'token' is the path where the token is obtained (our /token endpoint)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def get_current_user(token: str = Depends(oauth2_scheme)) -> User:
credentials_exception = HTTPException(
status_code=401,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub") # 'sub' is the subject claim
if username is None:
raise credentials_exception
token_data = TokenData(username=username)
except JWTError:
raise credentials_exception
user = FAKE_USERS.get(username) # In a real app, fetch user from DB using token_data.username
if user is None:
raise credentials_exception
return User(username=token_data.username) # Return a User object
@app.get("/users/me", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_user)):
return current_user
Let’s walk through this cool piece of code, folks. We import
OAuth2PasswordBearer
from
fastapi.security
. This is a handy helper class that handles extracting the token from the
Authorization: Bearer <token>
header. We initialize it with
tokenUrl='token'
, telling FastAPI where to find the endpoint that issues tokens. Then, we define
get_current_user
. This function uses
Depends(oauth2_scheme)
to get the token string. Inside the
try
block, we use
jwt.decode
to verify the token’s signature using our
SECRET_KEY
and
ALGORITHM
. If the signature is invalid or the token has expired (which
jwt.decode
handles automatically), a
JWTError
is raised. We then extract the
username
from the payload’s
sub
claim. If the username is missing, or if the user associated with the token doesn’t exist in our system (again, this is where a database lookup would happen), we raise a
401 Unauthorized
exception. If everything checks out, we return a
User
object, representing the authenticated user.
Finally, we have our protected endpoint,
/users/me
. Notice how
current_user: User = Depends(get_current_user)
is used. This means that
before
the
read_users_me
function can even run, FastAPI will execute
get_current_user
. If
get_current_user
returns a
User
object, it’s passed as the
current_user
argument to
read_users_me
. If
get_current_user
raises an
HTTPException
, the request is stopped right there, and the client receives the error. This is the power of FastAPI’s dependency injection system for securing your API!
Putting It All Together: Testing Your API
Alright, we’ve built the core pieces for our
FastAPI JWT example
: token generation and endpoint protection. Now, let’s see how to test it out. You’ll need
curl
or a tool like Postman or Insomnia.
-
Start the server: Make sure you’ve saved your
main.pyfile. In your terminal, navigate to the directory where you saved it and run:”`bash uvicorn main:app –reload
”`
The `--reload` flag is super handy during development as it restarts the server automatically when you save changes.
-
Get an Access Token: First, you need to obtain a JWT. Send a POST request to
http://127.0.0.1:8000/tokenwithusernameandpasswordin the request body. Useform-datafor the request body type.-
URL:
http://127.0.0.1:8000/token -
Method:
POST -
Body (form-data):
-
username:john_doe -
password:password123
-
You should receive a JSON response like this:
{ "access_token": "your_generated_jwt_string_here", "token_type": "bearer" }Copy the
access_tokenvalue. This is your key! -
URL:
-
Access a Protected Endpoint: Now, try accessing the protected
/users/meendpoint. You need to include theAuthorizationheader with your copied access token.-
URL:
http://127.0.0.1:8000/users/me -
Method:
GET -
Headers:
-
Authorization:Bearer your_generated_jwt_string_here(Replaceyour_generated_jwt_string_herewith the actual token you got)
-
If the token is valid and not expired, you should see:
{ "username": "john_doe" }If you try to access
/users/mewithout theAuthorizationheader, or with an invalid/expired token, you’ll get the401 Unauthorizederror we set up earlier. Pretty neat, right? -
URL:
Enhancements and Best Practices
This
FastAPI JWT example
is a great starting point, but there are always ways to level up your security game, guys. Firstly,
never use hardcoded secret keys
. Store them securely using environment variables (
os.environ.get('SECRET_KEY')
) or a secrets management service. Use a strong, long, and random secret key. Secondly,
implement proper password hashing
. Use libraries like
passlib
with algorithms like
bcrypt
or
argon2
to hash passwords before storing them. Your
authenticate_user
function should then verify the provided password against the stored hash.
Consider token refresh mechanisms . JWTs are often short-lived for security. You can implement a refresh token system where a long-lived refresh token is used to obtain new short-lived access tokens without the user having to re-enter their credentials. Also, think about token revocation . If a user’s account is compromised or they log out, you might want to invalidate their token immediately. Since JWTs are stateless, this can be tricky. Common solutions involve maintaining a blacklist of revoked tokens (e.g., in Redis or a database), though this adds state back into the system.
Error handling is crucial. Ensure your
HTTPException
details are informative but don’t leak too much sensitive information. For production, you’ll want robust logging. Finally,
validate all incoming data
meticulously. Pydantic models help a lot, but always be mindful of potential injection attacks or malformed requests. By implementing these enhancements, you can build a truly robust and secure authentication system using FastAPI and JWTs. Keep coding, keep learning!