Skip to main content

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.

License: MIT Python 3.11+ FastAPI PostgreSQL


๐Ÿš€ 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:

4. Create Your First Account

  1. Visit http://localhost:3000/register
  2. Sign up with email/password
  3. You'll auto-login and see your personal organization

Default seeded roles:

  • Owner - Full access
  • Admin - Manage users & settings
  • Member - Read organization data
  • Viewer - 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 Owner role 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 invited status
  • 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.update permission
  • Only Owner and Admin roles have this by default
  • Returns 403 Forbidden if 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.sent
  • user.role.updated, user.removed
  • org.created, org.updated
  • feature_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_KEY to 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.md for 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:

All endpoints return:

  • 200 OK - Success
  • 400 Bad Request - Validation error
  • 401 Unauthorized - Missing/invalid token
  • 403 Forbidden - Insufficient permissions
  • 404 Not Found - Resource not found
  • 500 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:

  1. User has user.invite permission
  2. Role ID exists (call GET /orgs/{org_id}/roles)
  3. Email is valid
  4. 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:

  1. Fork the repo
  2. Create a feature branch: git checkout -b feature/my-feature
  3. Make changes and test
  4. Commit: git commit -m "Add my feature"
  5. Push: git push origin feature/my-feature
  6. Open a Pull Request

๐Ÿ“„ License

MIT License - see LICENSE file for details.


๐Ÿ™ Acknowledgments

Built with:

Inspired by WorkOS, Auth0, and Clerk.


๐Ÿ“ž Support


Made with โค๏ธ for the SaaS community

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

saasready-1.0.0.tar.gz (33.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

saasready-1.0.0-py3-none-any.whl (20.6 kB view details)

Uploaded Python 3

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

Hashes for saasready-1.0.0.tar.gz
Algorithm Hash digest
SHA256 9858291e41d06988ad008b7adc59b984f33ba6316025c342b2427dcf22b07823
MD5 9554e00bd7a475d58bb3ab9dadfbf79e
BLAKE2b-256 27bf52b3624929387793f90ff28017f70a09f9b42e2f4754ea8f7762754a6cce

See more details on using hashes here.

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

Hashes for saasready-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b58ec54d889704af906fde5bc928481c7e8b5d7aa734951d3d9ff0bb6738e145
MD5 bdc3ae93c12d19f96127aa01b4c32fcf
BLAKE2b-256 ba81e7b8591e627bfe533cdff9b11675c4f3ac71fab6f34ff2554025e53e93f4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page