Skip to main content

A library for Microsoft SSO authentication and authorization with RBAC support

Project description

Core Infinity Stones SSO Authentication Library

A private library for Microsoft Single Sign-On (SSO) authentication and authorization with hybrid Role-Based Access Control (RBAC) support.

Features

  • ✅ Complete Microsoft SSO login and logout flows
  • Microsoft Azure AD groups - Use Azure AD groups for authorization
  • Microsoft application roles - Use app roles defined in Azure AD
  • Database roles and permissions - Store custom roles in your database
  • ✅ Token refresh support - Refresh expired tokens without re-authentication
  • ✅ FastAPI integration with middleware and dependencies
  • ✅ PostgreSQL database support with Alembic migrations
  • ✅ Secure JWT token handling and validation
  • ✅ Automatic user creation and role synchronization

Installation

Prerequisites

  • Python 3.10+
  • Poetry
  • PostgreSQL database

Setup

  1. Clone the repository and install dependencies:
poetry install
  1. Create a .env file (optional, only needed for migrations):
cp .env.example .env
  1. Configure your environment variables (for migrations only):
MICROSOFT_CLIENT_ID=your_client_id_here
MICROSOFT_CLIENT_SECRET=your_client_secret_here
MICROSOFT_TENANT_ID=your_tenant_id_here
MICROSOFT_REDIRECT_URI=http://localhost:8000/auth/callback
DATABASE_URL=postgresql://user:password@localhost:5432/microsoft_sso_db
SECRET_KEY=your-secret-key-here-change-this-in-production
  1. Set up the database:
# Create initial migration
alembic revision --autogenerate -m "Initial migration"

# Apply migrations
alembic upgrade head

# Set up initial database roles/permissions (optional)
python examples/setup_database.py

Azure AD Setup

  1. Go to Azure Portal
  2. Navigate to Azure Active Directory > App registrations
  3. Create a new registration:
    • Name: Your app name
    • Supported account types: Single tenant
    • Redirect URI: http://localhost:8000/auth/callback (or your production URL)
  4. After creation, note:
    • Application (client) IDMICROSOFT_CLIENT_ID
    • Directory (tenant) IDMICROSOFT_TENANT_ID
  5. Go to Certificates & secrets and create a new client secret → MICROSOFT_CLIENT_SECRET
  6. Go to API permissions and add:
    • Microsoft Graph > Delegated permissions:
      • User.Read
      • GroupMember.Read.All (to read user's group memberships)
      • email
      • offline_access (to get refresh tokens)
    • Click Grant admin consent (if you have admin rights)

Quick Start

Recommended: Using Class Initializers (For Compiled Packages)

from fastapi import FastAPI, Depends
from core_infinity_stones_sso import Settings, MicrosoftSSOAuthManager, User

app = FastAPI()

# Initialize with explicit parameters (no .env file needed)
settings = Settings(
    microsoft_client_id="your_client_id",
    microsoft_client_secret="your_client_secret",
    microsoft_tenant_id="your_tenant_id",
    microsoft_redirect_uri="http://localhost:8000/auth/callback",
    database_url="postgresql://user:pass@localhost:5432/db",
    secret_key="your-secret-key",
)

# Create auth manager
auth_manager = MicrosoftSSOAuthManager(settings)

# Include auth routes
app.include_router(auth_manager.get_router())

@app.get("/protected")
def protected_route(user: User = Depends(auth_manager.get_current_user_dependency())):
    return {
        "message": f"Hello {user.email}!",
        "groups": user.get_group_names(),  # Microsoft groups
        "app_roles": user.get_app_role_names(),  # Microsoft app roles
        "db_roles": user.get_db_role_names(),  # Database roles
        "permissions": user.get_permission_names(),  # Permissions from roles
    }

# Require a Microsoft group
@app.get("/admin")
def admin_route(user: User = Depends(auth_manager.require_group("Administrators"))):
    return {"message": "Admin access granted"}

# Require a Microsoft application role
@app.get("/app-admin")
def app_admin_route(user: User = Depends(auth_manager.require_app_role("App.Admin"))):
    return {"message": "App admin access granted"}

# Require a database role
@app.get("/editor")
def editor_route(user: User = Depends(auth_manager.require_db_role("Editor"))):
    return {"message": "Editor access granted"}

# Require a permission
@app.get("/manage-users")
def manage_users(user: User = Depends(auth_manager.require_permission("user.manage"))):
    return {"message": "Can manage users"}

Alternative: Using Environment Variables (Backward Compatibility)

from fastapi import FastAPI, Depends
from core_infinity_stones_sso import (
    get_current_user,
    require_group,
    require_app_role,
    require_db_role,
    require_permission,
    auth_router,
    User,
)

app = FastAPI()
app.include_router(auth_router)  # Uses .env file or environment variables

@app.get("/protected")
def protected_route(user: User = Depends(get_current_user)):
    return {"message": f"Hello {user.email}!"}

# Require a Microsoft group
@app.get("/admin")
def admin_route(user: User = Depends(require_group("Administrators"))):
    return {"message": "Admin access granted"}

# Require an application role
@app.get("/app-admin")
def app_admin_route(user: User = Depends(require_app_role("App.Admin"))):
    return {"message": "App admin access granted"}

# Require a database role
@app.get("/editor")
def editor_route(user: User = Depends(require_db_role("Editor"))):
    return {"message": "Editor access granted"}

React Frontend Integration

See examples/react_frontend_example.md for complete React integration guide.

API Endpoints

Authentication Endpoints

  • GET /auth/login - Initiate Microsoft SSO login (redirects to Microsoft)
  • GET /auth/callback - OAuth callback handler (exchanges code for token)
  • POST /auth/refresh - Refresh access token using refresh token
  • POST /auth/logout - Logout endpoint
  • GET /auth/me - Get current authenticated user information

Usage Examples

Protecting Routes

from fastapi import Depends
from core_infinity_stones_sso import (
    get_current_user,
    require_group,
    require_app_role,
    require_db_role,
    require_permission,
    User,
)

# Require authentication
@app.get("/protected")
def protected(user: User = Depends(get_current_user)):
    return {
        "user": user.email,
        "groups": user.get_group_names(),  # Microsoft groups
        "app_roles": user.get_app_role_names(),  # Microsoft app roles
        "db_roles": user.get_db_role_names(),  # Database roles
        "permissions": user.get_permission_names(),  # Permissions
    }

# Require specific Microsoft group
@app.get("/admin")
def admin(user: User = Depends(require_group("Administrators"))):
    return {"message": "Admin only"}

# Require specific Microsoft application role
@app.get("/app-admin")
def app_admin(user: User = Depends(require_app_role("App.Admin"))):
    return {"message": "App admin only"}

# Require specific database role
@app.get("/editor")
def editor(user: User = Depends(require_db_role("Editor"))):
    return {"message": "Editor only"}

# Require specific permission
@app.get("/manage-users")
def manage_users(user: User = Depends(require_permission("user.manage"))):
    return {"message": "Can manage users"}

# Check groups, app_roles, db_roles, or permissions separately
if user.has_group("Managers"):  # Check group membership
    pass
if user.has_app_role("App.Admin"):  # Check application role
    pass
if user.has_db_role("Editor"):  # Check database role
    pass
if user.has_permission("user.manage"):  # Check permission
    pass

Token Refresh

The library supports token refresh using Microsoft refresh tokens:

# After login, you'll receive a refresh_token in the response
# Use it to refresh the access token:

POST /auth/refresh
{
    "refresh_token": "your_refresh_token_here"
}

# Response:
{
    "access_token": "new_jwt_token",
    "token_type": "bearer",
    "user": {...},
    "refresh_token": "new_refresh_token_if_provided"
}

How Authorization Works

The library supports three separate authorization mechanisms:

1. Microsoft Azure AD Groups

  • User group memberships are fetched from Microsoft Graph API
  • Stored in JWT token (not in database)
  • Always up-to-date with Azure AD
  • Use require_group() or user.has_group()

2. Microsoft Application Roles

  • Application roles defined in your Azure AD app registration
  • Returned in ID token during login
  • Stored in JWT token (not in database)
  • Use require_app_role() or user.has_app_role()

3. Database Roles and Permissions

  • Custom roles stored in your PostgreSQL database
  • Roles have permissions
  • Hierarchical: User → Role → Permission
  • Stored in JWT token for performance
  • Use require_db_role() or require_permission()

Key Features:

  • Hybrid Approach: Combine Microsoft groups/app_roles with database roles
  • Token-Based: All authorization info stored in JWT token
  • Always Current: Microsoft groups/app_roles refreshed on login, database roles loaded from DB
  • Separate Checks: Check each type independently - no confusion
  • Flexible: Use whichever authorization method fits your needs

To use Microsoft groups:

  1. Create groups in Azure AD (e.g., "Administrators", "Managers", "Employees")
  2. Add users to these groups in Azure AD
  3. Use require_group("Administrators") in your code

To use Microsoft application roles:

  1. Go to Azure Portal > App Registrations > Your App > App roles
  2. Create application roles (e.g., "App.Admin", "App.User", "App.Manager")
  3. Assign these roles to users or groups in Azure AD
  4. Use require_app_role("App.Admin") in your code

To use database roles:

  1. Create roles and permissions in your database (see examples/setup_database.py)
  2. Assign roles to users via the user_roles table
  3. Use require_db_role("Editor") in your code

Database Migrations in Downstream Apps

The library supports selective model inclusion for downstream apps. Each app can choose which tables it needs:

  • App 1 (User only): Only needs the users table
  • App 2 (User + RBAC): Needs users, roles, permissions, and association tables

Setting Up Alembic in Your Downstream App

  1. Install Alembic in your app:
poetry add alembic
# or
pip install alembic
  1. Initialize Alembic:
cd your_app
alembic init alembic
  1. Configure alembic/env.py:

For App 1 (User table only):

# alembic/env.py
from core_infinity_stones_sso.models import get_library_metadata

# Get metadata for User table only
target_metadata = get_library_metadata(include_rbac=False)

# If you have your own app models, combine them:
# from sqlalchemy import MetaData
# from your_app.models import Base as AppBase
# combined_metadata = MetaData()
# get_library_metadata(include_rbac=False).tometadata(combined_metadata)
# AppBase.metadata.tometadata(combined_metadata)
# target_metadata = combined_metadata

For App 2 (User + RBAC tables):

# alembic/env.py
from core_infinity_stones_sso.models import get_library_metadata

# Get metadata for User + Role + Permission tables
target_metadata = get_library_metadata(include_rbac=True)

# If you have your own app models, combine them:
# from sqlalchemy import MetaData
# from your_app.models import Base as AppBase
# combined_metadata = MetaData()
# get_library_metadata(include_rbac=True).tometadata(combined_metadata)
# AppBase.metadata.tometadata(combined_metadata)
# target_metadata = combined_metadata
  1. Set your database URL in alembic.ini or alembic/env.py:
# In alembic/env.py, before target_metadata:
config.set_main_option("sqlalchemy.url", "postgresql://user:pass@localhost/dbname")
  1. Create and run migrations:
# Create initial migration
alembic revision --autogenerate -m "Initial migration"

# Apply migrations
alembic upgrade head

Example Alembic Configurations

See examples/alembic_env_user_only.py for a complete example of App 1 setup, and examples/alembic_env_with_rbac.py for App 2 setup.

Combining Library Models with Your App Models

If your app has its own models, you can combine them with the library models:

# alembic/env.py
from sqlalchemy import MetaData
from core_infinity_stones_sso.models import get_library_metadata
from your_app.models import Base as AppBase

# Get library metadata
library_metadata = get_library_metadata(include_rbac=True)  # or False

# Combine with your app's metadata
combined_metadata = MetaData()
library_metadata.tometadata(combined_metadata)
AppBase.metadata.tometadata(combined_metadata)

target_metadata = combined_metadata

Project Structure

core_infinity_stones_sso/
├── __init__.py           # Package exports
├── config.py             # Configuration settings
├── models.py             # Database models (User, Role, Permission)
├── database.py           # Database connection and session management
├── auth.py               # Core Microsoft SSO authentication logic
└── fastapi_integration.py # FastAPI dependencies and router

examples/
├── basic_usage.py        # Basic FastAPI integration example
├── basic_usage_with_init.py # Example with class initializers
├── setup_database.py     # Database initialization script
├── alembic_env_user_only.py # Alembic config for User-only apps
├── alembic_env_with_rbac.py # Alembic config for apps with RBAC
└── react_frontend_example.md # React integration guide

alembic/                  # Database migrations (for library development only)

Development

Running Tests

poetry run pytest

Code Formatting

poetry run black .
poetry run ruff check .

License

Private library for internal use only.

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

core_infinity_stones_sso-0.1.0.tar.gz (17.6 kB view details)

Uploaded Source

Built Distribution

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

core_infinity_stones_sso-0.1.0-py3-none-any.whl (22.9 kB view details)

Uploaded Python 3

File details

Details for the file core_infinity_stones_sso-0.1.0.tar.gz.

File metadata

  • Download URL: core_infinity_stones_sso-0.1.0.tar.gz
  • Upload date:
  • Size: 17.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.2.1 CPython/3.12.10 Windows/11

File hashes

Hashes for core_infinity_stones_sso-0.1.0.tar.gz
Algorithm Hash digest
SHA256 b629d61f546806ce4a931a0169e025ea00af9fd6e8624c14ea1ce19f693fe2a9
MD5 ba00a907847ab7d461f9eae6407e67ec
BLAKE2b-256 ae0265275f08f4fd05e08780f7c6aafd235f03e649a5627c8087b0e77814bde8

See more details on using hashes here.

File details

Details for the file core_infinity_stones_sso-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for core_infinity_stones_sso-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 18751a37df4f74f0ab24e4aeb1afcc334d5933809dfe24921c0bb1d45e3f51d1
MD5 0d25e853290261aaa9fb3a534e7ca550
BLAKE2b-256 7b20c2918028854c91387c615400c357bca0e8101d3fd9b75b78417c29e70c81

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