Complete, secure, modular authentication system database-agnostic built with Rust and Axum.
- Overview
- Features
- Architecture
- Quick Start
- Database Configuration
- API Endpoints
- Project Structure
- Security
- How to Use in Other Projects
- Examples
- Testing
- Contributing
- License
This is a production-ready authentication system that can be easily integrated into any Rust project. The key feature is complete database independence, allowing you to choose (or switch) databases without changing a single line of business logic code.
- ✅ Database Agnostic - Use PostgreSQL, MySQL, SQLite, MongoDB or even in-memory
- ✅ Security First - Argon2 for password hashing, JWT for tokens
- ✅ Modular and Reusable - Clone and use in any project
- ✅ Type-Safe - Leverage Rust's type safety
- ✅ Async/Await - Maximum performance with Tokio
- ✅ Production Ready - Robust error handling
- ✅ Easy to Extend - Add new databases by implementing a trait
- User registration with validation
- Login with username/password
- JWT tokens (JSON Web Tokens)
- Route protection via middleware
- Tokens with expiration (24 hours by default)
- Password hashing with Argon2 (OWASP recommended)
- JWT signed with HMAC-SHA256
- Passwords never returned in responses
- Uniqueness validation (unique email and username)
- In-Memory - For development and testing
- PostgreSQL - Robust relational database
- MySQL - Compatible with MariaDB
- SQLite - Local database
- MongoDB - NoSQL document-based
- Repository Pattern - Complete decoupling
- Trait-based - Extensible and testable
- Async/Await - Performance with Tokio
- Modular - Use only what you need
┌─────────────────────────────────────────────────────┐
│ HTTP Layer (Axum Handlers) │
│ ┌─────────────┐ ┌──────────────┐ ┌────────────┐ │
│ │ Register │ │ Login │ │ Protected │ │
│ │ Handler │ │ Handler │ │ Routes │ │
│ └─────────────┘ └──────────────┘ └────────────┘ │
└────────────────────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Business Logic Layer (Services) │
│ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │
│ │ JWT Service │ │Crypto Service│ │Auth Logic │ │
│ └──────────────┘ └──────────────┘ └───────────┘ │
└────────────────────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Repository Layer (UserRepository Trait) │
│ Trait-based Abstraction │
└────────────────────────┬────────────────────────────┘
│
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│PostgreSQL│ │ MySQL │ │ MongoDB │
│ Impl │ │ Impl │ │ Impl │
└──────────┘ └──────────┘ └──────────┘
- Rust 1.70 or higher
- (Optional) Database of your choice
# Clone the repository
git clone https://github.com/seu-usuario/auth-system-rust.git
cd auth-system-rust
# Copy the example .env file
cp .env.example .env
# Edit .env and configure your JWT_SECRET
# You can generate one with: openssl rand -base64 32
nano .env# Compile and run
cargo run
# The server will start at http://0.0.0.0:3000# 1. Register a new user
curl -X POST http://localhost:3000/register \
-H "Content-Type: application/json" \
-d '{
"username": "john",
"email": "john@email.com",
"password": "Password123!"
}'
# Response: {"token":"eyJ0eXAiOiJKV1QiLCJhbGc..."}
# 2. Login
curl -X POST http://localhost:3000/login \
-H "Content-Type: application/json" \
-d '{
"username": "john",
"password": "Password123!"
}'
# 3. Access protected route (use the received token)
curl -X GET http://localhost:3000/private \
-H "Authorization: Bearer YOUR_TOKEN_HERE"
# Response: "Access granted for user: <user_id>"Ideal for: Development, testing, prototypes
Configuration: None! It's ready to use.
Warning: Data is lost when the process ends.
// Already configured in main.rs
let user_repo = Arc::new(InMemoryUserRepository::new());Ideal for: Production, robust applications
[features]
default = ["postgres"]JWT_SECRET=your_secret_here
DATABASE_URL=postgresql://user:password@localhost/auth_dbCREATE DATABASE auth_db;
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
is_active BOOLEAN DEFAULT TRUE
);
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_username ON users(username);See the commented section at the end of the src/main.rs file and uncomment the PostgreSQL block.
Ideal for: Applications already using MySQL/MariaDB
[features]
default = ["mysql"]DATABASE_URL=mysql://user:password@localhost/auth_dbCREATE TABLE users (
id CHAR(36) PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
is_active BOOLEAN DEFAULT TRUE
);Ideal for: Desktop applications, small projects
[features]
default = ["sqlite"]DATABASE_URL=sqlite://auth.dbCREATE TABLE users (
id TEXT PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
is_active INTEGER DEFAULT 1
);Ideal for: NoSQL applications, unstructured data
[features]
default = ["mongodb"]MONGODB_URI=mongodb://localhost:27017
MONGODB_DATABASE=auth_dbMongoDB creates the collection automatically. Optionally, you can create indexes for better performance:
# Option A: Run setup script (creates indexes)
cargo run --example mongodb_setup --features mongodb
# Option B: MongoDB creates everything automatically on first use
# Simply run the application!Note: MongoDB is schema-less (no fixed schema), so it doesn't need migrations like SQL databases.
Register a new user.
Request Body:
{
"username": "john",
"email": "john@email.com",
"password": "Password123!"
}Response (201 Created):
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}Errors:
409 Conflict- User already exists500 Internal Server Error- Processing error
Authenticate an existing user.
Request Body:
{
"username": "john",
"password": "Password123!"
}Response (200 OK):
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}Errors:
401 Unauthorized- Invalid credentials
Protected route (requires authentication).
Headers:
Authorization: Bearer <your_jwt_token>
Response (200 OK):
Access granted for user: <user_id>
Errors:
401 Unauthorized- Invalid, expired or missing token
auth-system/
├── Cargo.toml # Dependencies and configurations
├── .env # Environment variables (do not commit!)
├── .env.example # Configuration example
├── README.md # This documentation
│
├── src/
│ ├── lib.rs # Main library (AppState)
│ ├── main.rs # Entry point (HTTP server)
│ ├── errors.rs # Custom error types
│ │
│ ├── auth/ # Authentication module
│ │ ├── mod.rs
│ │ ├── crypto.rs # Hash/verification of passwords (Argon2)
│ │ ├── jwt.rs # JWT creation/validation
│ │ └── extractor.rs # Authenticated user extractor (Axum)
│ │
│ ├── db/ # Database layer
│ │ ├── mod.rs
│ │ ├── user_repository.rs # Trait (interface)
│ │ ├── memory_connection.rs # In-memory implementation
│ │ ├── postgres_connection.rs # PostgreSQL implementation
│ │ ├── mysql_connection.rs # MySQL implementation
│ │ ├── sqlite_connection.rs # SQLite implementation
│ │ └── mongodb_connection.rs # MongoDB implementation
│ │
│ ├── models/ # Data models
│ │ ├── mod.rs
│ │ ├── user.rs # User, CreateUser
│ │ └── auth.rs # LoginRequest, RegisterRequest, LoginResponse
│ │
│ └── handlers/ # HTTP Handlers
│ ├── mod.rs
│ └── auth_handler.rs # register_handler, login_handler
│
└── migrations/ # SQL migrations (optional)
└── 001_create_users.sql
We use Argon2, winner of the Password Hashing Competition and recommended by OWASP:
- ✅ Resistant to brute force attacks
- ✅ Resistant to GPU/ASIC attacks
- ✅ Unique salt per password
- ✅ Secure settings by default
- ✅ Signed with HMAC-SHA256
- ✅ Expires in 24 hours (configurable)
- ✅ Contains only the user ID (no sensitive data)
- ✅ Validated on each request
- Never commit
.env- Add to.gitignore - Use strong secrets - Generate with
openssl rand -base64 32 - HTTPS in production - Use TLS/SSL
- Rate limiting - Add brute force protection
- Input validation - Always validate user data
- Clone this repository to your project
- Choose the database (see configuration section)
- Customize models and handlers as needed
- Run and develop!
# Your project/Cargo.toml
[dependencies]
auth-system = { path = "../auth-system" }// Your project/src/main.rs
use auth_system::{AppState, handlers::auth_handler};
use auth_system::db::postgres_connection::PostgresUserRepository;
#[tokio::main]
async fn main() {
// Configure your database
let user_repo = Arc::new(PostgresUserRepository::new(pool));
let state = AppState {
jwt_secret: "...".into(),
user_repo,
};
// Use the ready-made handlers!
let app = Router::new()
.route("/register", post(auth_handler::register_handler))
.route("/login", post(auth_handler::login_handler));
}// Your project/src/db/custom_repository.rs
use async_trait::async_trait;
use auth_system::db::user_repository::UserRepository;
struct MyRepository {
// Your implementation
}
#[async_trait]
impl UserRepository for MyRepository {
// Implement the methods
async fn create(...) -> Result<User, AuthError> {
// Your logic
}
// ...
}use auth_system::{AppState, handlers::auth_handler};
use auth_system::db::postgres_connection::PostgresUserRepository;
use sqlx::postgres::PgPoolOptions;
#[tokio::main]
async fn main() {
dotenv().ok();
let db_pool = PgPoolOptions::new()
.max_connections(5)
.connect(&std::env::var("DATABASE_URL").unwrap())
.await
.unwrap();
let user_repo = Arc::new(PostgresUserRepository::new(db_pool));
let state = AppState {
jwt_secret: std::env::var("JWT_SECRET").unwrap(),
user_repo,
};
let app = Router::new()
.route("/register", post(auth_handler::register_handler))
.route("/login", post(auth_handler::login_handler))
.route("/profile", get(profile_handler)) // Custom handler
.with_state(state);
// ... server
}
// Custom handler that uses AuthUser
async fn profile_handler(user: AuthUser) -> Json<UserProfile> {
// user.user_id contains the authenticated user's ID
// Fetch additional data and return
Json(UserProfile { /* ... */ })
}#[cfg(test)]
mod tests {
use super::*;
use auth_system::db::memory_connection::InMemoryUserRepository;
#[tokio::test]
async fn test_register_success() {
let user_repo = Arc::new(InMemoryUserRepository::new());
let state = AppState {
jwt_secret: "test_secret".into(),
user_repo,
};
let request = RegisterRequest {
username: "test".into(),
email: "test@test.com".into(),
password: "Password123!".into(),
};
let result = register_handler(State(state), Json(request)).await;
assert!(result.is_ok());
}
}# Run all tests
cargo test
# Run with detailed output
cargo test -- --nocapture
# Test specific feature
cargo test --features postgresContributions are welcome! Please:
- Fork the project
- Create a branch for your feature (
git checkout -b feature/MyFeature) - Commit your changes (
git commit -m 'Add MyFeature') - Push to the branch (
git push origin feature/MyFeature) - Open a Pull Request
- Add more databases (Redis, DynamoDB, etc)
- Implement refresh tokens
- Add 2FA (Two-Factor Authentication)
- Rate limiting
- Email verification
- Password reset
- OAuth2 integration
- GraphQL support
This project is licensed under the MIT License. See the LICENSE file for more details.
- Axum - Web framework
- SQLx - SQL toolkit
- jsonwebtoken - JWT implementation
- argon2 - Password hashing
If you have problems or questions:
- Check the documentation
- Search for existing issues
- Open a new issue
Made with ❤️ and Rust 🦀