Skip to main content

Django client for Onehux Accounts SSO/SLO integration

Project description

Onehux SSO Client for Django

Official Django client library for integrating with Onehux Accounts - A production-ready OAuth2/OIDC Identity Provider with SSO/SLO support.

Features

  • OAuth2/OIDC Authentication - Complete authorization code flow with PKCE
  • Single Sign-On (SSO) - Seamless login across multiple applications
  • Single Logout (SLO) - Logout from all connected applications
  • Token Management - Automatic token refresh and validation
  • User Synchronization - Real-time user profile updates via webhooks
  • Django Integration - Middleware, authentication backend, and decorators
  • Type Hints - Full type annotations for better IDE support
  • Production Ready - Battle-tested and secure

Quick Start

1. Install the package

pip install onehux-sso-client

2. Configure settings.py

INSTALLED_APPS = [
    'onehux_sso_client',
    'accounts',  # Your app with User model
    ...
]

AUTH_USER_MODEL = 'accounts.User'

3. Create your User model

You have four ways you can create your user models to work with this package

Option 1: Mixin (Recommended)

# myproject/models.py
from django.contrib.auth.models import AbstractUser
from onehux_sso_client.models import OnehuxSSOUserMixin

class User(OnehuxSSOUserMixin, AbstractUser):
    """Custom user with SSO support + your own fields"""
    department = models.CharField(max_length=100)
    employee_id = models.CharField(max_length=20)
    
    class Meta:
        db_table = 'users'

Option 2: Minimal Integration For users who just want SSO without profile sync:

from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    onehux_user_id = models.UUIDField(unique=True, null=True)
    # You handle profile sync yourself

Option 3: Use management command (easiest)

python manage.py onehux_init --app=accounts

Option 4: Copy template manually

Download the user_model_template.py and save it as accounts/models.py.

Option 5: Copy from installed package

cp $(python -c "import onehux_sso_client; print(onehux_sso_client.__path__[0])")/templates/user_model_template.py accounts/models.py

4. Run migrations

python manage.py makemigrations
python manage.py migrate

5. Add onehux configuration to your settings.py

# Onehux SSO Configuration
ONEHUX_SSO = {
    'CLIENT_ID': env('CLIENT_ID'),
    'CLIENT_SECRET': env('CLIENT_SECRET'),
    'REDIRECT_URI': env('REDIRECT_URI'),  # http://client.onehux.com/sso/callback/
    
    # IdP Endpoints
    'AUTHORIZATION_URL': env('AUTHORIZATION_URL'),  # http://accounts.onehux.com/sso/authorize/
    'TOKEN_URL': env('TOKEN_URL'),  # http://accounts.onehux.com/sso/token/
    'USERINFO_URL': env('USERINFO_URL'),  # http://accounts.onehux.com/sso/userinfo/
    'LOGOUT_URL': env('LOGOUT_URL'),  # http://accounts.onehux.com/sso/logout/
    'JWKS_URL': env('JWKS_URL'),  # http://accounts.onehux.com/sso/.well-known/jwks.json
    
    # Security
    'USE_PKCE': True,  # Highly recommended
    'VERIFY_SSL': False,  # Set to True in production
    
    # Scopes
    'SCOPES': 'openid profile email',
    
    # Token Management
    'TOKEN_REFRESH_THRESHOLD': 300,  # Refresh 5 minutes before expiry
    
    # Webhook
    'WEBHOOK_SECRET': env('WEBHOOK_SECRET'),
    'WEBHOOK_ENDPOINT': '/sso/api/webhooks/onehux/',
}

# ============================================================================
# PUBLIC PATHS CONFIGURATION (CRITICAL FOR SAAS)
# ============================================================================
# Define which paths are accessible WITHOUT authentication
# The SilentSSOMiddleware will NOT run on these paths

SSO_PUBLIC_PATHS = [
    # Core Public Pages
    '/',                    # Homepage
    '/about/',
    '/pricing/',
    '/features/',
    '/contact/',
    '/faq/',
    
    # Content Pages
    '/blog/',
    '/docs/',
    '/help/',
    '/support/',
    
    # Legal Pages
    '/legal/',
    '/privacy/',
    '/terms/',
    '/cookies/',
    
    # Authentication Pages (if you have native login)
    '/signup/',
    '/login/',
    '/forgot-password/',
    '/reset-password/',
    
    # Public API Endpoints
    '/api/public/',
    '/api/docs/',
    
    # Health Checks
    '/health/',
    '/status/',
]

# ============================================================================
# SILENT SSO CONFIGURATION
# ============================================================================

# Enable/disable silent SSO (auto-login if IdP session exists)
SSO_SILENT_AUTH_ENABLED = True

# Optional: Additional paths to ignore for silent SSO (beyond public paths)
# Use this for monitoring endpoints, admin pages, etc.
SSO_SILENT_AUTH_IGNORED_PATHS = [
    '/health/',
    '/metrics/',
    '/monitoring/',
]

# ============================================================================
# TOKEN REFRESH CONFIGURATION
# ============================================================================

# Paths to exclude from token refresh checks
SSO_TOKEN_REFRESH_EXCLUDED_PATHS = [
    '/health/',
    '/metrics/',
    '/monitoring/',
]

# ============================================================================
# MIDDLEWARE CONFIGURATION
# ============================================================================

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    
    # =========================================================================
    # ONEHUX SSO MIDDLEWARES (Order matters!)
    # =========================================================================
    # 1. Silent SSO - tries to auto-login anonymous users on protected pages
    'onehux_sso_client.middleware.SilentSSOMiddleware',
    
    # 2. Token Refresh - auto-refreshes expired tokens for authenticated users
    'onehux_sso_client.middleware.SSOTokenRefreshMiddleware',
    
    # 3. Rate Limiting (Optional)
    # 'onehux_sso_client.middleware.SSORateLimitMiddleware',
]

# ============================================================================
# AUTHENTICATION CONFIGURATION
# ============================================================================

# Custom authentication backend for SSO
AUTHENTICATION_BACKENDS = [
    'onehux_sso_client.backends.OnehuxSSOBackend',  # SSO authentication
    'django.contrib.auth.backends.ModelBackend',     # Fallback (superuser)
]

# Custom user model (if using OnehuxSSOUserMixin)
AUTH_USER_MODEL = 'accounts.User'

# Login/Logout URLs

# Where `login_required` / `@login_required` should redirect
# when an anonymous user hits a protected page
LOGIN_URL = 'sso:sso_login'  # Redirect to SSO login

# Where to send the user after they click "log out"
LOGOUT_REDIRECT_URL = '/'

# Where to send the user after a *successful* login
LOGIN_REDIRECT_URL = 'accounts:dashboard'          # or '/dashboard/'

# Session Configuration
SESSION_COOKIE_AGE = 86400  # 24 hours
SESSION_SAVE_EVERY_REQUEST = False
SESSION_COOKIE_SECURE = False  # Set to True in production (HTTPS only)
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'

# ============================================================================
# ADMIN CONFIGURATION
# ============================================================================

# Custom admin URL (for security)
ADMIN_URL = env('ADMIN_URL', default='admin')
ADMIN_LOGIN_PATH = f'/{ADMIN_URL}/'

6. Add url pattern to your project main urls.py

from django.urls import path, include

urlpatterns = [
    # ...
    
    # =========================================================================
    # SSO ENDPOINTS / URLS
    # =========================================================================
    path('sso/', include('onehux_sso_client.urls', namespace='sso')),
    
    # ...
]

7. Protecting Views in Service Providers

Function-Based Views (FBV)

Use decorators to protect function-based views:

from onehux_sso_client.decorators import (
    sso_login_required,      # Basic SSO auth
    require_sso_role,        # Specific role
    require_any_role,        # Multiple roles
    require_all_roles,       # All roles required
    require_sso_permission,  # Permission-based
)

# Basic SSO protection
@sso_login_required
def dashboard(request):
    return render(request, 'dashboard.html')

# Only owners can access
@require_sso_role('owner')
def owner_panel(request):
    return render(request, 'owner_panel.html')

# Owners OR admins can access
@require_any_role('owner', 'admin')
def management(request):
    return render(request, 'management.html')

# Must have BOTH roles
@require_all_roles('admin', 'developer')
def admin_dev_tools(request):
    return render(request, 'admin_dev.html')

# Permission-based protection
@require_sso_permission('can_delete_posts')
def delete_post(request, post_id):
    post = get_object_or_404(Post, id=post_id)
    post.delete()
    return redirect('posts:list')

Class-Based Views (CBV)

For class-based views, use the provided mixins instead of decorators:

from django.views.generic import ListView, CreateView, UpdateView, DeleteView, TemplateView
from onehux_sso_client.mixins import (
    SSOLoginRequiredMixin,         # Basic SSO authentication
    SSORequireRoleMixin,           # Single role requirement
    SSORequireAnyRoleMixin,        # Multiple role options
    SSORequireAllRolesMixin,       # All roles required
    SSORequirePermissionMixin,     # Permission-based
    APISSOLoginRequiredMixin,      # For API views
)

Basic Authentication Protection:

from django.views.generic import TemplateView, CreateView
from onehux_sso_client.mixins import SSOLoginRequiredMixin

# Simple protected view
class DashboardView(SSOLoginRequiredMixin, TemplateView):
    template_name = 'dashboard.html'

# Protected create view
class ArticleCreateView(SSOLoginRequiredMixin, CreateView):
    model = Article
    fields = ['title', 'content']
    template_name = 'article_form.html'
    
    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)

Single Role Protection:

from onehux_sso_client.mixins import SSORequireRoleMixin, SSOLoginRequiredMixin

class AdminDashboardView(SSORequireRoleMixin, SSOLoginRequiredMixin, TemplateView):
    """Only users with 'admin' role can access"""
    template_name = 'admin/dashboard.html'
    required_role = 'admin'  # ← Set required role

class ManagerPanelView(SSORequireRoleMixin, SSOLoginRequiredMixin, ListView):
    """Only managers can view this list"""
    model = Report
    template_name = 'manager/reports.html'
    required_role = 'manager'
    paginate_by = 25

Multiple Role Options (Any Role):

from onehux_sso_client.mixins import SSORequireAnyRoleMixin, SSOLoginRequiredMixin

class ContentModerationView(SSORequireAnyRoleMixin, SSOLoginRequiredMixin, ListView):
    """Accessible by admin, moderator, or editor"""
    model = Content
    template_name = 'moderation/content_list.html'
    required_roles = ['admin', 'moderator', 'editor']  # ← User needs ANY of these
    
    def get_queryset(self):
        return Content.objects.filter(status='pending')

Multiple Roles Required (All Roles):

from onehux_sso_client.mixins import SSORequireAllRolesMixin, SSOLoginRequiredMixin

class AdminDeveloperView(SSORequireAllRolesMixin, SSOLoginRequiredMixin, TemplateView):
    """User must have BOTH admin AND developer roles"""
    template_name = 'admin_dev/tools.html'
    required_roles = ['admin', 'developer']  # ← User needs ALL of these

Permission-Based Protection:

from onehux_sso_client.mixins import SSORequirePermissionMixin, SSOLoginRequiredMixin

class DeleteUserView(SSORequirePermissionMixin, SSOLoginRequiredMixin, DeleteView):
    """Only users with 'can_delete_users' permission can delete"""
    model = User
    template_name = 'users/confirm_delete.html'
    required_permission = 'can_delete_users'  # ← Set required permission
    success_url = '/users/'

Combining Multiple Protections:

from onehux_sso_client.mixins import (
    SSORequireRoleMixin,
    SSORequirePermissionMixin,
    SSOLoginRequiredMixin
)

class FeaturedContentUpdateView(
    SSORequireRoleMixin,           # Must have 'admin' role
    SSORequirePermissionMixin,      # AND 'can_feature_content' permission
    SSOLoginRequiredMixin,          # AND be authenticated
    UpdateView
):
    """Requires both specific role AND permission"""
    model = Content
    template_name = 'content/feature_form.html'
    required_role = 'admin'
    required_permission = 'can_feature_content'
    fields = ['featured', 'featured_until']

API Views (JSON Response):

from django.views import View
from django.http import JsonResponse
from onehux_sso_client.mixins import APISSOLoginRequiredMixin

class APIDataView(APISSOLoginRequiredMixin, View):
    """
    API endpoint that returns JSON errors instead of redirects
    Returns 401 with JSON error if not authenticated
    """
    
    def get(self, request):
        data = {
            'user': request.user.username,
            'email': request.user.email,
            'data': 'sensitive information'
        }
        return JsonResponse(data)

class APIStatsView(APISSOLoginRequiredMixin, View):
    """Protected API endpoint"""
    
    def get(self, request):
        stats = {
            'total_users': User.objects.count(),
            'active_sessions': SSOSession.objects.filter(is_active=True).count()
        }
        return JsonResponse(stats)

Real-World Example - E-commerce:

from django.views.generic import ListView, CreateView, UpdateView, DeleteView
from onehux_sso_client.mixins import (
    SSOLoginRequiredMixin,
    SSORequireRoleMixin,
    SSORequireAnyRoleMixin,
)

# Customer can view their orders
class MyOrdersView(SSOLoginRequiredMixin, ListView):
    model = Order
    template_name = 'orders/my_orders.html'
    
    def get_queryset(self):
        return Order.objects.filter(customer=self.request.user)

# Admin can view all orders
class AllOrdersView(SSORequireRoleMixin, SSOLoginRequiredMixin, ListView):
    model = Order
    template_name = 'admin/all_orders.html'
    required_role = 'admin'
    paginate_by = 50

# Admin or warehouse staff can update order status
class UpdateOrderStatusView(SSORequireAnyRoleMixin, SSOLoginRequiredMixin, UpdateView):
    model = Order
    template_name = 'orders/update_status.html'
    required_roles = ['admin', 'warehouse_staff']
    fields = ['status', 'tracking_number']

# Only admin can delete orders
class DeleteOrderView(SSORequireRoleMixin, SSOLoginRequiredMixin, DeleteView):
    model = Order
    template_name = 'orders/confirm_delete.html'
    required_role = 'admin'
    success_url = '/orders/'

Important Notes

  1. Mixin Order Matters - Always place SSOLoginRequiredMixin last (rightmost) in the inheritance chain:
   # ✅ CORRECT - SSOLoginRequiredMixin runs first
   class MyView(SSORequireRoleMixin, SSOLoginRequiredMixin, TemplateView):
       pass
   
   # ❌ WRONG - Auth check won't run first
   class MyView(SSOLoginRequiredMixin, SSORequireRoleMixin, TemplateView):
       pass
  1. Set Required Attributes - Don't forget to set required_role, required_roles, or required_permission:
   # ✅ CORRECT
   class AdminView(SSORequireRoleMixin, SSOLoginRequiredMixin, TemplateView):
       required_role = 'admin'
   
   # ❌ WRONG - Will raise ValueError
   class AdminView(SSORequireRoleMixin, SSOLoginRequiredMixin, TemplateView):
       pass  # Missing required_role!
  1. Custom 403 Template - Create templates/onehux_sso_client/403.html for unauthorized access pages:
   <!-- templates/onehux_sso_client/403.html -->
   {% extends 'base.html' %}
   
   {% block content %}
   <div class="error-page">
       <h1>403 - Access Denied</h1>
       <p>You don't have permission to access this page.</p>
       {% if required_role %}
           <p>Required role: <strong>{{ required_role }}</strong></p>
       {% endif %}
       {% if required_roles %}
           <p>Required roles: <strong>{{ required_roles|join:", " }}</strong></p>
       {% endif %}
       <p>Your roles: {{ user_roles|join:", "|default:"None" }}</p>
       <a href="{% url 'home' %}">Go Home</a>
   </div>
   {% endblock %}
  1. Alternative: Method Decorator - If you prefer using decorators on CBVs (not recommended):
   from django.utils.decorators import method_decorator
   from onehux_sso_client.decorators import sso_login_required, require_sso_role
   
   @method_decorator(sso_login_required, name='dispatch')
   class MyView(TemplateView):
       template_name = 'my_template.html'
   
   # Multiple decorators
   decorators = [sso_login_required, require_sso_role('admin')]
   
   @method_decorator(decorators, name='dispatch')
   class AdminView(TemplateView):
       template_name = 'admin.html'

However, using mixins (shown above) is the recommended Django approach.

Advanced Usage

Manual OAuth2 Flow

from onehux_sso_client import OnehuxClient

# Initialize client
client = OnehuxClient(
    client_id='your-client-id',
    client_secret='your-client-secret',
    redirect_uri='http://yourapp.com/oauth/callback',
    onehux_base_url='https://accounts.onehux.com'
)

# 1. Generate authorization URL
auth_url, state, code_verifier = client.get_authorization_url(
    scopes=['openid', 'profile', 'email']
)

# Store state and code_verifier in session, then redirect user to auth_url

# 2. Exchange authorization code for tokens (in callback view)
tokens = client.exchange_code_for_tokens(
    code='authorization-code',
    code_verifier=code_verifier
)

# 3. Get user information
user_info = client.get_user_info(tokens['access_token'])

# 4. Verify ID token (optional)
id_token_payload = client.verify_id_token(tokens['id_token'])

Token Refresh

# Refresh an expired access token
new_tokens = client.refresh_access_token(refresh_token)

Webhook Handling

Onehux sends webhooks for user events (login, logout, profile updates). The client automatically handles these via the onehux_webhook view.

Webhook Events:

  • user.login - User logged into your application
  • user.logout - User logged out (global SLO)
  • user.updated - User profile was updated

Webhook Payload Example:

{
  "event": "user.updated",
  "timestamp": "2024-01-15T12:00:00Z",
  "user": {
    "sub": "user-uuid",
    "email": "user@example.com",
    "name": "John Doe",
    "given_name": "John",
    "family_name": "Doe",
    "profile_version": 5
  }
}

Configuration Options

Setting Required Description
ONEHUX_CLIENT_ID Yes Your application's client ID from Onehux
ONEHUX_CLIENT_SECRET Yes Your application's client secret
ONEHUX_REDIRECT_URI Yes OAuth2 callback URL
ONEHUX_BASE_URL No Onehux platform URL (default: https://accounts.onehux.com)
ONEHUX_WEBHOOK_SECRET No Secret for webhook signature verification
ONEHUX_LOGIN_URL No Custom login URL (default: /oauth/login/)

Security Features

  • PKCE (Proof Key for Code Exchange) - Protects against authorization code interception
  • State Parameter - CSRF protection for OAuth2 flow
  • Webhook Signature Verification - HMAC-SHA256 validation
  • Token Validation - JWT signature verification with RSA keys
  • Secure Token Storage - Session-based token management

Development

# Clone the repository
git clone https://github.com/programmerisaac/onehux-sso-client.git
cd onehux-sso-client

# Install development dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Format code
black onehux_sso_client/

# Lint code
flake8 onehux_sso_client/

Example Application

See the example-service-provider directory for a complete Django application demonstrating SSO/SLO integration.

Support

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please read our Contributing Guidelines first.

Changelog

2.0.0 (2026-01-01)

  • Bug fixes
  • Replace pkg_resources with modern importlib.metadata
  • Added Mixins for Class Base Views

1.0.0 (2025-12-18)

  • Bug fixes
  • Improved token validation
  • Better error handling
  • Single Logout bug fix
  • Properly synchronize user data across service providers

0.1.0 (2025-12-15)

  • Initial release
  • OAuth2/OIDC authentication with PKCE

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

onehux_sso_client-2.0.0.tar.gz (56.5 kB view details)

Uploaded Source

Built Distribution

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

onehux_sso_client-2.0.0-py3-none-any.whl (64.2 kB view details)

Uploaded Python 3

File details

Details for the file onehux_sso_client-2.0.0.tar.gz.

File metadata

  • Download URL: onehux_sso_client-2.0.0.tar.gz
  • Upload date:
  • Size: 56.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.8

File hashes

Hashes for onehux_sso_client-2.0.0.tar.gz
Algorithm Hash digest
SHA256 51cf627eb8fe85847fb5a2f95ea63c09cd4d36419a332a0dab5a8d224e5fd5d8
MD5 752297b0d79c9c072802ed7586eb7498
BLAKE2b-256 6a4c26cf49355cf4bf8a508476a04d328be676ca23b6955dde1265d4ed8d2b4a

See more details on using hashes here.

File details

Details for the file onehux_sso_client-2.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for onehux_sso_client-2.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 00c85c2e618e4372c4271accdcc8f9c970ee6362bb7419fd529da75ae190cf29
MD5 f31f270e54d917fa6ebf7b1a562a4840
BLAKE2b-256 a8ff7bfed70f909132d8db8415608eb2925bc00642b72b50f5bac959b0f821b2

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