Skip to content

Authentication Context

The authentication context system tracks who is making a request and on whose behalf. It's the foundation for all authorization decisions.

AuthContext

AuthContext is a frozen dataclass that stores authentication state for each request.

Key Properties

Property Type Description
is_authenticated bool True if a principal is set
is_anonymous bool True if no principal (unauthenticated)
is_impersonated bool True if effective != real principal
is_delegated bool True if a delegate principal is set
real_principal AuthPrincipal The actor performing the action
effective_principal AuthPrincipal Subject of the action (actor or impersonated)
delegate_principal AuthPrincipal \| None Principal acting on behalf of real
session_id UUID \| None Current session identifier
session_scopes set[str] Session permission scopes
impersonation_mode ImpersonationMode \| None Type of impersonation

Accessing the Context

Migration in progress

current_auth_context will fully replace the legacy g.current_user / g.actor globals, but is not yet available on all backends. Check if your backend has been migrated before using it.

Use current_auth_context to access the auth context within a Flask request:

from shared.iam.helpers import current_auth_context

def my_endpoint():
    # Get the principal
    user = current_auth_context.real_principal
    print(f"Request by user {user.id}")

Never Create AuthContext Directly

Always access auth context via current_auth_context. The context is managed by auth providers and should never be instantiated manually.

Type-Safe Principal Access

Use *_as() methods for type-safe principal casting:

from shared.iam.helpers import current_auth_context

# Type-safe access - raises ValueError if wrong type
user = current_auth_context.real_principal_as(User)
employee = current_auth_context.real_principal_as(AlanEmployee)

# For effective principal (the subject)
subject = current_auth_context.effective_principal_as(User)

# For optional delegate
delegate = current_auth_context.delegate_principal_as(ServiceAccount)  # Returns None if not set

Impersonation

Impersonation allows Alaners to act on behalf of users. When impersonated:

  • real_principal = The Alaner (actor)
  • effective_principal = The impersonated user (subject)
  • is_impersonated = True

Impersonation Modes

Future change

Impersonation modes will likely be replaced by proper scopes in the future.

from shared.iam.helpers import ImpersonationMode

# Read-only: Alaner can view as user but not modify
ImpersonationMode.read_only

# Read-write: Alaner can view and modify as user
ImpersonationMode.read_write

# Service account delegation: Used by API Proxy system
ImpersonationMode.service_account_delegation

Setting Impersonation

from shared.iam.helpers import set_auth_context, ImpersonationMode

# In an auth provider
set_auth_context(
    real_principal=alaner,
    effective_principal=impersonated_user,
    impersonation_mode=ImpersonationMode.read_only,
)

Auth Context Providers

Migration in progress

Auth context providers are not yet available on all backends. Check if your backend has been migrated before using them.

Providers extract authentication from requests and set up the AuthContext.

Base Class

from abc import ABC, abstractmethod
from shared.iam.auth_context.providers.base import AuthContextProvider

class MyAuthProvider(AuthContextProvider):
    @abstractmethod
    def will_handle_request(self) -> bool:
        """Return True if this provider can handle the current request"""
        pass

    @abstractmethod
    def set_auth_context_from_request(self) -> None:
        """Extract auth info and call set_auth_context()"""
        pass

Provider Chain Pattern

Providers are evaluated in order - exactly ONE must handle the request.

How the chain works:

  1. Iterates through providers in order
  2. Calls will_handle_request() on each
  3. Exactly ONE provider must return True
  4. Calls that provider's set_auth_context_from_request()
  5. Returns 403 if zero or multiple providers match

Default setup: The default provider chain is configured at app initialization via CustomApi.register_default_auth_context_providers(). Most blueprints inherit this default automatically.

Blueprint customization: Individual blueprints can override the default chain:

from shared.api.custom_smorest_blueprint import CustomBlueprint
from shared.iam.auth_context.providers.zero_trust import ZeroTrustAuthContextProvider
from shared.iam.auth_context.providers.anonymous import AnonymousAuthContextProvider

# Blueprint with custom auth providers
public_blueprint = CustomBlueprint(
    "public",
    __name__,
    auth_context_providers=[
        ZeroTrustAuthContextProvider(User),
        AnonymousAuthContextProvider(),  # Allow unauthenticated access
    ],
)

Built-in Providers

AnonymousAuthContextProvider

Fallback for unauthenticated requests:

from shared.iam.auth_context.providers.anonymous import AnonymousAuthContextProvider

class AnonymousAuthContextProvider(AuthContextProvider):
    def will_handle_request(self) -> bool:
        return True  # Always matches (use as last in chain)

    def set_auth_context_from_request(self) -> None:
        # Does nothing - context is anonymous by default
        pass

ZeroTrustAuthContextProvider

For Cloudflare Zero Trust authentication:

from shared.iam.auth_context.providers.zero_trust import ZeroTrustAuthContextProvider

# Searches for user by email in these model classes
provider = ZeroTrustAuthContextProvider(Alaner, ExternalUser)

# Custom JWT audience config key
provider = ZeroTrustAuthContextProvider(
    Alaner,
    aud_config_key="MY_ZEROTRUST_AUDIENCE",
)

Behavior:

  • Returns False if Authorization header is present
  • Returns False if no Zero Trust cookie
  • Validates JWT token and extracts email
  • Looks up principal by email

ServiceAccountAuthContextProvider

For Google service account authentication via OAuth tokens.

WebhookAuthContextProvider

For webhook authentication with secret validation or HMAC-SHA256 signatures.

Creating Custom Providers

from flask import request
from flask_smorest import abort

from shared.iam.auth_context.providers.base import AuthContextProvider
from shared.iam.helpers import set_auth_context

class ApiKeyAuthContextProvider(AuthContextProvider):
    """Authenticate via API key header"""

    def __init__(self, *auth_principal_types: type[AuthPrincipal]):
        self.auth_principal_types = auth_principal_types

    def will_handle_request(self) -> bool:
        return "X-API-Key" in request.headers

    def set_auth_context_from_request(self) -> None:
        api_key = request.headers.get("X-API-Key")

        # Validate and lookup principal
        principal = self._lookup_by_api_key(api_key)
        if not principal:
            abort(403, message="Invalid API key")

        set_auth_context(real_principal=principal)

    def _lookup_by_api_key(self, api_key: str) -> AuthPrincipal | None:
        # Implementation here
        pass

TransactionAuthContext

TransactionAuthContext is a database model that stores auth context for each transaction (database operation). It enables audit trails.

from shared.models.versioning.base import TransactionAuthContextBase

class TransactionAuthContext(TransactionAuthContextBase):
    __tablename__ = "transaction_auth_context"

The id column automatically captures current_auth_context.id, linking database operations to the authentication context.

Serialization

Auth context is automatically handled in most cases:

  • RQ async jobs - Automatically serialized and restored
  • Structured logs - Automatically included under the authnz object (see shared/helpers/logging/configure_logging.py)

For other use cases, serialization methods are available:

# Serialize to dict
serialized = current_auth_context.to_dict()

# Restore from dict (context manager)
from shared.iam.helpers import set_auth_context_from_dict

with set_auth_context_from_dict(serialized):
    # Auth context is restored here
    pass

Helper Functions

Function Purpose
set_auth_context(real_principal, ...) Set the auth context (for providers)
reset_auth_context() Reset to anonymous
set_auth_context_from_dict(serialized) Context manager to restore from dict
is_alaner_admin() Check if current user is Alaner admin
is_impersonated() Check if current request is impersonated