FastAPI Login Authentication: Secure Your Apps

by Faj Lennon 47 views

Hey guys! Ever wondered how to secure your FastAPI applications like a pro? Well, you're in the right place! In this comprehensive guide, we're diving deep into the world of FastAPI login authentication. We'll cover everything from the basics to advanced techniques, ensuring your apps are protected and your users' data remains safe. So, buckle up and let's get started!

Why Authentication Matters in FastAPI

Authentication is the cornerstone of any secure web application. It's the process of verifying that users are who they claim to be. Without proper authentication, your FastAPI app is like an open house for hackers. They can access sensitive data, manipulate user accounts, and wreak havoc on your entire system. In the context of FastAPI, which is known for its speed and efficiency, implementing robust authentication is crucial to maintain performance while ensuring security. Neglecting authentication can lead to severe consequences, including data breaches, financial losses, and reputational damage. Therefore, understanding and implementing authentication correctly is not just a best practice; it's a necessity for any FastAPI project.

Moreover, authentication is not a one-size-fits-all solution. Different applications have different security requirements. A simple blog might only need basic username and password authentication, while a financial application requires multi-factor authentication (MFA) and biometric verification. With FastAPI, you have the flexibility to choose the authentication methods that best fit your needs. The framework supports various authentication schemes, including OAuth2, JWT (JSON Web Tokens), and API keys. By carefully selecting and configuring these schemes, you can create a tailored authentication system that provides the right level of security for your application. This adaptability is one of the key reasons why FastAPI is a popular choice among developers who prioritize security and performance.

Furthermore, remember that authentication is just one piece of the security puzzle. It works in conjunction with other security measures, such as authorization, encryption, and regular security audits. Authorization determines what authenticated users are allowed to do, while encryption protects data in transit and at rest. Regular security audits help identify and address vulnerabilities before they can be exploited. By integrating authentication with these other security measures, you can create a layered defense that significantly reduces the risk of security breaches. In short, investing in robust authentication is an investment in the long-term security and success of your FastAPI application.

Basic Authentication with FastAPI

Let's start with the basics. Basic authentication involves checking a username and password against a stored list of credentials. While it's not the most secure method, it's a good starting point for understanding authentication in FastAPI.

Setting Up Your FastAPI App

First, make sure you have FastAPI installed. If not, run:

pip install fastapi uvicorn

Now, let's create a simple FastAPI application:

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from typing import Annotated

app = FastAPI()

security = HTTPBasic()

users = {
 "admin": "password123"
}

def authenticate_user(credentials: HTTPBasicCredentials):
 user = users.get(credentials.username)
 if user and user == credentials.password:
 return credentials.username
 raise HTTPException(
 status_code=status.HTTP_401_UNAUTHORIZED,
 detail="Incorrect username or password",
 headers={"WWW-Authenticate": "Basic"},
 )


async def get_current_user(credentials: Annotated[HTTPBasicCredentials, Depends(security)]):
 return authenticate_user(credentials)


@app.get("/items/")
async def read_items(current_user: Annotated[str, Depends(get_current_user)]):
 return {"message": f"Hello {current_user}, you are authenticated!"}

In this example, we're using HTTPBasic from fastapi.security to handle the authentication. The authenticate_user function checks if the provided username and password match the stored credentials. If they do, it returns the username; otherwise, it raises an HTTPException.

Running the App

Save the code above as main.py and run the app using Uvicorn:

uvicorn main:app --reload

Now, when you go to http://127.0.0.1:8000/items/, you'll be prompted for a username and password. Enter admin as the username and password123 as the password. If you enter the correct credentials, you'll see the message "Hello admin, you are authenticated!"

Security Considerations

Remember, basic authentication sends credentials in plain text, so it's crucial to use it over HTTPS. In a production environment, you should never store passwords directly in your code. Instead, use a secure hashing algorithm like bcrypt to store password hashes.

JWT Authentication with FastAPI

JSON Web Tokens (JWT) are a popular and more secure way to handle authentication. JWTs are compact, self-contained, and can be used to securely transmit information between parties as a JSON object. Let's see how to implement JWT authentication in FastAPI.

Installing Dependencies

First, you'll need to install the python-jose and passlib libraries:

pip install python-jose passlib bcrypt

Implementing JWT Authentication

Here's an example of how to implement JWT authentication in FastAPI:

from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta
from typing import Annotated


app = FastAPI()

SECRET_KEY = "YOUR_SECRET_KEY"  # Change this in production!
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

crypt_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


def get_password_hash(password):
 return crypt_context.hash(password)


def verify_password(password, hashed_password):
 return crypt_context.verify(password, hashed_password)


# Dummy user database (replace with a real database in production)
users = {
 "admin": {
 "username": "admin",
 "hashed_password": get_password_hash("password123")
 }
}


oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


async def authenticate_user(form_data: OAuth2PasswordRequestForm = Depends()):
 user = users.get(form_data.username)
 if not user:
 raise HTTPException(
 status_code=status.HTTP_400_BAD_REQUEST,
 detail="Incorrect username or password"
 )
 if not verify_password(form_data.password, user["hashed_password"]):
 raise HTTPException(
 status_code=status.HTTP_400_BAD_REQUEST,
 detail="Incorrect username or password"
 )
 return user


def create_access_token(data: dict, expires_delta: timedelta | None = None):
 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})
 encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
 return encoded_jwt


@app.post("/token")
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
 user = await authenticate_user(form_data)
 if not user:
 raise HTTPException(
 status_code=status.HTTP_401_UNAUTHORIZED,
 detail="Incorrect username or password",
 headers={"WWW-Authenticate": "Bearer"},
 )
 access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
 access_token = create_access_token(data={"sub": user["username"]}, expires_delta=access_token_expires)
 return {"access_token": access_token, "token_type": "bearer"}


async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
 credentials_exception = HTTPException(
 status_code=status.HTTP_401_UNAUTHORIZED,
 detail="Could not validate credentials",
 headers={"WWW-Authenticate": "Bearer"},
 )
 try:
 payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
 username: str = payload.get("sub")
 if username is None:
 raise credentials_exception
 token_data = {"username": username}
 except JWTError:
 raise credentials_exception
 user = users.get(token_data["username"])
 if user is None:
 raise credentials_exception
 return user


@app.get("/items/")
async def read_items(current_user: Annotated[dict, Depends(get_current_user)]):
 return {"message": f"Hello {current_user['username']}, you are authenticated!"}

Key Components Explained

  • SECRET_KEY: A secret key used to sign and verify the JWT. Important: Keep this key secure and never expose it in your code.
  • ALGORITHM: The algorithm used to sign the JWT. In this case, we're using HS256.
  • OAuth2PasswordBearer: A helper class from fastapi.security that provides the OAuth2 password flow for obtaining a token.
  • create_access_token: This function creates a JWT token with the user's username as the subject and an expiration time.
  • get_current_user: This function verifies the JWT token, extracts the username, and retrieves the user from the database.

Running the App and Testing

Save the code as main.py and run the app using Uvicorn:

uvicorn main:app --reload

Now, you can use a tool like Swagger UI (which FastAPI provides automatically at http://127.0.0.1:8000/docs) to interact with the /token endpoint, obtain a JWT token, and then use that token to access the /items/ endpoint.

Securing Your JWT Implementation

  • Use a Strong SECRET_KEY: A weak secret key can be easily cracked, compromising your entire authentication system.
  • Implement Token Refresh: JWTs have a limited lifespan. Implement a token refresh mechanism to issue new tokens before the old ones expire.
  • Store Tokens Securely: Don't store JWTs in local storage. Use HTTP-only cookies or secure, client-side storage solutions.

OAuth2 Authentication with FastAPI

OAuth2 is a powerful authorization framework that enables third-party applications to access user data without exposing their credentials. Implementing OAuth2 in FastAPI can be a bit more complex, but it's worth it for the added security and flexibility.

Setting Up OAuth2

For OAuth2, you'll typically integrate with an OAuth2 provider like Google, Facebook, or Auth0. In this example, we'll use Auth0 as our OAuth2 provider.

Configuring Auth0

  1. Sign Up for Auth0: Create an account on Auth0.
  2. Create a New Application: In the Auth0 dashboard, create a new application of type "Regular Web Applications".
  3. Configure Settings: In the application settings, configure the following:
    • Allowed Callback URLs: Add the URL where Auth0 will redirect users after authentication (e.g., http://localhost:8000/callback).
    • Allowed Logout URLs: Add the URL where Auth0 will redirect users after logout.
    • Allowed Web Origins: Add the origin of your FastAPI application (e.g., http://localhost:8000).

FastAPI Implementation

Here's an example of how to implement OAuth2 authentication with Auth0 in FastAPI:

from fastapi import FastAPI, Depends, HTTPException, status, Request
from fastapi.responses import RedirectResponse
from authlib.integrations.starlette_client import OAuth
from starlette.config import Config
from typing import Annotated


app = FastAPI()

# Configuration
config = Config(".env")  # You can store these in a .env file
oauth = OAuth(config)

config.read_environment()

AUTH0_CLIENT_ID = config("AUTH0_CLIENT_ID")
AUTH0_CLIENT_SECRET = config("AUTH0_CLIENT_SECRET")
AUTH0_DOMAIN = config("AUTH0_DOMAIN")

oauth.register(
 name="auth0",
 client_id=AUTH0_CLIENT_ID,
 client_secret=AUTH0_CLIENT_SECRET,
 access_token_url=f"https://{AUTH0_DOMAIN}/oauth/token",
 authorize_url=f"https://{AUTH0_DOMAIN}/authorize",
 api_base_url=f"https://{AUTH0_DOMAIN}/userinfo",
 client_kwargs={
 "scope": "openid profile email"
 },
 jwks_uri=f"https://{AUTH0_DOMAIN}/.well-known/jwks.json",
)


@app.get("/login")
async def login(request: Request):
 redirect_uri = request.url_for("auth")
 return await oauth.auth0.authorize_redirect(request, redirect_uri)


@app.get("/auth")
async def auth(request: Request):
 token = await oauth.auth0.authorize_access_token(request)
 resp = await oauth.auth0.parse_id_token(request, token)
 request.session["user"] = resp
 return RedirectResponse(url="/items/")


@app.get("/items/")
async def read_items(request: Request):
 user = request.session.get("user")
 if not user:
 return RedirectResponse(url="/login")
 return {"message": f"Hello {user['name']}, you are authenticated!"}


@app.get("/logout")
async def logout(request: Request):
 request.session.pop("user", None)
 return RedirectResponse(url="/")

Explanation

  • Configuration: We're using the authlib library to handle the OAuth2 flow. You'll need to install it using pip install authlib starlette python-multipart. The configuration parameters like AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET, and AUTH0_DOMAIN are loaded from environment variables.
  • Login: The /login endpoint redirects the user to Auth0 for authentication.
  • Auth: The /auth endpoint handles the callback from Auth0, retrieves the user's information, and stores it in the session.
  • Items: The /items endpoint checks if the user is authenticated and displays a welcome message.
  • Logout: The /logout endpoint clears the user's session.

Running the App

  1. Create a .env File: Create a .env file in your project directory and add the following:
AUTH0_CLIENT_ID=YOUR_AUTH0_CLIENT_ID
AUTH0_CLIENT_SECRET=YOUR_AUTH0_CLIENT_SECRET
AUTH0_DOMAIN=YOUR_AUTH0_DOMAIN

Replace YOUR_AUTH0_CLIENT_ID, YOUR_AUTH0_CLIENT_SECRET, and YOUR_AUTH0_DOMAIN with the values from your Auth0 application.

  1. Run the App: Run the FastAPI app using Uvicorn:
uvicorn main:app --reload

Now, you can go to http://localhost:8000/login to start the OAuth2 flow. You'll be redirected to Auth0 for authentication, and after successful authentication, you'll be redirected back to your FastAPI app.

Best Practices for OAuth2

  • Use HTTPS: Always use HTTPS to protect the communication between your app and the OAuth2 provider.
  • Validate Tokens: Always validate the tokens you receive from the OAuth2 provider to ensure they are valid and have not been tampered with.
  • Implement Proper Error Handling: Handle errors gracefully and provide informative error messages to the user.

Conclusion

Alright, guys, we've covered a lot in this guide! From basic authentication to JWT and OAuth2, you now have a solid understanding of how to implement authentication in your FastAPI applications. Remember, security is an ongoing process, so always stay updated with the latest best practices and security measures. Keep building secure and awesome apps!