Skip to main content

Authenticating with X.509 client certificates

07 Dec 2015
Tags: ssl authentication symfony php

Last week, I was diving in different authentication systems for API's. One of the better ways of authentication is through X.509 client certificates. This one is a bit is harder to set-up, but sure is secure, manageable and powerful. While searching for documentation on the subject, I was surprised there weren't a lot of good articles. In this article, I will try to explain every step as easy as possible.

Why should I use X.509 authentication?

The main advantage is that the client is not sending a username or password to the server. This means that a man-in-the-middle attack is nearly impossible. It is much easier to steal a username/password login, for example by bruteforcing, then stealing a certificate. Because the certificate is signed, it is only possible to connect to the real server. It is possible to revoke and manage these certificates in an easy way.

Configuring the server

Client Certificate authentication can only be done while running HTTPS. So first of all, make sure the server is running HTTPS. This can be done with a self-signed or a signed certificate. Your apache VHost configuration should look more or less like this:

SSLEngine on
SSLCertificateFile      "/etc/ssl/certs/server.pem"
SSLCertificateKeyFile   "/etc/ssl/private/server.key"
SSLProtocol             TLSv1 TLSv1.1 TLSv1.2
SSLCipherSuite          "......."
# When using signed certificates:
SSLCertificateChainFile "/etc/ssl/"

Now that you got HTTPS up and running, you should be able to browse to your application through HTTPS.

Creating a certification authority

A certification authority (CA) hands out a digital certificate in which the CA says that a public key in the certificate, belongs to the person, organization, server or entity that is mentioned in the certificate. In our example, this will be done based on the e-mail address that is provided in the certificate. The task of the CA is to control the identity of the issuer, so that the client that is using the certificates from the CA can be trusted.

First we will need to create the CA private key and certificate:

cd /etc/ssl/certs/
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 365 -key ca.key -out ca.crt
Note: Make sure to set the CN to a valid server domain. Otherwise this might result in exceptions!

Now that we have the CA key and certificate, we can register it to Apache to validate the client certificates:

SSLCACertificateFile "/etc/ssl/certs/ca.crt"
SSLVerifyClient optional
SSLVerifyDepth 1
SSLOptions +StdEnvVars

As you can see the client certificate verification is optional. This will make it possible to add another type of authentication like basic authentication when there is nog client certificate. The verify depth is set to 1 so that it only accepts certificates signed by the configured CA. Finally, the StdEnvVars are registered so that the additional SSL server variables are available in PHP.

Configuring the client

Now that we got or server fully configured, it is time to create a client certificate. Following commands should be ran on the client machine to create a Certificate Signing Request (CSR):

openssl genrsa -out client.key 4096
openssl req -new -key client.key -out client.csr
Note: Make sure to set the CN to a valid server domain. Otherwise this might result in exceptions!
Note: The email field will be used to authenticate the user.

The CSR file is useless when it is not signed by the CA. So the logical next step is to transfer the certificate to the server and sign it with the CA.

openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt

The command above results in a useful client certificate. To make it easier to use the certificate, we will pack the client private key and the certificate in one file. This action should run on the client machine:

cat client.crt client.key > client.pem
Note: it's important to place the crt before the key in the pem file!

In above example the CSR was created on the client, to make it clear that the certificate + key should only be known by the client. However, it is perfectly possible to run all these commands on the server and send the pem file to the client who will be using the certificate. This means that the creation of the client certificate can be automated. You could create your own user interface to make the keys manageable per user of the application. This can for example be done with the built-in openssl extension of PHP. When the certification file is generated on the server, you should transfer this certificate in a trusted way. For example a download over HTTPS in the back-end of your application.

Requesting resources with a client certificate

Ok, we configured our server and requested a client certificate. Now how do I use this certificate to get my resources? The easiest way is the add the "-cert" attribute to the curl command. Since we are using Guzzle for HTTP requests, the client configuration will look like this:

$client = new Guzzle\Http\Client($baseUrl, [
    'ssl.certificate_authority' => 'system',
    'request.options' => [
        'cert' => 'client.pem',

As you can see it is possible to specify the certificate in the request.options part of the configuration. Another option is the ssl.certificate_authority. This one can be used to specify which CA that should be used. By default the built-in CA file is being used. You can choose to disable ssl verification or add your own ca file. For example, when using self-signed certificates, you can run following command:

openssl s_client -showcerts -connect > ca-file.pem

This ca-file.pem file will contain the certificate of the certification authority and mark it as trusted. Now it can be used in the PHP configuration as followed:

$client = new Guzzle\Http\Client($baseUrl, [
    'ssl.certificate_authority' => 'ca-file.pem',
    'request.options' => [
        'cert' => 'client.pem',

X.509 authentication in Symfony

One of the less known features of Symfony is X.509 authentication. It can easily be configured with the default x509 authentication security adapter. The configuration is pretty easy:

                      roles: ROLE_SUPER_ADMIN
                provider: client_certificate
            - { path: ^/, roles: ROLE_SUPER_ADMIN, requires_channel: https }
Note: This type of authentication only works with HTTPS. You might want to enforce HTTPS!

As you can see HTTPS is enforced and authentication will try X.509 authentication by default. When a valid client certificate is found, Symfony will try to match the email that is configured inside the certificate with a user in the client_certificate user provider. In this case we are using an in-memory provider that links an e-mail to a security role.

X.509 authentication in PHP

That was easy! But how does it work? By adding the "SSLOptions +StdEnvVars" configuration in Apache, there are some additional "SSL_" environment variables available. These variables contain the email in the client certificate. For example:

$email = $_SERVER['SSL_CLIENT_S_DN_Email'];
if (!$email && preg_match('#/emailAddress=(.+\@.+\..+)(/|$)#', $_SERVER['SSL_CLIENT_S_DN'], $matches)) {
    $email = $matches[1]

Now that we have the e-mail, it is possible to search this email in the list of configured client_certificates. Symfony will throw an exception when the email can't be found.


Even though it is a lot of work to get this type of authentication running, it sure is a powerful type of authentication. The users of your application will be able to connect to your application in an improved and secure way. It also comes in very handy for server to server communication in which you don't always want to hardcode user credentials. Your customers will surely thank you for the extra effort you made!

whois VeeWee


Hi there!

Glad you made it to my blog. Please feel free to take a look around. You will find some interesting stuff, mostly about web development and PHP.

Still can't get enough of me? Quick! Take a look at my Speakerdeck, Twitter or Github account.