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
- Clone the repository and install dependencies:
poetry install
- Create a
.envfile (optional, only needed for migrations):
cp .env.example .env
- 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
- 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
- Go to Azure Portal
- Navigate to Azure Active Directory > App registrations
- Create a new registration:
- Name: Your app name
- Supported account types: Single tenant
- Redirect URI:
http://localhost:8000/auth/callback(or your production URL)
- After creation, note:
- Application (client) ID →
MICROSOFT_CLIENT_ID - Directory (tenant) ID →
MICROSOFT_TENANT_ID
- Application (client) ID →
- Go to Certificates & secrets and create a new client secret →
MICROSOFT_CLIENT_SECRET - Go to API permissions and add:
- Microsoft Graph > Delegated permissions:
User.ReadGroupMember.Read.All(to read user's group memberships)emailoffline_access(to get refresh tokens)
- Click Grant admin consent (if you have admin rights)
- Microsoft Graph > Delegated permissions:
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 tokenPOST /auth/logout- Logout endpointGET /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()oruser.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()oruser.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()orrequire_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:
- Create groups in Azure AD (e.g., "Administrators", "Managers", "Employees")
- Add users to these groups in Azure AD
- Use
require_group("Administrators")in your code
To use Microsoft application roles:
- Go to Azure Portal > App Registrations > Your App > App roles
- Create application roles (e.g., "App.Admin", "App.User", "App.Manager")
- Assign these roles to users or groups in Azure AD
- Use
require_app_role("App.Admin")in your code
To use database roles:
- Create roles and permissions in your database (see
examples/setup_database.py) - Assign roles to users via the
user_rolestable - 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
userstable - App 2 (User + RBAC): Needs
users,roles,permissions, and association tables
Setting Up Alembic in Your Downstream App
- Install Alembic in your app:
poetry add alembic
# or
pip install alembic
- Initialize Alembic:
cd your_app
alembic init alembic
- 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
- Set your database URL in
alembic.inioralembic/env.py:
# In alembic/env.py, before target_metadata:
config.set_main_option("sqlalchemy.url", "postgresql://user:pass@localhost/dbname")
- 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
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 core_infinity_stones_sso-0.1.1.tar.gz.
File metadata
- Download URL: core_infinity_stones_sso-0.1.1.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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f1ac03800c3b6089753bf8dc1e94a849a9af775c9a980a7a531504917c4d5508
|
|
| MD5 |
52057914ddda12945789192a16a12473
|
|
| BLAKE2b-256 |
0372ef7f82d23caf228180a162102ca19fd75040e31678ce5ed7ea234090232e
|
File details
Details for the file core_infinity_stones_sso-0.1.1-py3-none-any.whl.
File metadata
- Download URL: core_infinity_stones_sso-0.1.1-py3-none-any.whl
- Upload date:
- Size: 22.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.2.1 CPython/3.12.10 Windows/11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
caf1b66a831777a5e1f1ef567b07c72d346aa9681e553f8cd9d364af4423bac2
|
|
| MD5 |
a7ee54825c17c89f341b26351a491ddf
|
|
| BLAKE2b-256 |
6d8a5d6dc675e9346b40ebebdd11b87b667cdcb257a4e01c56b989f154d7a322
|