
Implementing JWT Authentication in FastAPI
JWT (JSON Web Token) is a powerful and efficient method for securing APIs through token-based authentication, ensuring that only authenticated users can access your API endpoints. Unlike traditional session-based approaches, JWT is stateless, meaning it doesn't rely on server-side session storage, making it ideal for scalable and high-performance applications. In this guide, we'll walk you through implementing JWT authentication in a FastAPI application, covering everything from generating tokens upon user login to protecting your endpoints by validating these tokens, ultimately enhancing the security of your application's data and resources.
Prerequisites
- Python 3.10
Setup project
pip install fastapi uvicorn pyjwt python-dotenv
Project structure
├─ .env
└─ app
├─ main.py
├─ middleware
│ ├─ authenticate.py
│ └─ __init__.py
├─ static
│ ├─ index.html
│ └─ login.html
└─ __init__.py
Project files
.env
jwt_secret = b0WciedNJvFCqFRbB2A1QhZoCDnutAOen5g1FEDO0HsLTwGINp04GXh2OXVpTqQL
This .env
file contains a single environment variable jwt_secret
, which holds a secret key used for signing and verifying JWT tokens in the application.
authenticate.py
import jwt
import os
from fastapi import Request, Response
from typing import Callable
async def authenticate(request: Request, next: Callable):
if request.url.path in [ "/", "/login" ]:
return await next(request)
else:
header = request.headers.get("Authorization")
if not header or not header.startswith("Bearer "):
return Response(status_code=401)
token = header[7:]
try:
request.state.user = jwt.decode(token, os.getenv("jwt_secret"), algorithms=["HS256"])
except jwt.InvalidTokenError:
return Response(status_code=401)
return await next(request)
The authenticate.py
file defines an authentication middleware for a FastAPI application. It intercepts incoming requests and checks if the request is to a protected route (excluding the root /
and /login
). For these routes, it checks the Authorization
header for a valid JWT token. If the token is missing, invalid, or improperly formatted, it returns a 401 Unauthorized response. If the token is valid, it decodes it using a secret key from the environment and attaches the decoded user information to the request's state before passing the request to the next handler.
middleware/__init__.py
from app.middleware.authenticate import authenticate
The middleware/__init__.py
file exports the authenticate
function, making it available for use later in main.py
or other parts of the application.
main.py
import uvicorn
import jwt
import os
from dotenv import load_dotenv
from fastapi import FastAPI
from fastapi.requests import Request
from fastapi.responses import Response, FileResponse
from datetime import datetime, timedelta
from typing import Callable
from app.middleware import authenticate
load_dotenv()
app = FastAPI()
@app.get("/")
async def index():
return FileResponse("app/static/index.html")
@app.get("/login")
async def get_login():
return FileResponse("app/static/login.html")
@app.get("/user")
async def get_user(request: Request):
return { "name": request.state.user["name"] }
@app.post("/login")
async def login(request: Request):
login = await request.json()
if login["name"] == "admin" and login["password"] == "1234":
exp = datetime.utcnow() + timedelta(days=1)
token = jwt.encode({ "id": 1, "name": "admin", "exp": exp }, os.getenv("jwt_secret"))
return { "token": token }
return Response(status_code=400)
@app.middleware("http")
async def authenticate_middleware(request: Request, next: Callable):
return await authenticate(request, next)
if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1")
The main.py
file sets up a FastAPI application with basic routes and JWT authentication. It loads environment variables using dotenv
and initializes the FastAPI app. It defines routes for serving the root page (/
), login page (/login
), and a protected /user
route, which requires authentication. The login route (/login
) checks if the user credentials are correct and, if valid, generates a JWT token. The application also uses custom middleware (authenticate_middleware
) to validate JWT tokens on requests to protected routes.
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row">
<div class="col mt-5">
<div class="d-flex justify-content-center">
<div class="card p-0">
<div class="card-header">
<h3>Home</h3>
</div>
<div class="card-body text-center">
<i id="icon" class="fa fa-times-circle fa-5x mt-3 text-danger"></i>
<p id="message" class="mt-3">
You are currently not logged in.
<br/>
Redirecting to login page in seconds..
</p>
<div id="logout" class="col-12 d-none">
<button class="btn btn-primary w-100" onclick="logout()">Logout</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
function init() {
fetch('/user', {
headers: {
Authorization: 'Bearer ' + localStorage.getItem('token')
}
}).then(response => {
if (response.ok) {
response.json().then(data => {
document.getElementById('icon').className = 'fa fa-check-circle fa-5x mt-3 text-success'
document.getElementById('message').innerText = 'You are currently logged in as: ' + data.name
document.getElementById('logout').classList.remove('d-none')
})
}
else {
if (response.status === 401) {
setTimeout(() => {
location = '/login'
}, 4000)
}
else {
alert(`Error: ${response.status} ${response.statusText}`)
}
}
})
}
function logout() {
localStorage.removeItem('token')
location.reload()
}
init()
</script>
</body>
The index.html
is a simple web page that provides a user interface for displaying the login status of a user. It uses Bootstrap for styling and Font Awesome for icons. On page load, it checks the user's authentication status by sending a request to the server with a JWT token stored in localStorage
. If the user is logged in, it shows a success message with the user's name and a logout button. If not logged in, it shows a message indicating the user is not logged in and redirects them to the login page after a few seconds.
login.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row">
<div class="col mt-5">
<div class="d-flex justify-content-center">
<div class="card p-0" style="width: 300px;">
<div class="card-header">
<h3>Login</h3>
</div>
<div class="card-body">
<i class="fa fa-user-circle fa-5x d-block text-center text-secondary"></i>
<form onsubmit="return login()">
<div class="row">
<div class="mb-3 col-12">
<label class="form-label" for="user_account_name">User Name</label>
<input id="name" class="form-control form-control" value="admin" required/>
</div>
<div class="mb-3 col-12">
<label class="form-label" for="user_account_password">Password</label>
<input id="password" class="form-control form-control" type="password" value="1234" required/>
</div>
<div class="col-12">
<button class="btn btn-primary w-100">Login</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
function login() {
fetch('/login', {
method: 'POST',
body: JSON.stringify({
name: document.getElementById('name').value,
password: document.getElementById('password').value
}),
headers: {
'Content-Type': 'application/json'
}
}).then(response => {
if (response.ok) {
response.json().then(data => {
localStorage.setItem('token', data.token)
location = '/'
})
}
else {
alert(`Error: ${response.status} ${response.statusText}`)
}
})
return false
}
</script>
</body>
The login.html
page provides a simple login form where users can input their username and password. It uses Bootstrap for styling and Font Awesome for icons. When the user submits the form, a JavaScript function login()
sends a POST
request to the /login
endpoint with the entered credentials. If the login is successful, the server returns a JWT token, which is stored in localStorage
. The page then redirects the user to the home page (/
). If the login fails, an error message is displayed.
Run project
uvicorn app.main:app
Open the web browser and goto http://localhost:8000
You will find this test page.

Testing
After a few seconds, you will be redirected to the login page.

Press the login button, and you will be logged in to the home page, which will display the logged-in user's name.

Try refreshing the browser, and you will see that you're still logged in. Then, press the logout button, the JWT token will be removed, and you will be redirected to the login page again.

Conclusion
In conclusion, implementing JWT authentication in a FastAPI application provides a secure, stateless way to handle user authentication. By leveraging FastAPI's built-in support for middleware and dependency injection, along with libraries like pyjwt
, we can easily generate, verify, and handle JWT tokens. The middleware ensures that only authenticated users can access protected routes, improving the overall security of the API. This approach is scalable and ideal for modern applications where stateless communication is preferred, such as in web and mobile APIs. The integration of environment variables for managing secrets further enhances security and flexibility.
Source code: https://github.com/stackpuz/Example-JWT-FastAPI
Create a CRUD Web App in Minutes: https://stackpuz.com