Skip to content
Back to skills

Rfc 8705 Mtls

ASecurity

Certificate-based client authentication and token binding for high-security OAuth 2.0. Use when implementing or validating rfc 8705 mtls protocol compliance.

  • 9 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 8, 2026
securitypythongofastapiapibackendsecurity

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add anubhavg-icpl/vibe --skill rfc-8705-mtls --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Rfc 8705 Mtls?

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

Security grade badge for Rfc 8705 Mtls
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/anubhavg-icpl-rfc-8705-mtls/badge)](https://www.skillsdirectory.com/skills/anubhavg-icpl-rfc-8705-mtls)

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-8705-mtls
description: Certificate-based client authentication and token binding for high-security OAuth 2.0. Use when implementing or validating rfc 8705 mtls protocol compliance.
license: CC-BY-NC-SA-4.0
metadata:
  risk: unknown
  source: community
  kind: mode
  category: rfc
  tags: [oauth, mtls, certificates, security, multi-tenancy, identity-broker]
---

# RFC 8705 - OAuth 2.0 Mutual-TLS Client Authentication

You are an expert in implementing mTLS for OAuth 2.0 as defined in RFC 8705. You provide certificate-based client authentication and sender-constrained access tokens for high-security multi-tenant environments.

## RFC Overview

| Property   | Value                                                                          |
| ---------- | ------------------------------------------------------------------------------ |
| RFC Number | 8705                                                                           |
| Title      | OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens |
| Status     | Proposed Standard                                                              |
| Published  | February 2020                                                                  |
| Extends    | RFC 6749                                                                       |

## mTLS Architecture

```text
┌─────────────────────────────────────────────────────────────────────────┐
│                     mTLS OAuth 2.0 Flow                                  │
│                                                                          │
│  ┌──────────────┐                           ┌──────────────────────┐   │
│  │              │   TLS with Client Cert    │                      │   │
│  │    Client    │◄─────────────────────────►│  Authorization       │   │
│  │  (with cert) │                           │  Server              │   │
│  │              │                           │                      │   │
│  └──────┬───────┘                           └──────────────────────┘   │
│         │                                                               │
│         │  Certificate-bound                                            │
│         │  Access Token                                                 │
│         │  (contains x5t#S256)                                         │
│         │                                                               │
│         ▼                                                               │
│  ┌──────────────┐   TLS with Client Cert    ┌──────────────────────┐   │
│  │              │◄─────────────────────────►│                      │   │
│  │   Resource   │   + Access Token          │  Resource Server     │   │
│  │   Request    │                           │  (validates cert     │   │
│  │              │                           │   matches token)     │   │
│  └──────────────┘                           └──────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────┘
```

## Certificate Thumbprint Calculation

```python
import hashlib
import base64
from cryptography import x509
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa, ec
from cryptography.hazmat.backends import default_backend
from cryptography.x509.oid import NameOID
from datetime import datetime, timedelta

class CertificateThumbprint:
    """
    Calculate certificate thumbprint for mTLS token binding.

    RFC 8705 Section 3: Certificate Thumbprint Confirmation Method
    """

    @staticmethod
    def calculate_x5t_s256(cert_pem: bytes) -> str:
        """
        Calculate x5t#S256 thumbprint.

        This is the SHA-256 hash of the DER-encoded certificate,
        base64url encoded.
        """
        cert = x509.load_pem_x509_certificate(cert_pem, default_backend())
        der_bytes = cert.public_bytes(serialization.Encoding.DER)
        digest = hashlib.sha256(der_bytes).digest()
        return base64.urlsafe_b64encode(digest).decode().rstrip('=')

    @staticmethod
    def from_tls_connection(ssl_socket) -> str:
        """Extract certificate thumbprint from TLS connection."""
        peer_cert_der = ssl_socket.getpeercert(binary_form=True)
        digest = hashlib.sha256(peer_cert_der).digest()
        return base64.urlsafe_b64encode(digest).decode().rstrip('=')
```

## Implementation

### mTLS Client Authentication

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

class MTLSClientAuthMethod(Enum):
    """RFC 8705 Section 2: Client Authentication Methods"""
    TLS_CLIENT_AUTH = "tls_client_auth"  # PKI-based
    SELF_SIGNED_TLS_CLIENT_AUTH = "self_signed_tls_client_auth"  # Self-signed

@dataclass
class MTLSClientConfig:
    """mTLS client configuration for multi-tenant environment."""
    client_id: str
    tenant_id: str
    auth_method: MTLSClientAuthMethod

    # For tls_client_auth (PKI)
    tls_client_auth_subject_dn: Optional[str] = None
    tls_client_auth_san_dns: Optional[str] = None
    tls_client_auth_san_uri: Optional[str] = None
    tls_client_auth_san_ip: Optional[str] = None
    tls_client_auth_san_email: Optional[str] = None

    # For self_signed_tls_client_auth
    jwks_uri: Optional[str] = None
    jwks: Optional[dict] = None


class MTLSAuthenticator:
    """
    Authenticate OAuth clients using mTLS.

    RFC 8705 Section 2: Mutual-TLS Client Authentication
    """

    def __init__(self):
        self.clients: dict[str, MTLSClientConfig] = {}

    def register_client(self, config: MTLSClientConfig) -> None:
        """Register mTLS client configuration."""
        key = f"{config.tenant_id}:{config.client_id}"
        self.clients[key] = config

    def authenticate(
        self,
        client_id: str,
        tenant_id: str,
        client_cert: x509.Certificate,
    ) -> bool:
        """
        Authenticate client using presented certificate.

        RFC 8705 Section 2.1 & 2.2
        """
        key = f"{tenant_id}:{client_id}"
        config = self.clients.get(key)

        if not config:
            return False

        if config.auth_method == MTLSClientAuthMethod.TLS_CLIENT_AUTH:
            return self._authenticate_pki(config, client_cert)
        else:
            return self._authenticate_self_signed(config, client_cert)

    def _authenticate_pki(
        self,
        config: MTLSClientConfig,
        cert: x509.Certificate,
    ) -> bool:
        """
        PKI-based authentication.

        RFC 8705 Section 2.1: PKI Mutual-TLS Method
        """
        # Check Subject DN
        if config.tls_client_auth_subject_dn:
            subject_dn = cert.subject.rfc4514_string()
            if subject_dn != config.tls_client_auth_subject_dn:
                return False

        # Check SAN (Subject Alternative Name)
        try:
            san = cert.extensions.get_extension_for_class(
                x509.SubjectAlternativeName
            )

            if config.tls_client_auth_san_dns:
                dns_names = san.value.get_values_for_type(x509.DNSName)
                if config.tls_client_auth_san_dns not in dns_names:
                    return False

            if config.tls_client_auth_san_uri:
                uris = san.value.get_values_for_type(x509.UniformResourceIdentifier)
                if config.tls_client_auth_san_uri not in uris:
                    return False

            if config.tls_client_auth_san_email:
                emails = san.value.get_values_for_type(x509.RFC822Name)
                if config.tls_client_auth_san_email not in emails:
                    return False

        except x509.ExtensionNotFound:
            if any([
                config.tls_client_auth_san_dns,
                config.tls_client_auth_san_uri,
                config.tls_client_auth_san_email,
            ]):
                return False

        return True

    def _authenticate_self_signed(
        self,
        config: MTLSClientConfig,
        cert: x509.Certificate,
    ) -> bool:
        """
        Self-signed certificate authentication.

        RFC 8705 Section 2.2: Self-Signed Certificate Mutual-TLS Method
        """
        # Get public key from certificate
        cert_public_key = cert.public_key()

        # Verify against registered JWKS
        if config.jwks:
            return self._verify_against_jwks(cert_public_key, config.jwks)

        return False

    def _verify_against_jwks(self, public_key, jwks: dict) -> bool:
        """Verify certificate public key matches a key in JWKS."""
        for key in jwks.get("keys", []):
            if self._keys_match(public_key, key):
                return True
        return False

    def _keys_match(self, cert_key, jwk: dict) -> bool:
        """Compare certificate public key with JWK."""
        # Implementation would compare key parameters
        pass
```

### Certificate-Bound Access Tokens

```python
@dataclass
class CertificateBoundToken:
    """
    Access token bound to client certificate.

    RFC 8705 Section 3: Client Certificate-Bound Access Tokens
    """
    access_token: str
    token_type: str
    expires_in: int
    scope: str

    # Certificate binding
    cnf: dict  # Contains x5t#S256

class MTLSTokenIssuer:
    """Issue certificate-bound access tokens."""

    def __init__(self, signing_key: bytes, issuer: str):
        self.signing_key = signing_key
        self.issuer = issuer

    def issue_token(
        self,
        client_id: str,
        tenant_id: str,
        user_id: Optional[str],
        scope: str,
        client_cert_thumbprint: str,
        expires_in: int = 3600,
    ) -> dict:
        """
        Issue certificate-bound JWT access token.

        RFC 8705 Section 3.1: JWT Certificate Thumbprint Confirmation Method
        """
        now = datetime.utcnow()

        claims = {
            "iss": f"https://{tenant_id}.auth.example.com",
            "sub": user_id or client_id,
            "aud": f"https://{tenant_id}.api.example.com",
            "client_id": client_id,
            "tenant_id": tenant_id,
            "scope": scope,
            "iat": int(now.timestamp()),
            "exp": int((now + timedelta(seconds=expires_in)).timestamp()),
            "jti": secrets.token_urlsafe(16),

            # Certificate binding (RFC 8705 Section 3.1)
            "cnf": {
                "x5t#S256": client_cert_thumbprint
            }
        }

        access_token = self._sign_jwt(claims)

        return {
            "access_token": access_token,
            "token_type": "Bearer",  # Still Bearer, but cert-bound
            "expires_in": expires_in,
            "scope": scope,
        }

    def _sign_jwt(self, claims: dict) -> str:
        """Sign JWT with server key."""
        import jwt
        return jwt.encode(claims, self.signing_key, algorithm="HS256")


class MTLSTokenValidator:
    """
    Validate certificate-bound tokens at resource server.

    RFC 8705 Section 3: Confirmation of Possession
    """

    def validate(
        self,
        access_token: str,
        client_cert_thumbprint: str,
        expected_tenant: str,
    ) -> dict:
        """
        Validate token and confirm certificate binding.

        The resource server MUST verify that the certificate thumbprint
        in the token matches the certificate used in the TLS connection.
        """
        # Decode and verify JWT
        try:
            claims = jwt.decode(access_token, options={"verify_signature": True})
        except jwt.InvalidTokenError as e:
            raise TokenValidationError(f"Invalid token: {e}")

        # Verify tenant
        if claims.get("tenant_id") != expected_tenant:
            raise TokenValidationError("Token not valid for this tenant")

        # Verify certificate binding (RFC 8705 Section 3)
        cnf = claims.get("cnf", {})
        token_thumbprint = cnf.get("x5t#S256")

        if not token_thumbprint:
            raise TokenValidationError("Token is not certificate-bound")

        if not secrets.compare_digest(token_thumbprint, client_cert_thumbprint):
            raise TokenValidationError(
                "Certificate does not match token binding"
            )

        return claims


class TokenValidationError(Exception):
    pass
```

### FastAPI mTLS Integration

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

app = FastAPI()

class MTLSMiddleware(BaseHTTPMiddleware):
    """Extract client certificate from TLS connection."""

    async def dispatch(self, request: Request, call_next):
        # Get client certificate from TLS connection
        # This depends on your reverse proxy configuration

        # Option 1: Certificate in header (from nginx/envoy)
        cert_pem = request.headers.get("X-Client-Cert")

        # Option 2: Direct TLS termination
        # cert_der = request.scope.get("transport").get_extra_info("ssl_object").getpeercert(binary_form=True)

        if cert_pem:
            thumbprint = CertificateThumbprint.calculate_x5t_s256(
                cert_pem.encode()
            )
            request.state.client_cert_thumbprint = thumbprint
        else:
            request.state.client_cert_thumbprint = None

        return await call_next(request)


def require_mtls(request: Request) -> str:
    """Dependency to require mTLS client certificate."""
    thumbprint = getattr(request.state, "client_cert_thumbprint", None)
    if not thumbprint:
        raise HTTPException(401, "Client certificate required")
    return thumbprint


@app.post("/oauth/token")
async def token_endpoint(
    request: Request,
    cert_thumbprint: str = Depends(require_mtls),
):
    """Token endpoint with mTLS client authentication."""
    form = await request.form()
    client_id = form.get("client_id")
    tenant_id = request.headers.get("X-Tenant-ID")

    # Authenticate client via mTLS
    authenticator = MTLSAuthenticator()
    # ... client authentication logic

    # Issue certificate-bound token
    issuer = MTLSTokenIssuer(signing_key, issuer_url)
    token_response = issuer.issue_token(
        client_id=client_id,
        tenant_id=tenant_id,
        user_id=None,  # Client credentials grant
        scope=form.get("scope", ""),
        client_cert_thumbprint=cert_thumbprint,
    )

    return token_response


@app.get("/api/resource")
async def protected_resource(
    request: Request,
    cert_thumbprint: str = Depends(require_mtls),
):
    """Protected resource requiring certificate-bound token."""
    auth_header = request.headers.get("Authorization", "")
    if not auth_header.startswith("Bearer "):
        raise HTTPException(401, "Bearer token required")

    token = auth_header[7:]
    tenant_id = request.headers.get("X-Tenant-ID")

    # Validate token with certificate binding
    validator = MTLSTokenValidator()
    try:
        claims = validator.validate(
            access_token=token,
            client_cert_thumbprint=cert_thumbprint,
            expected_tenant=tenant_id,
        )
    except TokenValidationError as e:
        raise HTTPException(401, str(e))

    return {"data": "protected resource", "client": claims.get("client_id")}
```

### Nginx mTLS Configuration

```nginx
# nginx.conf for mTLS termination

server {
    listen 443 ssl;
    server_name api.example.com;

    # Server certificate
    ssl_certificate /etc/nginx/certs/server.crt;
    ssl_certificate_key /etc/nginx/certs/server.key;

    # Client certificate verification
    ssl_client_certificate /etc/nginx/certs/ca-bundle.crt;
    ssl_verify_client optional;  # or 'on' to require
    ssl_verify_depth 2;

    location /oauth/token {
        # Require client certificate for token endpoint
        if ($ssl_client_verify != SUCCESS) {
            return 403;
        }

        # Pass certificate to backend
        proxy_set_header X-Client-Cert $ssl_client_escaped_cert;
        proxy_set_header X-Client-Cert-DN $ssl_client_s_dn;
        proxy_set_header X-Client-Cert-Verify $ssl_client_verify;

        proxy_pass http://auth-backend;
    }

    location /api/ {
        # Optional client certificate for API
        proxy_set_header X-Client-Cert $ssl_client_escaped_cert;
        proxy_set_header X-Client-Cert-Verify $ssl_client_verify;

        proxy_pass http://api-backend;
    }
}
```

## Multi-Tenancy Patterns

### Per-Tenant Certificate Authority

```python
class MultiTenantPKI:
    """Manage per-tenant certificate authorities."""

    def __init__(self):
        self.tenant_cas: dict[str, x509.Certificate] = {}

    def register_tenant_ca(self, tenant_id: str, ca_cert: x509.Certificate):
        """Register CA certificate for tenant."""
        self.tenant_cas[tenant_id] = ca_cert

    def verify_client_cert(
        self,
        tenant_id: str,
        client_cert: x509.Certificate,
    ) -> bool:
        """Verify client certificate against tenant CA."""
        ca_cert = self.tenant_cas.get(tenant_id)
        if not ca_cert:
            return False

        try:
            # Verify certificate chain
            ca_cert.public_key().verify(
                client_cert.signature,
                client_cert.tbs_certificate_bytes,
                # padding and algorithm from signature
            )
            return True
        except Exception:
            return False
```

## Security Considerations

1. **Certificate Validation** - Always verify full certificate chain
2. **Revocation Checking** - Check CRL/OCSP for revoked certificates
3. **Thumbprint Binding** - Use SHA-256 for thumbprint calculation
4. **TLS Version** - Require TLS 1.2+ for mTLS
5. **Certificate Lifetime** - Use short-lived certificates when possible

## Related RFCs

| RFC      | Title     | Relationship        |
| -------- | --------- | ------------------- |
| RFC 6749 | OAuth 2.0 | Base framework      |
| RFC 9449 | DPoP      | Alternative binding |
| RFC 7517 | JWK       | Key representation  |

## Output Format

Provide:

- mTLS client authentication implementations
- Certificate-bound token issuance
- Token validation with binding verification
- Nginx/reverse proxy 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.