Skip to content
Back to skills

Rfc Multitenancy Architecture

ASecurity

Comprehensive multi-tenancy architecture guide using RFC standards for identity, authorization, and provisioning. Use when implementing or validating rfc multitenancy architecture protocol compliance.

  • 9 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added September 8, 2026
securitypythongofastapiapisecurity

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add anubhavg-icpl/vibe --skill rfc-multitenancy-architecture --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Rfc Multitenancy Architecture?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Rfc Multitenancy Architecture
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/anubhavg-icpl-rfc-multitenancy-architecture/badge)](https://www.skillsdirectory.com/skills/anubhavg-icpl-rfc-multitenancy-architecture)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: rfc-multitenancy-architecture
description: Comprehensive multi-tenancy architecture guide using RFC standards for identity, authorization, and provisioning. Use when implementing or validating rfc multitenancy architecture protocol compliance.
license: CC-BY-NC-SA-4.0
metadata:
  risk: unknown
  source: community
  kind: mode
  category: rfc
  tags: [multi-tenancy, architecture, oauth, scim, jwt, identity]
---

# Multi-Tenancy RFC Architecture

You are an expert in designing multi-tenant identity and access management systems using IETF RFC standards. You implement secure tenant isolation, identity federation, and standards-compliant authorization.

## Architecture Overview

```text
┌─────────────────────────────────────────────────────────────────────────────┐
│                     Multi-Tenant Identity Platform                           │
│                                                                              │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │                         Tenant Layer                                    │ │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐                 │ │
│  │  │   Tenant A   │  │   Tenant B   │  │   Tenant C   │                 │ │
│  │  │  acme.auth   │  │  corp.auth   │  │  demo.auth   │                 │ │
│  │  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘                 │ │
│  └─────────┼─────────────────┼─────────────────┼─────────────────────────┘ │
│            │                 │                 │                            │
│  ┌─────────┴─────────────────┴─────────────────┴─────────────────────────┐ │
│  │                    Authorization Layer (OAuth 2.0)                     │ │
│  │                                                                        │ │
│  │  ┌────────────┐  ┌────────────┐  ┌────────────┐  ┌────────────┐      │ │
│  │  │ RFC 6749   │  │ RFC 7636   │  │ RFC 9449   │  │ RFC 8705   │      │ │
│  │  │ OAuth 2.0  │  │   PKCE     │  │   DPoP     │  │   mTLS     │      │ │
│  │  └────────────┘  └────────────┘  └────────────┘  └────────────┘      │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                                                                              │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │                      Token Layer (JOSE)                                 │ │
│  │                                                                        │ │
│  │  ┌────────────┐  ┌────────────┐  ┌────────────┐  ┌────────────┐      │ │
│  │  │ RFC 7519   │  │ RFC 7515   │  │ RFC 7517   │  │ RFC 9068   │      │ │
│  │  │    JWT     │  │    JWS     │  │    JWK     │  │ JWT AT     │      │ │
│  │  └────────────┘  └────────────┘  └────────────┘  └────────────┘      │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                                                                              │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │                   Provisioning Layer (SCIM)                             │ │
│  │                                                                        │ │
│  │  ┌────────────┐  ┌────────────┐  ┌────────────┐                       │ │
│  │  │ RFC 7643   │  │ RFC 7644   │  │ RFC 7642   │                       │ │
│  │  │  Schema    │  │  Protocol  │  │ Definitions│                       │ │
│  │  └────────────┘  └────────────┘  └────────────┘                       │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
```

## RFC Standards by Layer

### Authorization Layer

| RFC      | Title        | Multi-Tenancy Use               |
| -------- | ------------ | ------------------------------- |
| RFC 6749 | OAuth 2.0    | Tenant-scoped authorization     |
| RFC 6750 | Bearer Token | Tenant-bound token transmission |
| RFC 7636 | PKCE         | Security for all clients        |
| RFC 9449 | DPoP         | Sender-constrained tokens       |
| RFC 8705 | mTLS         | Certificate-based tenant auth   |
| RFC 9126 | PAR          | Secure request submission       |
| RFC 9207 | Issuer ID    | Prevent tenant mix-up attacks   |

### Token Layer

| RFC      | Title          | Multi-Tenancy Use          |
| -------- | -------------- | -------------------------- |
| RFC 7519 | JWT            | Tenant claims in tokens    |
| RFC 7515 | JWS            | Per-tenant signing keys    |
| RFC 7516 | JWE            | Tenant-specific encryption |
| RFC 7517 | JWK            | Tenant key management      |
| RFC 9068 | JWT AT Profile | Standardized access tokens |

### Provisioning Layer

| RFC      | Title         | Multi-Tenancy Use            |
| -------- | ------------- | ---------------------------- |
| RFC 7643 | SCIM Schema   | Tenant-scoped user resources |
| RFC 7644 | SCIM Protocol | Tenant-isolated provisioning |

## Implementation

### Tenant Configuration

```python
from dataclasses import dataclass, field
from typing import Optional, List, Dict
from enum import Enum

class TenantIsolationModel(Enum):
    SILO = "silo"          # Separate infrastructure per tenant
    POOL = "pool"          # Shared infrastructure, logical separation
    BRIDGE = "bridge"      # Hybrid approach

@dataclass
class TenantSecurityConfig:
    """Security configuration per tenant."""
    # OAuth/OIDC settings
    issuer: str
    authorization_endpoint: str
    token_endpoint: str
    jwks_uri: str

    # Token settings
    access_token_lifetime: int = 3600
    refresh_token_lifetime: int = 86400 * 30
    id_token_lifetime: int = 3600

    # Security requirements
    require_pkce: bool = True
    require_dpop: bool = False
    require_mtls: bool = False

    # Allowed grant types
    allowed_grant_types: List[str] = field(default_factory=lambda: [
        "authorization_code",
        "refresh_token",
        "client_credentials",
    ])

    # Token binding
    token_binding: str = "none"  # "none", "dpop", "mtls"


@dataclass
class TenantProvisioningConfig:
    """SCIM provisioning configuration per tenant."""
    scim_endpoint: str
    supported_schemas: List[str] = field(default_factory=lambda: [
        "urn:ietf:params:scim:schemas:core:2.0:User",
        "urn:ietf:params:scim:schemas:core:2.0:Group",
        "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User",
    ])
    max_results: int = 1000
    bulk_supported: bool = True
    bulk_max_operations: int = 1000


@dataclass
class Tenant:
    """Complete tenant configuration."""
    id: str
    name: str
    domain: str
    isolation_model: TenantIsolationModel

    security: TenantSecurityConfig
    provisioning: TenantProvisioningConfig

    # Tenant metadata
    created_at: str
    status: str = "active"

    # Custom attributes
    metadata: Dict[str, any] = field(default_factory=dict)
```

### Multi-Tenant Authorization Server

```python
from abc import ABC, abstractmethod
import secrets
import hashlib
from datetime import datetime, timedelta

class MultiTenantAuthorizationServer:
    """
    OAuth 2.0 Authorization Server with multi-tenancy.

    Implements:
    - RFC 6749: OAuth 2.0 Authorization Framework
    - RFC 7636: PKCE
    - RFC 9449: DPoP
    - RFC 9207: Issuer Identification
    """

    def __init__(self):
        self.tenants: Dict[str, Tenant] = {}
        self.tenant_stores: Dict[str, TenantDataStore] = {}

    def register_tenant(self, tenant: Tenant) -> None:
        """Register a new tenant."""
        self.tenants[tenant.id] = tenant
        self.tenant_stores[tenant.id] = TenantDataStore(tenant.id)

    def get_tenant(self, tenant_id: str) -> Optional[Tenant]:
        """Get tenant by ID."""
        return self.tenants.get(tenant_id)

    def get_tenant_by_domain(self, domain: str) -> Optional[Tenant]:
        """Get tenant by domain (for subdomain routing)."""
        for tenant in self.tenants.values():
            if tenant.domain == domain:
                return tenant
        return None

    def authorize(
        self,
        tenant_id: str,
        client_id: str,
        redirect_uri: str,
        response_type: str,
        scope: str,
        state: str,
        code_challenge: Optional[str],
        code_challenge_method: Optional[str],
        user_id: str,
    ) -> str:
        """
        Authorization endpoint with tenant context.

        RFC 6749 Section 4.1 + RFC 7636 PKCE
        """
        tenant = self.get_tenant(tenant_id)
        if not tenant:
            raise TenantNotFoundError(tenant_id)

        store = self.tenant_stores[tenant_id]

        # Validate client belongs to tenant
        client = store.get_client(client_id)
        if not client:
            raise InvalidClientError("Unknown client")

        # Enforce PKCE if required
        if tenant.security.require_pkce and not code_challenge:
            raise InvalidRequestError("PKCE required")

        # Generate authorization code
        code = secrets.token_urlsafe(32)

        store.save_authorization_code(
            code=code,
            client_id=client_id,
            redirect_uri=redirect_uri,
            scope=scope,
            user_id=user_id,
            code_challenge=code_challenge,
            code_challenge_method=code_challenge_method,
            expires_at=datetime.utcnow() + timedelta(minutes=10),
        )

        # Include issuer for RFC 9207
        params = {
            "code": code,
            "state": state,
            "iss": tenant.security.issuer,
        }

        return f"{redirect_uri}?{urlencode(params)}"

    def token(
        self,
        tenant_id: str,
        grant_type: str,
        client_id: str,
        client_secret: Optional[str] = None,
        code: Optional[str] = None,
        redirect_uri: Optional[str] = None,
        code_verifier: Optional[str] = None,
        refresh_token: Optional[str] = None,
        dpop_proof: Optional[str] = None,
    ) -> dict:
        """
        Token endpoint with tenant context.

        RFC 6749 + RFC 7636 + RFC 9449
        """
        tenant = self.get_tenant(tenant_id)
        if not tenant:
            raise TenantNotFoundError(tenant_id)

        store = self.tenant_stores[tenant_id]

        # Validate DPoP if required
        dpop_jkt = None
        if tenant.security.require_dpop:
            if not dpop_proof:
                raise InvalidRequestError("DPoP proof required")
            dpop_result = self._validate_dpop(dpop_proof, "POST", f"{tenant.security.token_endpoint}")
            if not dpop_result.valid:
                raise InvalidRequestError(f"Invalid DPoP: {dpop_result.error}")
            dpop_jkt = dpop_result.jkt

        # Handle grant type
        if grant_type == "authorization_code":
            return self._handle_authorization_code(
                tenant, store, client_id, client_secret,
                code, redirect_uri, code_verifier, dpop_jkt
            )
        elif grant_type == "refresh_token":
            return self._handle_refresh_token(
                tenant, store, client_id, refresh_token, dpop_jkt
            )
        elif grant_type == "client_credentials":
            return self._handle_client_credentials(
                tenant, store, client_id, client_secret, dpop_jkt
            )
        else:
            raise UnsupportedGrantTypeError(grant_type)

    def _issue_tokens(
        self,
        tenant: Tenant,
        client_id: str,
        user_id: Optional[str],
        scope: str,
        dpop_jkt: Optional[str] = None,
    ) -> dict:
        """Issue tokens with tenant-specific configuration."""

        now = datetime.utcnow()

        # JWT Access Token per RFC 9068
        access_token_claims = {
            "iss": tenant.security.issuer,
            "sub": user_id or client_id,
            "aud": f"https://{tenant.domain}/api",
            "client_id": client_id,
            "tenant_id": tenant.id,
            "scope": scope,
            "iat": int(now.timestamp()),
            "exp": int((now + timedelta(seconds=tenant.security.access_token_lifetime)).timestamp()),
            "jti": secrets.token_urlsafe(16),
        }

        # Add DPoP binding if used
        if dpop_jkt:
            access_token_claims["cnf"] = {"jkt": dpop_jkt}

        access_token = self._sign_jwt(access_token_claims, tenant)

        response = {
            "access_token": access_token,
            "token_type": "DPoP" if dpop_jkt else "Bearer",
            "expires_in": tenant.security.access_token_lifetime,
            "scope": scope,
        }

        # Issue refresh token for user grants
        if user_id:
            refresh_token = secrets.token_urlsafe(32)
            self.tenant_stores[tenant.id].save_refresh_token(
                token=refresh_token,
                client_id=client_id,
                user_id=user_id,
                scope=scope,
                dpop_jkt=dpop_jkt,
                expires_at=now + timedelta(seconds=tenant.security.refresh_token_lifetime),
            )
            response["refresh_token"] = refresh_token

        return response

    def _sign_jwt(self, claims: dict, tenant: Tenant) -> str:
        """Sign JWT with tenant-specific key."""
        # In production, use tenant-specific signing keys
        # retrieved from a secure key management system
        pass
```

### Multi-Tenant SCIM Service

```python
class MultiTenantSCIMService:
    """
    SCIM 2.0 Service with multi-tenancy.

    Implements:
    - RFC 7643: SCIM Core Schema
    - RFC 7644: SCIM Protocol
    """

    def __init__(self):
        self.tenant_stores: Dict[str, SCIMTenantStore] = {}

    def get_tenant_store(self, tenant_id: str) -> SCIMTenantStore:
        """Get or create tenant-specific SCIM store."""
        if tenant_id not in self.tenant_stores:
            self.tenant_stores[tenant_id] = SCIMTenantStore(tenant_id)
        return self.tenant_stores[tenant_id]

    def create_user(
        self,
        tenant_id: str,
        user_data: dict,
    ) -> dict:
        """Create user in tenant scope."""
        store = self.get_tenant_store(tenant_id)

        # Validate schema
        errors = SCIMValidator.validate_user(user_data)
        if errors:
            raise SCIMValidationError(errors)

        # Create user with tenant binding
        user = SCIMUser.from_dict(user_data)
        user.tenant_id = tenant_id

        created = store.create_user(user)

        return created.to_dict()

    def search_users(
        self,
        tenant_id: str,
        filter_expr: Optional[str] = None,
        start_index: int = 1,
        count: int = 100,
    ) -> dict:
        """Search users within tenant scope."""
        store = self.get_tenant_store(tenant_id)

        users, total = store.search_users(
            filter_expr=filter_expr,
            start_index=start_index,
            count=count,
        )

        return {
            "schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
            "totalResults": total,
            "startIndex": start_index,
            "itemsPerPage": len(users),
            "Resources": [u.to_dict() for u in users],
        }

    def provision_from_idp(
        self,
        tenant_id: str,
        idp_user: dict,
        idp_groups: List[str],
    ) -> SCIMUser:
        """
        Provision user from external IdP with group sync.

        Just-In-Time (JIT) provisioning pattern.
        """
        store = self.get_tenant_store(tenant_id)

        # Check if user exists
        existing = store.find_user_by_external_id(idp_user.get("sub"))

        if existing:
            # Update existing user
            existing.name = SCIMName(
                givenName=idp_user.get("given_name"),
                familyName=idp_user.get("family_name"),
            )
            existing.emails = [SCIMEmail(value=idp_user.get("email"), primary=True)]
            return store.update_user(existing)
        else:
            # Create new user
            user = SCIMUser(
                userName=idp_user.get("preferred_username") or idp_user.get("email"),
                externalId=idp_user.get("sub"),
                name=SCIMName(
                    givenName=idp_user.get("given_name"),
                    familyName=idp_user.get("family_name"),
                ),
                emails=[SCIMEmail(value=idp_user.get("email"), primary=True)],
                tenant_id=tenant_id,
            )
            created = store.create_user(user)

            # Sync groups
            for group_name in idp_groups:
                group = store.find_or_create_group(group_name)
                store.add_user_to_group(created.id, group.id)

            return created
```

### Tenant Routing Middleware

```python
from fastapi import FastAPI, Request, HTTPException
from starlette.middleware.base import BaseHTTPMiddleware

class TenantRoutingMiddleware(BaseHTTPMiddleware):
    """
    Route requests to appropriate tenant context.

    Supports:
    - Subdomain routing: tenant1.api.example.com
    - Path routing: api.example.com/tenant1/...
    - Header routing: X-Tenant-ID header
    """

    def __init__(self, app: FastAPI, auth_server: MultiTenantAuthorizationServer):
        super().__init__(app)
        self.auth_server = auth_server

    async def dispatch(self, request: Request, call_next):
        tenant_id = self._extract_tenant(request)

        if not tenant_id:
            # Allow unauthenticated endpoints
            if request.url.path in ["/health", "/.well-known/openid-configuration"]:
                return await call_next(request)
            raise HTTPException(400, "Tenant not specified")

        tenant = self.auth_server.get_tenant(tenant_id)
        if not tenant:
            raise HTTPException(404, f"Tenant not found: {tenant_id}")

        if tenant.status != "active":
            raise HTTPException(403, "Tenant is not active")

        # Add tenant to request state
        request.state.tenant = tenant
        request.state.tenant_id = tenant_id

        return await call_next(request)

    def _extract_tenant(self, request: Request) -> Optional[str]:
        # Try subdomain
        host = request.headers.get("host", "")
        if "." in host:
            subdomain = host.split(".")[0]
            tenant = self.auth_server.get_tenant_by_domain(subdomain)
            if tenant:
                return tenant.id

        # Try header
        tenant_id = request.headers.get("X-Tenant-ID")
        if tenant_id:
            return tenant_id

        # Try path
        path_parts = request.url.path.split("/")
        if len(path_parts) > 1 and path_parts[1]:
            potential_tenant = path_parts[1]
            if self.auth_server.get_tenant(potential_tenant):
                return potential_tenant

        return None
```

## Security Requirements

### Per RFC 9700 (Security BCP)

1. **PKCE Required** - All OAuth clients must use PKCE
2. **Sender-Constrained Tokens** - Use DPoP or mTLS for high-security
3. **Short Token Lifetime** - Access tokens max 1 hour
4. **Issuer Validation** - Verify `iss` claim matches tenant
5. **Audience Validation** - Verify `aud` claim for tenant

### Tenant Isolation

```python
class TenantIsolationValidator:
    """Validate tenant isolation in all operations."""

    @staticmethod
    def validate_token_tenant(token_claims: dict, expected_tenant: str) -> bool:
        """Ensure token is valid for the expected tenant."""
        token_tenant = token_claims.get("tenant_id")

        if not token_tenant:
            return False

        if token_tenant != expected_tenant:
            return False

        # Validate issuer matches tenant
        issuer = token_claims.get("iss", "")
        if expected_tenant not in issuer:
            return False

        return True

    @staticmethod
    def validate_resource_tenant(resource: dict, expected_tenant: str) -> bool:
        """Ensure resource belongs to expected tenant."""
        return resource.get("tenant_id") == expected_tenant
```

## Related RFC Modes

| Mode                          | Purpose                  |
| ----------------------------- | ------------------------ |
| `rfc-6749-oauth2-mode`        | OAuth 2.0 implementation |
| `rfc-7636-pkce-mode`          | PKCE security            |
| `rfc-9449-dpop-mode`          | DPoP token binding       |
| `rfc-7519-jwt-mode`           | JWT tokens               |
| `rfc-7643-scim-schema-mode`   | SCIM resources           |
| `rfc-7644-scim-protocol-mode` | SCIM API                 |

## Output Format

Provide:

- Multi-tenant architecture designs
- RFC-compliant implementations
- Tenant isolation patterns
- Security configurations

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.