Introduction
User authentication and management are critical aspects of modern web applications. AWS Cognito provides a reliable, scalable solution for handling user sign-ups, sign-ins, and access control. By integrating AWS Cognito with FastAPI and applying Clean Architecture principles, we can create a robust and maintainable authentication system.
This guide will cover setting up AWS Cognito, integrating it with FastAPI, and organizing the codebase using Clean Architecture. This structure will keep your code modular, easy to test, and scalable.
1. Why Use AWS Cognito?
AWS Cognito simplifies the complexity of managing users, including storing passwords, enforcing security policies, enabling multi-factor authentication, and supporting social logins. Key advantages of AWS Cognito include:
– Scalability: Supports millions of users.
– Built-in Security: Uses JSON Web Tokens (JWT) for secure, managed authentication.
– Integration with AWS: Works seamlessly with other AWS services like Lambda, API Gateway, and more.
Combined with FastAPI, AWS Cognito can provide a solid foundation for user management.
2. Prerequisites
Ensure you have the following:
– AWS Account: Sign up at AWS.
– Python 3.7+: Make sure it’s installed.
– FastAPI and Dependencies: Install FastAPI, Uvicorn, and Boto3 (for AWS interaction):
pip install fastapi uvicorn boto3 python-jose
- Basic Knowledge: Familiarity with FastAPI and REST APIs.
3. Setting Up AWS Cognito
Step 1: Create a Cognito User Pool
1. Log in to AWS and go to Cognito. Select Manage User Pools and click Create a user pool. Name it (e.g., FastAPIUserPool
).
2. Configure Sign-In: Enable sign-in with username or email.
3. Define Security Settings: Set a password policy and enable multi-factor authentication if needed.
4. App Clients: Under App Clients, create a client application.
— Note: Uncheck Generate client secret to avoid exposing sensitive information.
5. Save Details: Note the User Pool ID and App Client ID.
Step 2: Set Up a Domain for Cognito
In App Integration:
1. Define a domain prefix (e.g., myapp-userpool
). AWS appends the region and amazoncognito.com
.
2. Click Save Changes.
Step 3: Enable OAuth 2.0 Flows
In App Client Settings:
1. Enable Authorization Code Grant and Implicit Grant.
2. Set http://localhost:8000/callback
as the callback URL for local testing.
4. Understanding Clean Architecture
Clean Architecture organizes code into layers with specific responsibilities, reducing dependencies between components. Here’s how we’ll apply it to our FastAPI + Cognito setup:
1. API Layer: Exposes endpoints, handling HTTP requests and responses.
2. Core Layer: Stores app configurations and settings.
3. Domain Layer: Defines business entities (like User
) and data validation schemas.
4. Service Layer: Contains business logic, like registering and logging in users.
5. Repository Layer: Handles interactions with external dependencies, such as AWS Cognito.
This structure ensures that each layer operates independently, making it easier to test, maintain, and scale.
5. Implementing Clean Architecture with FastAPI
Project Structure
We’ll start by setting up the following directory structure:
project/
│
├── app/
│ ├── main.py # Entry point for FastAPI
│ ├── api/ # API layer
│ │ ├── __init__.py
│ │ └── v1/
│ │ ├── routes.py # Defines routes for each endpoint
│ ├── core/ # Configuration and settings
│ │ ├── __init__.py
│ │ └── config.py # AWS and app configurations
│ ├── domain/ # Business logic and schemas
│ │ ├── __init__.py
│ │ └── models.py # User data models and schemas
│ ├── services/ # Business logic (e.g., authentication)
│ │ ├── __init__.py
│ │ └── auth_service.py # Authentication logic
│ └── repository/ # AWS Cognito integration
│ ├── __init__.py
│ └── cognito_repository.py
└── tests/ # Test cases for each layer
6. Creating User Management Functions
We’ll set up the layers, configuring each one to interact with AWS Cognito and handle user authentication.
core/config.py
— Configuration Layer
Manage environment variables and app configurations here.
# core/config.py
import os
from pydantic import BaseSettings
class Settings(BaseSettings):
aws_region: str = os.getenv("AWS_REGION", "us-west-2")
cognito_user_pool_id: str = os.getenv("COGNITO_USER_POOL_ID")
cognito_client_id: str = os.getenv("COGNITO_CLIENT_ID")
settings = Settings()
domain/models.py
— Domain Layer
Define the core data structures and validation schemas for User
.
# domain/models.py
from pydantic import BaseModel, EmailStr
class User(BaseModel):
username: str
email: EmailStr
password: str
class UserLogin(BaseModel):
username: str
password: str
repository/cognito_repository.py
— Repository Layer
This layer interfaces with AWS Cognito using boto3
.
# repository/cognito_repository.py
import boto3
from botocore.exceptions import ClientError
from app.core.config import settings
class CognitoRepository:
def __init__(self):
self.client = boto3.client("cognito-idp", region_name=settings.aws_region)
def sign_up_user(self, username: str, password: str, email: str):
try:
response = self.client.sign_up(
ClientId=settings.cognito_client_id,
Username=username,
Password=password,
UserAttributes=[{"Name": "email", "Value": email}],
)
return response
except ClientError as e:
raise e
def login_user(self, username: str, password: str):
try:
response = self.client.initiate_auth(
ClientId=settings.cognito_client_id,
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={
"USERNAME": username,
"PASSWORD": password,
},
)
return response["AuthenticationResult"]["AccessToken"]
except ClientError as e:
raise e
services/auth_service.py
— Service Layer
Handles business logic for user registration and login.
# services/auth_service.py
from fastapi import HTTPException
from app.domain.models import User, UserLogin
from app.repository.cognito_repository import CognitoRepository
class AuthService:
def __init__(self):
self.repo = CognitoRepository()
def register_user(self, user: User):
try:
response = self.repo.sign_up_user(user.username, user.password, user.email)
return {"message": "User registered successfully", "response": response}
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
def login_user(self, user_login: UserLogin):
try:
token = self.repo.login_user(user_login.username, user_login.password)
return {"access_token": token}
except Exception as e:
raise HTTPException(status_code=400, detail="Incorrect username or password")
api/v1/routes.py
— API Layer
Exposes endpoints to handle HTTP requests and responses.
# api/v1/routes.py
from fastapi import APIRouter, Depends
from app.domain.models import User, UserLogin
from app.services.auth_service import AuthService
router = APIRouter()
auth_service = AuthService()
@router.post("/signup")
def signup(user: User):
return auth_service.register_user(user)
@router.post("/login")
def login(user_login: UserLogin):
return auth_service.login_user(user_login)
main.py
— FastAPI Entry Point
Set up the FastAPI app and include routes.
# main.py
from fastapi import FastAPI
from app.api.v1 import routes
app = FastAPI()
app.include_router(routes.router, prefix="/api/v1")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
7. Deploying with Docker
Docker is an essential tool for creating and managing containers, enabling you to package the application along with all its dependencies, including FastAPI and the AWS SDK. Let’s configure Docker to deploy this application.
Step 1: Set Up a Dockerfile
Create a Dockerfile
in the root directory of your project. This file will define the instructions for building the Docker image.
# Start with an official Python image
FROM python:3.9-slim
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# Create a working directory for the app
WORKDIR /app
# Copy the requirements file and install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy the app source code
COPY . /app
# Expose the port FastAPI will run on
EXPOSE 8000
# Start FastAPI using Uvicorn
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Step 2: Add a Requirements File
Make sure you have a requirements.txt
file with the necessary dependencies.
fastapi==0.68.2
uvicorn==0.15.0
boto3==1.20.11
python-jose==3.3.0
pydantic==1.8.2
Step 3: Configure Docker Compose
Using Docker Compose can help you manage multi-container applications, including setting up any additional services (like a database if you later need one). Create a docker-compose.yml
file in the root directory:
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "8000:8000"
environment:
- AWS_REGION=us-west-2
- COGNITO_USER_POOL_ID=your_cognito_user_pool_id
- COGNITO_CLIENT_ID=your_cognito_client_id
Note: Replace
your_cognito_user_pool_id
andyour_cognito_client_id
with actual values from AWS Cognito.
Step 4: Build and Run the Docker Container
Now, you can build and start the Docker container using Docker Compose:
docker-compose up -d --build
This command will:
- Build the Docker image according to the instructions in the
Dockerfile
. - Start a container based on this image and expose it on port 8000.
Step 5: Test the Deployment
Once the container is running, you can test the endpoints by visiting http://localhost:8000/docs
to access FastAPI’s automatically generated Swagger documentation.
8. Conclusion: Clean Architecture and Docker for Consistency
By deploying with Docker, you ensure that your FastAPI app with AWS Cognito integration is consistent across different environments, with all dependencies neatly packaged. Coupled with Clean Architecture, this approach keeps your application modular, maintainable, and scalable. This setup is ready for production deployment on platforms like AWS ECS, Azure Container Instances, or any Kubernetes cluster, giving you flexibility and ease of scaling.