Skip to content

Authentication

Certificate-based authentication decorators and helpers.

CertificateIdentity

Represents a user identity based on their client certificate.

CertificateIdentity dataclass

CertificateIdentity(
    fingerprint: str, cert: "Certificate | None" = None
)

Represents a user identity based on their client certificate.

Example

identity = get_identity(request) if identity: print(f"User: {identity.short_id}")

cert class-attribute instance-attribute

cert: 'Certificate | None' = None

The raw certificate object (if available).

fingerprint instance-attribute

fingerprint: str

SHA-256 fingerprint of the certificate.

short_id property

short_id: str

Short identifier suitable for display (first 16 chars).

Helper Functions

get_identity

get_identity

get_identity(
    request: "Request",
) -> CertificateIdentity | None

Get the certificate-based identity from a request.

Parameters:

Name Type Description Default
request 'Request'

The current request.

required

Returns:

Type Description
CertificateIdentity | None

CertificateIdentity if a client certificate was provided, None otherwise.

Example

@app.gemini("/whoami") def whoami(request: Request): identity = get_identity(request) if identity: return f"# Your ID: {identity.short_id}" return "# You are anonymous"

Source code in src/xitzin/auth.py
def get_identity(request: "Request") -> CertificateIdentity | None:
    """Get the certificate-based identity from a request.

    Args:
        request: The current request.

    Returns:
        CertificateIdentity if a client certificate was provided, None otherwise.

    Example:
        @app.gemini("/whoami")
        def whoami(request: Request):
            identity = get_identity(request)
            if identity:
                return f"# Your ID: {identity.short_id}"
            return "# You are anonymous"
    """
    if request.client_cert_fingerprint:
        return CertificateIdentity(
            fingerprint=request.client_cert_fingerprint,
            cert=request.client_cert,
        )
    return None

Decorators

require_certificate

require_certificate

require_certificate(
    handler: Callable[..., Any],
) -> Callable[..., Any]

Decorator that requires a valid client certificate.

If no certificate is provided, returns status 60 (certificate required).

Example

@app.gemini("/admin") @require_certificate def admin_panel(request: Request): return "# Admin Panel"

Source code in src/xitzin/auth.py
def require_certificate(handler: Callable[..., Any]) -> Callable[..., Any]:
    """Decorator that requires a valid client certificate.

    If no certificate is provided, returns status 60 (certificate required).

    Example:
        @app.gemini("/admin")
        @require_certificate
        def admin_panel(request: Request):
            return "# Admin Panel"
    """

    @wraps(handler)
    def wrapper(request: "Request", *args: Any, **kwargs: Any) -> Any:
        if not request.client_cert_fingerprint:
            raise CertificateRequired("Client certificate required")
        return handler(request, *args, **kwargs)

    return wrapper

require_fingerprint

require_fingerprint

require_fingerprint(
    *allowed_fingerprints: str,
) -> Callable[[Callable[..., Any]], Callable[..., Any]]

Decorator factory that requires specific certificate fingerprints.

If the client certificate fingerprint is not in the allowed list, returns status 61 (certificate not authorized).

Uses timing-safe comparison to prevent fingerprint enumeration attacks.

Parameters:

Name Type Description Default
*allowed_fingerprints str

SHA-256 fingerprints that are allowed.

()
Example

ADMIN_CERTS = [ "abc123...", # Alice's certificate "def456...", # Bob's certificate ]

@app.gemini("/admin") @require_fingerprint(*ADMIN_CERTS) def admin_panel(request: Request): return "# Admin Panel"

Source code in src/xitzin/auth.py
def require_fingerprint(
    *allowed_fingerprints: str,
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
    """Decorator factory that requires specific certificate fingerprints.

    If the client certificate fingerprint is not in the allowed list,
    returns status 61 (certificate not authorized).

    Uses timing-safe comparison to prevent fingerprint enumeration attacks.

    Args:
        *allowed_fingerprints: SHA-256 fingerprints that are allowed.

    Example:
        ADMIN_CERTS = [
            "abc123...",  # Alice's certificate
            "def456...",  # Bob's certificate
        ]

        @app.gemini("/admin")
        @require_fingerprint(*ADMIN_CERTS)
        def admin_panel(request: Request):
            return "# Admin Panel"
    """
    allowed_list = list(allowed_fingerprints)

    def decorator(handler: Callable[..., Any]) -> Callable[..., Any]:
        @wraps(handler)
        def wrapper(request: "Request", *args: Any, **kwargs: Any) -> Any:
            if not request.client_cert_fingerprint:
                raise CertificateRequired("Client certificate required")

            if not _timing_safe_fingerprint_check(
                request.client_cert_fingerprint, allowed_list
            ):
                raise CertificateNotAuthorized("Certificate not authorized")

            return handler(request, *args, **kwargs)

        return wrapper

    return decorator

optional_certificate

optional_certificate

optional_certificate(
    handler: Callable[..., Any],
) -> Callable[..., Any]

Decorator that makes certificate identity available but not required.

Sets request.state.identity to CertificateIdentity or None.

Example

@app.gemini("/profile") @optional_certificate def profile(request: Request): identity = request.state.identity if identity: return f"# Welcome back, {identity.short_id}" return "# Welcome, anonymous visitor"

Source code in src/xitzin/auth.py
def optional_certificate(handler: Callable[..., Any]) -> Callable[..., Any]:
    """Decorator that makes certificate identity available but not required.

    Sets request.state.identity to CertificateIdentity or None.

    Example:
        @app.gemini("/profile")
        @optional_certificate
        def profile(request: Request):
            identity = request.state.identity
            if identity:
                return f"# Welcome back, {identity.short_id}"
            return "# Welcome, anonymous visitor"
    """

    @wraps(handler)
    def wrapper(request: "Request", *args: Any, **kwargs: Any) -> Any:
        request.state.identity = get_identity(request)
        return handler(request, *args, **kwargs)

    return wrapper