Skip to content

Sbussiso/LOauth2

Repository files navigation

Your OAuth2 Server. Your Rules. Your Data.

A complete, self-hosted OAuth 2.0 / OIDC Authorization Server that you control.

No third-party dependencies. No cloud providers. No data sharing. Deploy it locally, own it completely, and never worry about external OAuth services again.

Python 3.10+ License: MIT Self-Hosted


πŸ” Why Self-Host Your OAuth2 Server?

You Own Your Authentication

  • No vendor lock-in - Your auth infrastructure, your control
  • Complete data privacy - User credentials never leave your infrastructure
  • Zero external dependencies - No Auth0, Okta, or cloud provider required
  • Unlimited users - No per-user pricing or subscription fees

Enterprise-Grade Security

  • Industry-standard protocols - OAuth 2.0, OIDC, PKCE (S256)
  • Production-ready - Used in real-world applications
  • Audit everything - Full access to logs, tokens, and user activity
  • Custom policies - Define your own consent flows and token lifetimes

Total Flexibility

  • Deploy anywhere - Local network, private cloud, or air-gapped environments
  • Customize everything - Scopes, claims, token formats, consent screens
  • Integrate seamlessly - Works with any OAuth2-compatible application
  • Scale on your terms - From 10 users to 10,000+

πŸš€ Get Started in 60 Seconds

# Clone and run
git clone https://github.com/Sbussiso/LOauth2.git
cd LOauth2
export ENABLE_DEV_ENDPOINTS=true
uv run server.py

# Complete setup at http://127.0.0.1:8000/setup
# Your OAuth2 server is now running!

That's it. No accounts to create. No credit card required. No data sent to third parties.


⚑ Core Features

πŸ”’ Security First

  • βœ… PKCE (S256) - Protection against authorization code interception
  • βœ… Refresh Token Rotation - Automatic token rotation for enhanced security
  • βœ… RS256 Signing Keys - Industry-standard JWT signing with key rotation
  • βœ… Consent Management - Granular user consent with configurable policies
  • βœ… Client Authentication - Multiple auth methods (none, secret_post, secret_basic)

πŸŽ›οΈ Full Control

  • βœ… Admin Web UI - Manage clients, scopes, policies, and keys through intuitive interface
  • βœ… Admin REST APIs - Automate configuration and management
  • βœ… Custom Scopes - Define your own scopes with descriptions and claims
  • βœ… Flexible Policies - Per-client token lifetimes, consent rules, and formats
  • βœ… SQLite/PostgreSQL/MySQL - Choose your database backend

🌐 Standards Compliant

  • βœ… OpenID Connect (OIDC) - Full OIDC discovery, JWKS, UserInfo, ID tokens
  • βœ… OAuth 2.0 - Authorization Code, Refresh Token grants
  • βœ… Token Operations - Revocation, introspection, refresh
  • βœ… Multiple Client Types - Public (SPAs, mobile) and confidential (backend)

πŸ› οΈ Developer Friendly

  • βœ… Working Examples - Complete demo apps included (todo, camera)
  • βœ… Dev Tools - Seed data, PKCE generator, quick client creation
  • βœ… Comprehensive Docs - Troubleshooting guides and code examples
  • βœ… Easy Integration - Works with any OAuth2 client library

πŸ“– Documentation

🚦 Getting Started

πŸ‘¨β€πŸ’» For Developers

πŸš€ Advanced


LOauth2 vs. Cloud OAuth Providers

Feature Self-Hosted (This Project) Auth0 / Okta / etc.
Data Privacy βœ… 100% on your infrastructure ❌ Data on their servers
Cost βœ… Free (MIT License) ❌ $$$+ per user/month
Vendor Lock-in βœ… None ❌ Tied to provider
Customization βœ… Unlimited ⚠️ Limited by their plans
Network Requirements βœ… Works offline/air-gapped ❌ Requires internet
Control βœ… You own everything ❌ Subject to their terms
Audit & Compliance βœ… Full access to all data ⚠️ Limited visibility
Scalability βœ… Scale on your hardware ⚠️ Pay for each tier

🎯 Perfect For

  • 🏒 Enterprise Teams - Own your authentication without vendor fees
  • πŸ”¬ Research Labs - Air-gapped or secure environments
  • 🏠 Homelab Enthusiasts - Self-host all your services with OAuth2
  • πŸš€ Startups - Start free, scale without per-user costs
  • πŸ›‘οΈ Privacy-Focused Orgs - Keep user data on your infrastructure
  • πŸŽ“ Education - Learn OAuth2/OIDC with a real server

πŸ“‹ Requirements

  • Python 3.10+
  • SQLite (included) or PostgreSQL/MySQL
  • 512MB RAM minimum (scales with usage)

πŸ”§ Installation

Quick Start (SQLite)

# Using uv (recommended)
uv run server.py

# Using pip
python3 -m venv .venv
source .venv/bin/activate
pip install flask authlib sqlalchemy
python server.py

Server starts at http://127.0.0.1:8000

Production Setup (PostgreSQL)

# Set database URL
export DATABASE_URL="postgresql://user:pass@localhost/oauth"
export APP_SECRET="your-secure-random-secret-key"

# Run with Gunicorn
pip install gunicorn
gunicorn -w 4 -b 0.0.0.0:8000 server:app

Docker Deployment

docker build -t oauth2-server .
docker run -p 8000:8000 \
  -e DATABASE_URL="sqlite:////data/oauth.db" \
  -e APP_SECRET="..." \
  -v oauth2_app_data:/data \
  oauth2-server

Docker Compose (SQLite-only)

# Optional: set a strong secret for sessions
export APP_SECRET=$(python -c 'import secrets; print(secrets.token_hex(32))')

# Build and start (no .env required)
docker compose up -d --build

# Then open http://127.0.0.1:8000/setup
  • Compose defaults to DATABASE_URL=sqlite:////data/oauth.db and persists data in the app_data volume.
  • To enable dev helpers (e.g. /dev/seed, /dev/pkce):
    export ENABLE_DEV_ENDPOINTS=true
    docker compose up -d
    Then seed:
    curl "http://127.0.0.1:8000/dev/seed?reset=1" -H "X-Admin-Token: <ADMIN_TOKEN>"

Where your data lives (Docker)

  • SQLite data is stored inside the container at: /data/oauth.db.
  • It is persisted via the named volume app_data in docker-compose.yml.
  • The volume keeps your data when you stop/remove the container.
  • To remove all data, including the volume:
    docker compose down -v   # WARNING: deletes volumes and your data

βš™οΈ Configuration

Environment Variables

Variable Description Default
APP_SECRET Flask session secret (use strong random value) Random (dev only)
DATABASE_URL SQLAlchemy database URL sqlite:///oauth.db
ENABLE_DEV_ENDPOINTS Enable /dev/* helper routes false
ADMIN_TOKEN Bootstrap admin token (first-time setup only) Generated at /setup

Database Options

SQLite (development/small deployments):

# Automatic - database file created at ./oauth.db
uv run server.py

PostgreSQL (recommended for production):

export DATABASE_URL="postgresql://user:password@host:5432/dbname"
uv run server.py

MySQL/MariaDB:

export DATABASE_URL="mysql+pymysql://user:password@host:3306/dbname"
pip install pymysql
uv run server.py

🎬 Quick Start Guide

1. Initial Setup (One Time)

export ENABLE_DEV_ENDPOINTS=true
uv run server.py

Open http://127.0.0.1:8000/setup in your browser:

  • System generates a secure Admin Token
  • Copy it immediately (shown only once)
  • Token is hashed and stored in database
  • Use it to access Admin UI and APIs

2. Seed Demo Data (Optional)

export BASE=http://127.0.0.1:8000
curl "$BASE/dev/seed" -H "X-Admin-Token: <YOUR_ADMIN_TOKEN>"

Creates:

  • Demo users: alice/alice, bob/bob
  • Demo client: demo-web (public, PKCE-enabled)
  • Default scopes: openid, profile, email, offline_access

3. Test with Demo App

# Run the included todo demo
uv run todo_demo.py

# Open http://localhost:3000
# Sign in with alice/alice

πŸŽ‰ You now have a working OAuth2 server!


πŸ”„ OAuth2 Authorization Flow

Complete Walkthrough

Step 1: Register Your Application

Access Admin UI at http://127.0.0.1:8000/admin/login

Create a new client:

Client ID: my-app
Redirect URI: http://localhost:3000/callback
Grant Types: authorization_code refresh_token
Response Types: code
Scope: openid profile email offline_access
Client Type: Public (for SPAs/mobile) or Confidential (for backends)

For confidential clients: Copy the generated client_secret

Step 2: Implement Authorization (Your App)

# Generate PKCE
import os, base64, hashlib

verifier = base64.urlsafe_b64encode(os.urandom(40)).decode().rstrip("=")
challenge = base64.urlsafe_b64encode(
    hashlib.sha256(verifier.encode()).digest()
).decode().rstrip("=")

# Redirect user to authorization endpoint
auth_url = (
    "http://127.0.0.1:8000/authorize?"
    "client_id=my-app&"
    "response_type=code&"
    "redirect_uri=http://localhost:3000/callback&"
    "scope=openid profile email offline_access&"
    "code_challenge_method=S256&"
    f"code_challenge={challenge}&"
    "state=random-state-value"
)

Step 3: Handle Callback

User approves consent β†’ Redirected to your callback URL with code

# Exchange code for tokens
response = requests.post(
    "http://127.0.0.1:8000/token",
    data={
        "grant_type": "authorization_code",
        "client_id": "my-app",
        "client_secret": "...",  # Only for confidential clients
        "code": code_from_callback,
        "redirect_uri": "http://localhost:3000/callback",
        "code_verifier": verifier,  # PKCE verifier
    }
)

tokens = response.json()
# {
#   "access_token": "...",
#   "refresh_token": "...",
#   "id_token": "...",
#   "token_type": "Bearer",
#   "expires_in": 3600
# }

Step 4: Access Protected Resources

# Call UserInfo endpoint
response = requests.get(
    "http://127.0.0.1:8000/userinfo",
    headers={"Authorization": f"Bearer {access_token}"}
)

user_info = response.json()
# {
#   "sub": "1",
#   "preferred_username": "alice",
#   "email": "alice@example.com",
#   ...
# }

Step 5: Refresh Tokens (Optional)

# When access token expires
response = requests.post(
    "http://127.0.0.1:8000/token",
    data={
        "grant_type": "refresh_token",
        "client_id": "my-app",
        "refresh_token": refresh_token,
    }
)

new_tokens = response.json()
# Old refresh_token is revoked, new one issued (rotation)

πŸ“š See Client Examples for complete working code.


🚨 Troubleshooting

Common Issues

❌ Getting 401 during token exchange?

This is usually one of three things:

  1. Missing client_secret (confidential clients)

    # Check your client configuration
    python3 -c "import sqlite3; conn = sqlite3.connect('oauth.db');
    print(conn.execute('SELECT client_id, token_endpoint_auth_method, client_secret
    FROM oauth2_client WHERE client_id=\"YOUR_CLIENT_ID\"').fetchone())"

    β†’ Full solution in Troubleshooting Guide

  2. Missing PKCE (public clients) β†’ See PKCE implementation guide

  3. redirect_uri mismatch β†’ Must match exactly in both authorize and token requests

❌ Session/state lost during OAuth redirect?

❌ Still having issues?


πŸ“š Demo Applications & Integrations

Included Demo Apps

Two complete, working OAuth2 client applications:

Todo Demo (todo_demo.py)

  • Complete SPA-style OAuth flow
  • PKCE implementation
  • Session management
  • Refresh token handling

Camera Demo (camera_demo.py)

  • Custom scopes demonstration
  • File access permissions
  • Real-world use case
# Run any demo
uv run todo_demo.py
# Open http://localhost:3000

Real-World Integration: OpenSentry

OpenSentry - Smart security camera system with OAuth2 authentication

OpenSentry is a complete production application that integrates with this OAuth2 server for centralized authentication across multiple camera devices.

Key Features:

  • πŸ“Ή Live video streaming with motion/object/face detection
  • πŸ” OAuth2/OIDC authentication (fallback to local auth)
  • 🌐 mDNS device discovery
  • 🏒 Multi-device SSO support
  • 🐳 Docker-ready deployment

Quick Integration:

# 1. Start the OAuth2 server
cd Oauth2
uv run server.py

# 2. Register OpenSentry as a client
cat > add_opensentry_client.py << 'EOF'
#!/usr/bin/env python3
import os
os.environ['DATABASE_URL'] = os.environ.get('DATABASE_URL', 'sqlite:///oauth.db')
from server import SessionLocal, OAuth2Client

db = SessionLocal()
existing = db.query(OAuth2Client).filter_by(client_id='opensentry-device').first()

redirect_uris = 'http://localhost:5000/oauth2/callback http://127.0.0.1:5000/oauth2/callback'
scope = 'openid profile email offline_access'

if existing:
    print("Updating existing client...")
    existing.client_secret = None
    existing.client_name = 'OpenSentry Device'
    existing.redirect_uris = redirect_uris
    existing.scope = scope
    existing.grant_types = 'authorization_code refresh_token'
    existing.response_types = 'code'
    existing.token_endpoint_auth_method = 'none'
    existing.require_consent = True
    db.commit()
    print("βœ“ Client 'opensentry-device' updated")
else:
    print("Creating new client...")
    client = OAuth2Client(
        client_id='opensentry-device',
        client_secret=None,
        client_name='OpenSentry Device',
        redirect_uris=redirect_uris,
        scope=scope,
        grant_types='authorization_code refresh_token',
        response_types='code',
        token_endpoint_auth_method='none',
        require_consent=True
    )
    db.add(client)
    db.commit()
    print("βœ“ Client 'opensentry-device' created")

db.close()
EOF

uv run python add_opensentry_client.py

# 3. Start OpenSentry
cd ../OpenSentry
uv run server.py

# 4. Configure OAuth2 in OpenSentry settings
# Navigate to http://127.0.0.1:5000/settings
# Select OAuth2 Authentication
# Base URL: http://127.0.0.1:8000
# Client ID: opensentry-device
# Save and restart

Benefits of OAuth2 with Multiple OpenSentry Devices:

  • βœ… Single Sign-On - One login for all your security cameras
  • βœ… Centralized User Management - Add/remove users in one place
  • βœ… Audit Trail - Track authentication across all devices
  • βœ… Enhanced Security - MFA, token rotation, PKCE
  • βœ… Graceful Fallback - Local auth available if OAuth2 server is down

See OpenSentry documentation for complete setup guide.


πŸ” Security & Production

Security Checklist

Before deploying to production:

  • Replace demo auth - Integrate your production authentication (LDAP, SAML, etc.)
  • Generate strong APP_SECRET - 32+ random bytes
  • Use environment variables - Never hardcode secrets
  • Enforce strict redirect_uris - No wildcards, exact matches only
  • Enable PKCE for public clients - Mandatory in client policy
  • Use client_secret_basic - For confidential client authentication
  • Enable rate limiting - Protect /token and /authorize endpoints
  • Set up monitoring - Track failed auth attempts, token usage
  • Rotate signing keys - Periodic key rotation via Admin UI
  • Use HTTPS - TLS termination at reverse proxy (nginx, ALB)
  • Regular backups - Backup database (contains keys and settings)
  • Audit logging - Log all admin actions and token operations

Production Deployment

With Gunicorn (Recommended)

pip install gunicorn
gunicorn -w 4 -b 0.0.0.0:8000 server:app

With Docker

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8000", "server:app"]
docker build -t oauth2-server .
docker run -d -p 8000:8000 \
  -e DATABASE_URL="postgresql://..." \
  -e APP_SECRET="..." \
  oauth2-server

With Docker Compose

version: '3.8'
services:
  oauth2:
    build: .
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgresql://oauth:password@db:5432/oauth
      APP_SECRET: ${APP_SECRET}
    depends_on:
      - db

  db:
    image: postgres:15
    environment:
      POSTGRES_DB: oauth
      POSTGRES_USER: oauth
      POSTGRES_PASSWORD: password
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

Behind Nginx (TLS Termination)

server {
    listen 443 ssl http2;
    server_name oauth.yourdomain.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Production Checklist

  • Use PostgreSQL or MySQL (not SQLite)
  • Configure connection pooling
  • Set up TLS-terminating reverse proxy
  • Ensure consistent external URL (stable OIDC issuer)
  • Enable database replication/backups
  • Set up centralized logging
  • Configure health checks (/health endpoint)
  • Use secrets management (Vault, AWS Secrets Manager, etc.)
  • Set up alerting for auth failures
  • Document disaster recovery procedures

πŸ› οΈ Client Types Explained

Type Auth Method Secret? PKCE? Best For
Public none ❌ No βœ… Required SPAs, mobile apps, desktop apps
Confidential client_secret_post
client_secret_basic
βœ… Required ⚠️ Optional Backend services, server-to-server

Public clients can't securely store secrets (e.g., JavaScript in browser), so they must use PKCE for security.

Confidential clients run on secure servers where secrets can be protected.


❓ FAQ

Is this production-ready?

Yes! This server implements industry-standard OAuth 2.0 and OIDC protocols. It's used in real-world applications. Follow the Security Checklist for production deployments.

Can I use this commercially?

Absolutely! MIT License means you can use it for any purpose, including commercial projects. No attribution required (but appreciated!).

How does this compare to Auth0/Okta?

See the comparison table above. Main advantages: zero cost, complete control, data privacy, no vendor lock-in.

Can I integrate with my existing user database?

Yes! Replace the demo login system in server.py with your own authentication (LDAP, database, SAML, etc.). The OAuth2/OIDC layer remains the same.

Does it work offline/air-gapped?

Yes! Self-hosted means no external dependencies. Perfect for secure environments without internet access.

How do I reset the Admin Token?

Update server_settings.admin_token_hash in the database, or reinitialize the database for development.

Can I run multiple instances for high availability?

Yes! Use a shared database (PostgreSQL with replication) and deploy multiple app instances behind a load balancer. Sessions are stored server-side in the database.

What about user registration?

User registration is intentionally not included (out of scope for OAuth2). Integrate your own user management system or use the /dev/seed helper for development.


🀝 Contributing

Contributions welcome! This project thrives on community feedback.


πŸ“„ License

MIT License - See LICENSE for details.

Use it, modify it, sell it, deploy it anywhere. No restrictions.


🌟 Why We Built This

We believe authentication should be:

  • βœ… Under your control - Not locked behind a vendor
  • βœ… Privacy-respecting - Your users' data stays with you
  • βœ… Cost-effective - No per-user fees that scale with success
  • βœ… Transparent - Open source means you can audit everything
  • βœ… Standards-based - Works with any OAuth2-compatible app

Cloud OAuth providers have their place, but you should have the choice to self-host.


πŸ”— Resources & Links

Documentation

Related Projects

Community

Standards


Take back control of your authentication.
Self-hosted. Open source. Yours forever.

Get Started β†’

About

a local Oauth2 server for local applications and services

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors