A production-ready, security-hardened FastAPI authentication & authorization API featuring JWT tokens, bcrypt hashing, OAuth2 Password Flow, token refresh lifecycle, Role-Based Access Control (RBAC), and persistent PostgreSQL storage.
Click to expand
- 📖 Project Overview
- ✨ Core Features
- 🏗️ System Architecture
- 🔄 Authentication & Authorization Lifecycle
- 🛠️ Tech Stack
- 📁 Project Structure
- ⚙️ Setup & Installation
- 🔐 Environment Variables
▶️ Running the Application- 📡 API Endpoints & Usage
- 🎟️ JWT & Refresh Token Mechanics
- 👑 Role-Based Access Control (RBAC)
- 🧪 Testing & Verification Guide
- 🎥 Project Demonstration Guide
- 🔒 Security Practices & Implementation
⚠️ Limitations & Scope- 📚 Learning Outcomes
- 🔮 Future Improvements
- 👨💻 Author
Authentication and authorization are the backbone of secure web backends. This project delivers a robust, real-world authentication system built with FastAPI, SQLAlchemy, and PostgreSQL.
It provides complete user onboarding, deterministic password hashing using bcrypt, stateless authentication using dual Access + Refresh JWTs, compliance with FastAPI's OAuth2 Password Flow, and granular Role-Based Access Control (RBAC) protecting privileged administrative routes.
- 👤 User Registration: Input validation via Pydantic schemas with unique constraint checks.
- 🔑 Bcrypt Password Hashing: Salted, non-reversible password encryption using
passlib[bcrypt]. - 🎟️ Dual-Token JWT Architecture:
- Short-lived Access Tokens (30 min) for authorized API interactions.
- Long-lived Refresh Tokens (7 days) for seamless token renewals.
- 🌐 OAuth2 Password Flow: Standard-compliant
/tokenendpoint supportingOAuth2PasswordRequestForm. - 🛡️ Protected Routes: FastAPI dependency injection (
Depends) validating bearer tokens and user status. - 👑 Role-Based Access Control (RBAC): Distinct permission boundaries separating standard
userand privilegedadminaccounts. - 🗄️ Relational Persistence: Database abstraction via SQLAlchemy ORM with PostgreSQL backend.
- 👥 Admin User Management: Search, retrieve, update user roles, change passwords, and delete user records.
- 📖 Interactive API Documentation: Native OpenAPI Swagger UI (
/docs) and ReDoc (/redoc).
flowchart TD
subgraph Client
C[🌐 Client / Browser / Swagger]
end
subgraph FastAPI Application
GW[🚪 FastAPI Gateway]
subgraph Security Layer
AUTH[🔐 Auth Handlers\n/login & /token & /refresh]
DEP[🛡️ OAuth2 Bearer Dependency\nJWT Validation]
RBAC{👑 RBAC Guard\nrole == 'admin'?}
end
subgraph Business Logic & Endpoints
REG[POST /register]
PROT[GET /protected]
ADMIN[GET /admin\nUser Management APIs]
end
end
subgraph Persistence Layer
ORM[(SQLAlchemy ORM)]
DB[(🐘 PostgreSQL Database)]
end
C -->|HTTP Requests| GW
GW --> REG
GW --> AUTH
GW --> DEP
DEP -->|Authenticated User| PROT
DEP -->|Validate Role| RBAC
RBAC -->|Admin Granted| ADMIN
RBAC -->|Forbidden 403/401| C
REG -->|Bcrypt Hash & Save| ORM
AUTH -->|Verify Hash & Issue JWT| ORM
ADMIN -->|CRUD Users| ORM
ORM --> DB
sequenceDiagram
autonumber
actor Client
participant API as FastAPI /auth
participant DB as PostgreSQL
Client->>API: POST /login {username, password}
API->>DB: Fetch user by username
DB-->>API: User record (with hashed password)
API->>API: Verify bcrypt password hash
API-->>Client: Returns 200 OK (access_token [30m] + refresh_token [7d])
Note over Client,API: Access Protected Endpoints
Client->>API: GET /protected (Bearer access_token)
API->>API: Decode & validate signature/expiry
API-->>Client: Returns 200 OK (Protected Data)
Note over Client,API: When Access Token Expires (30m)
Client->>API: GET /protected (Expired access_token)
API-->>Client: 401 Unauthorized (Token expired)
Client->>API: POST /refresh {refresh_token}
API->>API: Validate refresh token signature & claims
API-->>Client: Returns 200 OK (New access_token)
| Technology | Purpose | Implementation Detail |
|---|---|---|
| Python | Core Language | Python 3.9+ |
| FastAPI | Web Framework | Async endpoints, dependency injection, auto OpenAPI docs |
| Uvicorn | ASGI Web Server | High-performance asynchronous runtime |
| SQLAlchemy | Database ORM | Declarative models, session lifecycle, and transaction management |
| PostgreSQL | Relational Database | ACID-compliant persistent user storage |
| Psycopg2 | PostgreSQL Driver | Native DBAPI driver for Python |
| Python-Jose / PyJWT | Token Generation | Cryptographic HMAC-SHA256 (HS256) signing & claim decoding |
| Passlib (Bcrypt) | Password Hashing | Automated salting and cryptographic hashing |
| OAuth2 | Auth Flow Standard | OAuth2PasswordBearer and OAuth2PasswordRequestForm integration |
| Pydantic | Data Validation | Request body schemas, email/password validation, response serialization |
| Python-Dotenv | Configuration | Environment-based secret and credential isolation |
secure-authentication-api/
│
├── main.py # FastAPI application, route declarations, and lifecycle
├── security.py # Password hashing utilities, JWT encoder/decoder, auth dependencies
├── database.py # SQLAlchemy engine, session maker, and DB dependency injection
├── models.py # Relational database models (User table definitions)
├── schemas.py # Pydantic models for request/response serialization & validation
│
├── requirements.txt # Pinned Python package dependencies
├── .env.example # Template for environment variables (committed to Git)
├── .env # Local secrets and connection strings (ignored by Git)
├── .gitignore # Excludes virtualenv, bytecode, and sensitive .env files
└── README.md # Comprehensive project documentation
git clone <YOUR_GITHUB_REPOSITORY_URL>
cd secure-authentication-api# Linux / macOS
python3 -m venv venv
source venv/bin/activate
# Windows (Command Prompt / PowerShell)
python -m venv venv
venv\Scripts\activatepip install --upgrade pip
pip install -r requirements.txtCreate a .env file in the root directory based on the .env.example template:
cp .env.example .envDefine the required parameters:
# PostgreSQL Database Connection String
DATABASE_URL=postgresql://username:password@localhost:5432/secure_auth_db
# Cryptographic JWT Secret & Algorithm
SECRET_KEY=super_secret_random_64_character_hex_key_here
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=30
REFRESH_TOKEN_EXPIRE_DAYS=7
⚠️ Security Warning: Never commit.envor production credentials to GitHub. Always keep.envin.gitignore.
Make sure your PostgreSQL instance is running and the database specified in DATABASE_URL exists.
uvicorn main:app --reload --host 127.0.0.1 --port 8000- Swagger UI: http://127.0.0.1:8000/docs
- ReDoc: http://127.0.0.1:8000/redoc
- Endpoint:
POST /register - Description: Creates a new user account with hashed password storage.
- Request Body:
{
"username": "vijay",
"password": "StrongPassword@123"
}- Response (
201 Created):
{
"id": 1,
"username": "vijay",
"role": "user",
"created_at": "2026-09-29T10:00:00"
}- Endpoint:
POST /login - Description: Verifies credentials and issues dual Access + Refresh tokens.
- Request Body:
{
"username": "vijay",
"password": "StrongPassword@123"
}- Response (
200 OK):
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer"
}- Endpoint:
POST /token - Content-Type:
application/x-www-form-urlencoded - Body Fields:
username=vijay&password=StrongPassword@123 - Response (
200 OK):
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer"
}- Endpoint:
POST /refresh - Request Body:
{
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}- Response (
200 OK):
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer"
}| Method | Endpoint | Authorization | Purpose |
|---|---|---|---|
GET |
/protected |
Bearer <access_token> |
Test authenticated user access |
GET |
/admin |
Bearer <admin_token> |
Test privileged administrative access |
GET |
/users |
Bearer <admin_token> |
Retrieve user lists or search by query |
GET |
/users/{id} |
Bearer <admin_token> |
Retrieve single user record by ID |
GET |
/search-users |
Bearer <admin_token> |
Search users by specific filters |
PATCH |
/update-role/{id} |
Bearer <admin_token> |
Promote/demote a user (user ⇄ admin) |
PATCH |
/password-update/ |
Bearer <access_token> |
Update authenticated user's password |
DELETE |
/user-delete/{id} |
Bearer <admin_token> |
Permanently remove user record |
The issued JWT contains standard cryptographic claims along with application identity:
{
"sub": "1",
"username": "vijay",
"role": "user",
"type": "access",
"exp": 1790680000
}- Access Token Lifespan (30 Minutes):
- Transmitted in HTTP headers:
Authorization: Bearer <access_token>. - Evaluated statelessly without frequent database lookups.
- Limited lifespan drastically reduces the window of vulnerability if intercepted.
- Transmitted in HTTP headers:
- Refresh Token Lifespan (7 Days):
- Transmitted strictly to the
/refreshendpoint when an access token expires. - Restores API access without forcing the user to re-enter credentials.
- Transmitted strictly to the
The API enforces strict permission tiers using FastAPI dependencies:
flowchart TD
REQ[HTTP Request] --> TOK{Valid Bearer JWT?}
TOK -->|No / Invalid| E401[❌ 401 Unauthorized]
TOK -->|Yes| USER{Resolve Current User}
USER --> ROUTE{Requested Route}
ROUTE -->|/protected| POK[✅ Access Granted]
ROUTE -->|/admin & Admin APIs| CHK{User.role == 'admin'?}
CHK -->|No| E403[⛔ 403 Forbidden / Access Denied]
CHK -->|Yes| AOK[✅ Admin Access Granted]
def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
# Validates token signature, expiration, and resolves user record
...
def require_admin(current_user: User = Depends(get_current_user)):
if current_user.role != "admin":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Admin access required"
)
return current_userThe entire API surface can be verified through Swagger UI (http://127.0.0.1:8000/docs) or curl:
Click to expand validation test cases
| # | Test Scenario | Steps | Expected Result | Status |
|---|---|---|---|---|
| 01 | User Registration | Send POST /register with username and password |
Returns 201 Created with user ID |
✅ Passed |
| 02 | Successful Login | Send POST /login with registered credentials |
Returns 200 OK with Access & Refresh tokens |
✅ Passed |
| 03 | Invalid Login | Send POST /login with bad password |
Returns 401 Unauthorized |
✅ Passed |
| 04 | Protected Endpoint (No Auth) | Call GET /protected without header |
Returns 401 Unauthorized (Not authenticated) |
✅ Passed |
| 05 | Protected Endpoint (With Auth) | Authorize Swagger with Bearer token & call GET /protected |
Returns 200 OK with user payload |
✅ Passed |
| 06 | Admin Access (Regular User) | Call GET /admin with standard user token |
Returns 403 Forbidden (Admin access required) |
✅ Passed |
| 07 | Admin Access (Admin User) | Call GET /admin with admin role token |
Returns 200 OK (Welcome Admin) |
✅ Passed |
| 08 | Refresh Token Flow | Submit valid refresh token to POST /refresh |
Returns 200 OK with new access token |
✅ Passed |
| 09 | OAuth2 Password Form | Submit form data to POST /token |
Returns 200 OK with Bearer access token |
✅ Passed |
| 10 | Expiration Lifetimes | Verify token timestamps (exp) |
Access token = 30 min, Refresh token = 7 days | ✅ Passed |
When recording or presenting a walkthrough of this project, follow this structured narrative:
- Architecture & Schema Overview: Introduce FastAPI, PostgreSQL, SQLAlchemy ORM, and bcrypt password hashing.
- User Onboarding: Demonstrate
POST /registerand inspect the database to prove password salt and hash (no plaintext storage). - Login & Dual Token Generation: Execute
POST /loginto display the Access Token and Refresh Token payloads. - Protected Route Verification:
- Attempt
GET /protectedunauthenticated → Demonstrate401 Unauthorized. - Attach the Bearer token in Swagger → Demonstrate
200 OK.
- Attempt
- RBAC Demonstration:
- Call
GET /adminas standard user → Show403 Forbidden. - Update user role to
adminin database or via/update-role/{id}. - Re-authenticate and call
GET /admin→ Show200 OK.
- Call
- Token Refresh Lifecycle: Submit the refresh token to
POST /refreshand use the resulting new access token to continue making authenticated requests. - FastAPI OAuth2 Standard: Show native integration with Swagger's Authorize button using the
/tokenendpoint.
- 🛡️ No Plaintext Passwords: Passwords are mathematically transformed using salted bcrypt hashing prior to SQL insertion.
- ⏳ Limited Token Exposure: 30-minute access token limit bounds the blast radius of compromised tokens.
- 🔑 Cryptographic Signing: HS256 algorithm with 256-bit secret key validates token authenticity and prevents tampering.
- 🧱 Least Privilege Enforcement: Strict separation between standard user capabilities and administrative APIs.
- ⚙️ Strict Environment Isolation: No hardcoded database credentials or keys; configurations are injected at runtime via
.env.
- Logout Token Blacklisting (Bonus):
- Status: Not Implemented.
- Reason: In standard stateless JWTs, tokens remain valid until expiration unless tracked in a fast in-memory blacklist (such as Redis). This project maintains stateless tokens without Redis infrastructure dependencies.
Through building and testing this project, the following core software engineering competencies were developed:
- Implementing clean RESTful authentication patterns in FastAPI.
- Structuring relational database operations using SQLAlchemy models, engines, and sessions.
- Generating, validating, and signing JSON Web Tokens (JWT).
- Implementing the official OAuth2 Password Bearer specification.
- Engineering granular Role-Based Access Control via reusable dependency injection.
- Managing database connections and configuration securely through environment variables.
- Performing API validation and end-to-end regression testing via Swagger UI.
- Implement token revocation/blacklist using Redis key-value TTLs.
- Add email verification on registration using background tasks.
- Implement rate-limiting on
/loginand/tokenendpoints (slow-down brute force attacks). - Add automated unit and integration tests using
pytestandhttpx. - Containerize application and PostgreSQL service using
docker-compose.
Vijay Rangvani
Backend & AI Engineering Learner
Built with FastAPI, PostgreSQL, and SQLAlchemy.