PHPackages                             kyzegs/mtls-bundle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. kyzegs/mtls-bundle

ActiveSymfony-bundle[Authentication &amp; Authorization](/categories/authentication)

kyzegs/mtls-bundle
==================

A reusable Symfony bundle for mTLS (mutual TLS) client-certificate authentication.

00PHP

Since Apr 13Pushed 1mo agoCompare

[ Source](https://github.com/Kyzegs/mtls-bundle)[ Packagist](https://packagist.org/packages/kyzegs/mtls-bundle)[ RSS](/packages/kyzegs-mtls-bundle/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependenciesVersions (1)Used By (0)

kyzegs/mtls-bundle
==================

[](#kyzegsmtls-bundle)

A reusable Symfony bundle for **mTLS (mutual TLS) client certificate authentication**.

mTLS validation happens at the transport layer (Nginx / Caddy / Apache / Envoy). This bundle lives at the application layer: it reads the certificate the web server already validated, parses it, maps it to a Symfony security user, and integrates with Symfony's firewall, passport, and access-control system.

---

Requirements
------------

[](#requirements)

- PHP **8.2+**
- Symfony **6.4** or **7.x**

---

Installation
------------

[](#installation)

```
composer require kyzegs/mtls-bundle
```

If you are not using Symfony Flex, register the bundle manually:

```
// config/bundles.php
return [
    Kyzegs\MTLSBundle\MTLSBundle::class => ['all' => true],
];
```

---

Web server configuration
------------------------

[](#web-server-configuration)

The bundle expects your reverse proxy to forward the client certificate and verification status as server variables (or headers converted to server vars by your framework).

### Nginx example

[](#nginx-example)

```
ssl_client_certificate /etc/ssl/ca.crt;
ssl_verify_client      on;

location / {
    proxy_pass http://127.0.0.1:8000;
    proxy_set_header SSL_CLIENT_CERT   $ssl_client_escaped_cert;
    proxy_set_header SSL_CLIENT_VERIFY $ssl_client_verify;
}
```

### Caddy example

[](#caddy-example)

```
https://api.example.com {
    tls /certs/server.crt /certs/server.key {
        client_auth {
            mode require_and_verify
            trust_pool file /certs/ca.crt
        }
    }

    reverse_proxy 127.0.0.1:8000 {
        header_up SSL_CLIENT_CERT   {http.request.tls.client.certificate_pem}
        header_up SSL_CLIENT_VERIFY SUCCESS
    }
}
```

---

Configuration
-------------

[](#configuration)

```
# config/packages/mtls.yaml
mtls:
  enabled: true
  certificate_server_key: 'SSL_CLIENT_CERT'   # server var carrying the PEM
  verify_server_key:      'SSL_CLIENT_VERIFY'  # server var carrying the verify status
  allowed_verify_values:  ['SUCCESS']          # accepted verify statuses
  request_attribute_certificate: '_mtls_certificate'  # request attribute key for the ParsedCertificate
```

---

Security configuration
----------------------

[](#security-configuration)

```
# config/packages/security.yaml
security:
  providers:
    mtls_memory:
      memory: ~

  firewalls:
    dev:
      pattern: ^/(_(profiler|wdt)|css|images|js)/
      security: false

    api:
      pattern: ^/api
      stateless: true
      custom_authenticators:
        - Kyzegs\MTLSBundle\Security\Authenticator\MTLSAuthenticator

  access_control:
    - { path: ^/api, roles: ROLE_MTLS }
```

---

Custom user resolver
--------------------

[](#custom-user-resolver)

The bundle ships with `DefaultCertificateUserResolver`, which maps the certificate's **Common Name (CN)** — or serial number as fallback — to an `MTLSUser`.

To plug in your own identity mapping, implement `CertificateUserResolverInterface`and alias it in your services configuration:

```
// src/Security/MyCustomUserResolver.php
namespace App\Security;

use Kyzegs\MTLSBundle\Contract\CertificateUserResolverInterface;
use Kyzegs\MTLSBundle\ValueObject\ParsedCertificate;
use Symfony\Component\Security\Core\User\UserInterface;

final class MyCustomUserResolver implements CertificateUserResolverInterface
{
    public function resolve(ParsedCertificate $certificate): ?UserInterface
    {
        // Your domain logic here — e.g. look up participant by SAN URI
        $participantId = $certificate->subjectAltName('URI')[0] ?? null;
        // ...
    }
}
```

```
# config/services.yaml
services:
  Kyzegs\MTLSBundle\Contract\CertificateUserResolverInterface:
    alias: App\Security\MyCustomUserResolver
```

---

Multiple firewalls / multiple resolvers
---------------------------------------

[](#multiple-firewalls--multiple-resolvers)

When you need different certificate resolvers for different API endpoints, define multiple named authenticator services — each wired to a different resolver — and assign them to separate Symfony firewalls:

```
# config/services.yaml
services:
  App\Security\PartnerCertificateUserResolver: ~

  mtls.authenticator.partner:
    class: Kyzegs\MTLSBundle\Security\Authenticator\MTLSAuthenticator
    arguments:
      $enabled: '%mtls.enabled%'
      $extractor: '@Kyzegs\MTLSBundle\Service\CertificateExtractor'
      $parser: '@Kyzegs\MTLSBundle\Service\CertificateParser'
      $validator: '@Kyzegs\MTLSBundle\Service\CertificateValidator'
      $userResolver: '@App\Security\PartnerCertificateUserResolver'
      $requestAttributeCertificate: '%mtls.request_attribute_certificate%'
```

```
# config/packages/security.yaml
security:
  firewalls:
    api_partner:
      pattern: ^/api/partner
      stateless: true
      custom_authenticators:
        - mtls.authenticator.partner
```

---

Accessing the certificate in a controller
-----------------------------------------

[](#accessing-the-certificate-in-a-controller)

After successful authentication the parsed certificate is available as a request attribute:

```
use Kyzegs\MTLSBundle\ValueObject\ParsedCertificate;

#[Route('/api/profile')]
public function profile(Request $request): Response
{
    /** @var ParsedCertificate $cert */
    $cert = $request->attributes->get('_mtls_certificate');

    return new JsonResponse([
        'cn'     => $cert->commonName,
        'serial' => $cert->serialNumber,
        'san'    => $cert->subjectAltName('URI'),
    ]);
}
```

---

Architecture
------------

[](#architecture)

```
Web server (Nginx / Caddy)
  └─ validates client cert via TLS (mTLS)
  └─ forwards SSL_CLIENT_CERT + SSL_CLIENT_VERIFY to Symfony

MTLSAuthenticator::supports()
  └─ checks bundle is enabled + SSL_CLIENT_CERT header present

MTLSAuthenticator::authenticate()
  ├─ CertificateExtractor  — reads PEM + verify status from $request->server
  ├─ CertificateValidator  — asserts verify status is in allowed_verify_values
  ├─ CertificateParser     — openssl_x509_parse() → ParsedCertificate
  ├─ CertificateUserResolverInterface::resolve() → UserInterface
  └─ Returns SelfValidatingPassport with CertificateBadge

```

---

Local development / testing
---------------------------

[](#local-development--testing)

See the ChatGPT design conversation for a full Caddy + OpenSSL local mTLS setup. Quick summary:

```
# 1. Generate local CA
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt \
  -subj "/CN=Local Dev CA"

# 2. Generate client certificate
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr -subj "/CN=test-participant"
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key \
  -CAcreateserial -out client.crt -days 825 -sha256

# 3. Call your endpoint
curl --cacert ca.crt --cert client.crt --key client.key \
  https://localhost:8443/api/profile
```

---

License
-------

[](#license)

MIT

###  Health Score

19

—

LowBetter than 10% of packages

Maintenance59

Moderate activity, may be stable

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/eb13f61de13b186245a047cd1d882784e4dacb5b9f2a8f930b20e5992159e61a?d=identicon)[Kyzegs](/maintainers/Kyzegs)

---

Top Contributors

[![Kyzegs](https://avatars.githubusercontent.com/u/45851377?v=4)](https://github.com/Kyzegs "Kyzegs (4 commits)")

### Embed Badge

![Health badge](/badges/kyzegs-mtls-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/kyzegs-mtls-bundle/health.svg)](https://phpackages.com/packages/kyzegs-mtls-bundle)
```

###  Alternatives

[kartik-v/yii2-password

Useful password strength validation utilities for Yii Framework 2.0

761.2M17](/packages/kartik-v-yii2-password)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
