Enterprise-grade authentication and multi-tenancy SDK for Python
Project description
SaaSReady - Enterprise Multi-Tenant Authentication & Authorization
Drop-in authentication and authorization infrastructure for B2B SaaS applications. Similar to WorkOS, Auth0, or Clerk, but self-hosted and fully customizable.
๐ What is SaaSReady?
SaaSReady is a production-ready authentication and multi-tenancy backend that handles:
- User Authentication (JWT-based)
- Multi-Tenant Organizations (workspace isolation)
- Role-Based Access Control (RBAC) (permissions & roles)
- Member Invitations (database-ready, email integration needed)
- Audit Logging (track all user actions)
- Feature Flags (gradual rollouts & A/B testing)
- Authentication Secure JWT-based auth with email/password
- Admin Dashboard Beautiful Next.js UI for managing organizations, members, and permissions
- Database Migrations Alembic-powered schema versioning
- Docker Ready Complete containerization with docker-compose
- MIT Licensed Free to use for personal and commercial projects
- Perfect for B2B SaaS startups that need enterprise-grade auth without building from scratch
๐ Features
โ Core Authentication
- JWT-based user authentication
- Secure password hashing (Argon2 + Bcrypt)
- Session management with configurable token expiry
- Protected endpoints with Bearer token auth
โ Multi-Tenancy
- Organization-based tenant isolation
- Unique organization slugs (e.g.,
acme-corp) - Auto-creation of personal workspace on signup
- Member management per organization
โ RBAC (Role-Based Access Control)
- Pre-built roles:
Owner,Admin,Member,Viewer - Granular permissions:
org.update,user.invite,audit.read, etc. - Permission-based endpoint protection
- Custom role creation support
โ Member Invitations
- Invite users by email
- Automatic account creation for new users
- Membership status tracking (
active,invited,suspended) - Role assignment during invitation
โ Audit Logging
- Track all user actions (login, invite, role changes, etc.)
- Store IP address, user agent, metadata
- Organization-scoped logs
- Queryable with pagination
โ Feature Flags
- Global feature flags with default states
- Organization-level overrides
- Gradual rollout with percentage-based targeting
- Toggle features without code deployments
๐ ๏ธ Admin UI Included
- React/Next.js frontend
- Organization switcher
- Member management with role assignment
- Audit log viewer
- Feature flag dashboard
๐๏ธ Architecture
โโโโโโโโโโโโโโโโโโโ
โ Frontend โ Next.js (React) + Tailwind CSS
โ (Port 3000) โ โ Authentication, Org Management, Member Invites
โโโโโโโโโโฌโโโโโโโโโ
โ REST API (JWT)
โผ
โโโโโโโโโโโโโโโโโโโ
โ Backend โ FastAPI (Python) + SQLAlchemy
โ (Port 8000) โ โ Auth, RBAC, Multi-Tenancy, Audit Logs
โโโโโโโโโโฌโโโโโโโโโ
โ PostgreSQL
โผ
โโโโโโโโโโโโโโโโโโโ
โ Database โ PostgreSQL 15
โ (Port 5432) โ โ Users, Orgs, Roles, Permissions, Audit Logs
โโโโโโโโโโโโโโโโโโโ
๐ Quick Start (5 minutes)
Prerequisites
- Docker & Docker Compose
- Git
1. Clone the Repository
git clone https://github.com/ramprag/saasready.git
cd saasready
2. Configure Environment
# Backend
cp backend/.env.example backend/.env
# Frontend
cp frontend/.env.local.example frontend/.env.local
Edit backend/.env and change the secret key:
SECRET_KEY=your-super-secret-key-min-32-chars-change-this-now
3. Start the Stack
docker-compose up --build
Services will start:
- Frontend: http://localhost:3000
- Backend API: http://localhost:8000
- API Docs: http://localhost:8000/docs
- PostgreSQL: localhost:5432
4. Create Your First Account
- Visit http://localhost:3000/register
- Sign up with email/password
- You'll auto-login and see your personal organization
Default seeded roles:
Owner- Full accessAdmin- Manage users & settingsMember- Read organization dataViewer- Read-only access
๐ API Integration Guide
Authentication Flow
1. Register a New User
curl -X POST http://localhost:8000/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "user@company.com",
"password": "SecurePassword123",
"full_name": "John Doe"
}'
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer"
}
What happens:
- User account created
- Personal organization auto-created (
john-doe-org) - User assigned
Ownerrole in their org - Returns JWT token (expires in 7 days by default)
2. Login
curl -X POST http://localhost:8000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "user@company.com",
"password": "SecurePassword123"
}'
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer"
}
3. Get Current User
curl -X GET http://localhost:8000/api/v1/auth/me \
-H "Authorization: Bearer YOUR_TOKEN"
Response:
{
"id": "uuid-here",
"email": "user@company.com",
"full_name": "John Doe",
"is_active": true,
"is_superuser": false,
"created_at": "2025-01-20T10:30:00"
}
Organization Management
4. List User's Organizations
curl -X GET http://localhost:8000/api/v1/orgs \
-H "Authorization: Bearer YOUR_TOKEN"
Response:
[
{
"id": "org-uuid",
"name": "Acme Corp",
"slug": "acme-corp",
"description": "Our main workspace",
"is_active": true,
"created_at": "2025-01-20T10:30:00",
"updated_at": "2025-01-20T10:30:00"
}
]
5. Create Organization
curl -X POST http://localhost:8000/api/v1/orgs \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Engineering Team",
"slug": "engineering-team",
"description": "Development workspace"
}'
Response:
{
"id": "new-org-uuid",
"name": "Engineering Team",
"slug": "engineering-team",
"description": "Development workspace",
"is_active": true,
"created_at": "2025-01-20T11:00:00",
"updated_at": "2025-01-20T11:00:00"
}
Note: Creator automatically becomes Owner of the new org.
Member Invitations
6. Get Available Roles
curl -X GET http://localhost:8000/api/v1/orgs/{org_id}/roles \
-H "Authorization: Bearer YOUR_TOKEN"
Response:
[
{
"id": "role-uuid-1",
"name": "Owner",
"description": "Full access to organization",
"is_system": true,
"created_at": "2025-01-20T10:00:00"
},
{
"id": "role-uuid-2",
"name": "Admin",
"description": "Administrative access",
"is_system": true,
"created_at": "2025-01-20T10:00:00"
}
]
7. Invite User to Organization
curl -X POST http://localhost:8000/api/v1/orgs/{org_id}/invite \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "newuser@company.com",
"role_id": "role-uuid-2",
"full_name": "Jane Smith"
}'
Response:
{
"id": "membership-uuid",
"user_id": "new-user-uuid",
"organization_id": "org-uuid",
"role_id": "role-uuid-2",
"status": "invited",
"created_at": "2025-01-20T11:30:00",
"user_email": "newuser@company.com",
"user_full_name": "Jane Smith",
"role_name": "Admin"
}
โ ๏ธ Current Limitation:
- Creates membership with
invitedstatus - No email is sent (email service integration required)
- User can login immediately with default password
changeme123
To enable emails: Integrate SendGrid/AWS SES in org_service.py
8. List Organization Members
curl -X GET http://localhost:8000/api/v1/orgs/{org_id}/members \
-H "Authorization: Bearer YOUR_TOKEN"
Response:
[
{
"id": "membership-uuid",
"user_id": "user-uuid",
"organization_id": "org-uuid",
"role_id": "role-uuid",
"status": "active",
"created_at": "2025-01-20T10:30:00",
"user_email": "user@company.com",
"user_full_name": "John Doe",
"role_name": "Owner"
}
]
9. Update Member Role
curl -X PATCH http://localhost:8000/api/v1/orgs/{org_id}/members/{membership_id}/role \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"role_id": "new-role-uuid"
}'
Required Permission: user.manage
Permission-Protected Endpoints
10. Update Organization (Requires org.update)
curl -X PATCH http://localhost:8000/api/v1/orgs/{org_id} \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Updated Org Name",
"description": "New description"
}'
Response:
{
"id": "org-uuid",
"name": "Updated Org Name",
"slug": "acme-corp",
"description": "New description",
"is_active": true,
"created_at": "2025-01-20T10:30:00",
"updated_at": "2025-01-20T12:00:00"
}
Permission Check:
- Endpoint requires
org.updatepermission - Only
OwnerandAdminroles have this by default - Returns
403 Forbiddenif user lacks permission
Audit Logs
11. Get Organization Audit Logs
curl -X GET "http://localhost:8000/api/v1/audit/orgs/{org_id}/logs?limit=50&offset=0" \
-H "Authorization: Bearer YOUR_TOKEN"
Response:
[
{
"id": "log-uuid",
"actor_user_id": "user-uuid",
"organization_id": "org-uuid",
"action": "user.invite.sent",
"target_type": "membership",
"target_id": "membership-uuid",
"audit_metadata": {
"invited_email": "newuser@company.com",
"role_id": "role-uuid",
"inviter_id": "user-uuid"
},
"ip_address": "192.168.1.1",
"user_agent": "curl/7.81.0",
"created_at": "2025-01-20T11:30:00",
"actor_email": "admin@company.com"
}
]
Tracked Events:
user.registered,user.logged_in,user.invite.sentuser.role.updated,user.removedorg.created,org.updatedfeature_flag.enabled,feature_flag.disabled
Feature Flags
12. Get Organization Feature Flags
curl -X GET http://localhost:8000/api/v1/orgs/{org_id}/feature-flags \
-H "Authorization: Bearer YOUR_TOKEN"
Response:
[
{
"key": "beta-new-ui",
"name": "Beta New UI",
"description": "Enable the new redesigned user interface",
"default_enabled": false,
"enabled": true,
"overridden": true,
"rollout_percent": null
},
{
"key": "ai-insights",
"name": "AI Insights",
"description": "Enable AI-powered analytics",
"default_enabled": false,
"enabled": false,
"overridden": false,
"rollout_percent": null
}
]
13. Toggle Feature Flag for Organization
curl -X PUT http://localhost:8000/api/v1/orgs/{org_id}/feature-flags/beta-new-ui \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"rollout_percent": null
}'
Response:
{
"key": "beta-new-ui",
"name": "Beta New UI",
"description": "Enable the new redesigned user interface",
"default_enabled": false,
"enabled": true,
"overridden": true,
"rollout_percent": null
}
14. Reset Feature Flag to Default
curl -X DELETE http://localhost:8000/api/v1/orgs/{org_id}/feature-flags/beta-new-ui \
-H "Authorization: Bearer YOUR_TOKEN"
Response:
{
"key": "beta-new-ui",
"name": "Beta New UI",
"description": "Enable the new redesigned user interface",
"default_enabled": false,
"enabled": false,
"overridden": false,
"rollout_percent": null
}
๐ก๏ธ RBAC Implementation Guide
How Permissions Work
SaaSReady uses a Role โ Permission mapping system:
User โ Membership (in Org) โ Role โ Permissions
Default Roles & Permissions
| Role | Permissions |
|---|---|
| Owner | org.*, user.*, role.*, audit.read, feature_flags.* |
| Admin | org.read, org.update, user.invite, user.manage, audit.read |
| Member | org.read, audit.read |
| Viewer | org.read |
Protecting Endpoints
from app.core.dependencies import require_permission
@router.patch("/{org_id}")
def update_organization(
org_id: str,
data: OrganizationUpdate,
membership: Membership = Depends(require_permission("org.update")),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
# Only users with org.update permission can access this
org = db.query(Organization).filter(Organization.id == org_id).first()
# ... update logic
Available Permissions
# Organization
"org.read" # View organization details
"org.update" # Modify organization settings
"org.delete" # Delete organization
"org.settings" # Manage organization settings
# Users
"user.read" # View user information
"user.invite" # Invite users to organization
"user.manage" # Change roles, remove users
"user.create" # Create new users
"user.update" # Update user information
"user.delete" # Delete users
# Roles
"role.read" # View roles
"role.manage" # Create/edit roles and permissions
# Audit
"audit.read" # View audit logs
# API Keys (model exists, not implemented)
"api_key.manage" # Create/delete API keys
# Settings
"settings.read" # View settings
"settings.update" # Modify settings
๐ง Development
Project Structure
saasready/
โโโ backend/
โ โโโ alembic/ # Database migrations
โ โโโ app/
โ โ โโโ core/ # Config, database, security
โ โ โโโ models/ # SQLAlchemy models
โ โ โโโ schemas/ # Pydantic schemas
โ โ โโโ routes/ # API endpoints
โ โ โโโ services/ # Business logic
โ โ โโโ main.py # FastAPI app
โ โโโ tests/ # Pytest tests
โ โโโ requirements.txt
โ โโโ .env
โโโ frontend/
โ โโโ app/ # Next.js pages
โ โโโ components/ # React components
โ โโโ lib/ # API client, types
โ โโโ .env.local
โโโ docker-compose.yml
โโโ README.md
Local Development
Backend
cd backend
# Install dependencies
pip install -r requirements.txt
# Run migrations
alembic upgrade head
# Start server
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
Frontend
cd frontend
# Install dependencies
npm install
# Start dev server
npm run dev
Database Migrations
# Create new migration
alembic revision --autogenerate -m "Add new table"
# Apply migrations
alembic upgrade head
# Rollback
alembic downgrade -1
Run Tests
cd backend
pytest -v
๐ฆ Production Deployment
Environment Variables
Backend (backend/.env):
DATABASE_URL=postgresql://user:pass@host:5432/dbname
SECRET_KEY=your-super-secret-key-min-32-chars-long
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=10080 # 7 days
Frontend (frontend/.env.local):
NEXT_PUBLIC_API_URL=https://api.yourapp.com/api/v1
Deployment Checklist
- Change
SECRET_KEYto a strong random value (32+ chars) - Use managed PostgreSQL (AWS RDS, DigitalOcean, etc.)
- Enable HTTPS/SSL for API and frontend
- Set
ALGORITHM=HS256(or RS256 for asymmetric JWT) - Configure CORS origins in
app/main.py - Set up error tracking (Sentry)
- Enable database backups
- Review
SECURITY.mdfor production hardening - Implement rate limiting (Redis-based)
- Add email service (SendGrid, AWS SES)
Docker Production
# Build images
docker-compose -f docker-compose.prod.yml build
# Run in detached mode
docker-compose -f docker-compose.prod.yml up -d
๐ Security
Best Practices Implemented
โ
JWT-based authentication with expiry
โ
Argon2 password hashing (72-byte limit safe)
โ
CORS protection
โ
SQL injection prevention (SQLAlchemy ORM)
โ
Audit logging with IP tracking
โ
Permission-based endpoint protection
Security Considerations
โ ๏ธ JWT tokens stored in localStorage (consider httpOnly cookies)
โ ๏ธ Rate limiting is in-memory (use Redis for production)
โ ๏ธ Email verification not enforced (flag exists)
โ ๏ธ 2FA not implemented
โ ๏ธ No email sending (invitation emails)
See SECURITY.md for detailed security guidelines.
๐ค Comparison to Auth Providers
| Feature | SaaSReady | WorkOS | Auth0 | Clerk |
|---|---|---|---|---|
| Self-Hosted | โ | โ | โ | โ |
| Multi-Tenancy (Orgs) | โ | โ | โ | โ |
| RBAC | โ | โ | โ | โ |
| Audit Logs | โ | โ | โ | โ |
| Feature Flags | โ | โ | โ | โ |
| SSO (SAML/OIDC) | ๐ง | โ | โ | โ |
| Email Invitations | ๐ง | โ | โ | โ |
| Admin UI Included | โ | โ | โ | โ |
| Open Source | โ | โ | โ | โ |
| Free Tier | โ (Full) | โ | โ | โ |
Legend: โ Available | โ Not Available | ๐ง Partial (DB-ready, needs email)
๐ฃ๏ธ Roadmap
Phase 1: Current MVP โ
- User authentication (JWT)
- Multi-tenant organizations
- RBAC with permissions
- Member invitations (DB-only)
- Audit logging
- Feature flags
- Admin UI
Phase 2: Email & Invitations
- Email service integration (SendGrid/AWS SES)
- Secure invitation tokens
- Email templates (invite, password reset)
- Password reset flow
- Email verification enforcement
Phase 3: SSO & Advanced Auth
- SAML 2.0 authentication
- OIDC/OAuth2 providers
- Google Workspace integration
- Azure AD integration
- 2FA/MFA support
Phase 4: Enterprise Features
- API key authentication
- Webhooks system
- Custom role creation UI
- Directory sync (SCIM)
- Session management
Phase 5: Scale & Performance
- Redis rate limiting
- Horizontal scaling guide
- Multi-region deployment
- CDN integration
- Performance monitoring
๐ API Documentation
Interactive API Docs:
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
All endpoints return:
200 OK- Success400 Bad Request- Validation error401 Unauthorized- Missing/invalid token403 Forbidden- Insufficient permissions404 Not Found- Resource not found500 Internal Server Error- Server error
๐ Troubleshooting
"Organization not found" after registration
Solution: The organization is created but might not be returned immediately. Refresh the page or call GET /api/v1/orgs to list organizations.
"Failed to invite user"
Check:
- User has
user.invitepermission - Role ID exists (call
GET /orgs/{org_id}/roles) - Email is valid
- User isn't already a member
"403 Forbidden" on protected endpoints
Solution: Check user's role has required permission. Use GET /audit/orgs/{org_id}/logs to see permission checks.
Database connection errors
Solution:
# Check if PostgreSQL is running
docker-compose ps
# View logs
docker-compose logs db
# Restart services
docker-compose restart
Frontend can't connect to backend
Solution: Verify NEXT_PUBLIC_API_URL in frontend/.env.local matches your backend URL.
๐ค Contributing
See CONTRIBUTING.md for guidelines.
Quick Start:
- Fork the repo
- Create a feature branch:
git checkout -b feature/my-feature - Make changes and test
- Commit:
git commit -m "Add my feature" - Push:
git push origin feature/my-feature - Open a Pull Request
๐ License
MIT License - see LICENSE file for details.
๐ Acknowledgments
Built with:
- FastAPI - Modern Python web framework
- SQLAlchemy - SQL toolkit
- PostgreSQL - Database
- Next.js - React framework
- Tailwind CSS - CSS framework
Inspired by WorkOS, Auth0, and Clerk.
๐ Support
- ๐ Documentation
- ๐ฌ Discussions
- ๐ Report Bug
- ๐ก Request Feature
Made with โค๏ธ for the SaaS community
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file saasready-1.0.0.tar.gz.
File metadata
- Download URL: saasready-1.0.0.tar.gz
- Upload date:
- Size: 33.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9858291e41d06988ad008b7adc59b984f33ba6316025c342b2427dcf22b07823
|
|
| MD5 |
9554e00bd7a475d58bb3ab9dadfbf79e
|
|
| BLAKE2b-256 |
27bf52b3624929387793f90ff28017f70a09f9b42e2f4754ea8f7762754a6cce
|
File details
Details for the file saasready-1.0.0-py3-none-any.whl.
File metadata
- Download URL: saasready-1.0.0-py3-none-any.whl
- Upload date:
- Size: 20.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b58ec54d889704af906fde5bc928481c7e8b5d7aa734951d3d9ff0bb6738e145
|
|
| MD5 |
bdc3ae93c12d19f96127aa01b4c32fcf
|
|
| BLAKE2b-256 |
ba81e7b8591e627bfe533cdff9b11675c4f3ac71fab6f34ff2554025e53e93f4
|