Authenticate Apache HTTPD with Keycloak

Let's assume that you have already installed Keycloak. Now we want our HTTP server to authenticate with it. We'll start with the simplest local users and groups. Next, we will try to use Keycloak's self-registration feature to achieve complete user management through tool integration alone.

Apache HTTPD server installation.

A minimal installation of SUSE SLE 15 SP4 was installed on a virtual machine with 2 processors and 4G of memory. Add some programs for comfortable work:

root@httpd:~ # zypper in vim bash-completion rsync rsyslog
Bold font indicates the command that you are typing, the rest is the output of the command.

It is important to set up a fixed IP address and DNS name for a server. In this demo it is httpd.mydomain.com. Please create a server SSL certificate for this name, IP and FQDN. Save the certificate and private key for future use.

The local CA must be known and trusted. To do this, place the CA certificate in the appropriate location and run the command:

root@httpd:~ # cp -v ca.crt /etc/pki/trust/anchors/
'ca.crt' -> '/etc/pki/trust/anchors/ca.crt'
root@httpd:~ # update-ca-certificates

Subscribe to the appropriate module and install apache and the required modules:

root@httpd:~ # SUSEConnect -d -p sle-module-web-scripting/15.4/x86_64
root@httpd:~ # zypper in apache2-mod_auth_openidc

Apache server will be installed automatically as a dependency.

I found that installing PHP and using phpinfo(); helped me a lot to debug the configuration. You are not required to install it if you are using a working solution. Optional:

root@httpd:~ # zypper in php apache2-mod_php7

Set up an SSL virtual host by copying the template and modifying it.

root@httpd: # cd /etc/apache2/vhosts.d
root@httpd:/etc/apache2/vhosts.d # ll
total 12
-rw-r--r-- 1 root root 1754 Jun 14  2022 vhost-ssl.template
-rw-r--r-- 1 root root 4815 Jun 14  2022 vhost.template
root@httpd:/etc/apache2/vhosts.d # cp vhost-ssl.template 00.httpd.mydomain.com.conf
root@httpd:/etc/apache2/vhosts.d # vi 00.httpd.mydomain.com.conf
root@httpd:/etc/apache2/vhosts.d # egrep -v "#|^$" 00.httpd.mydomain.com.conf
<IfDefine SSL>
<IfDefine !NOSSL>
<VirtualHost _default_:443>
        DocumentRoot "/srv/www/htdocs"
        ErrorLog /var/log/apache2/error_log
        TransferLog /var/log/apache2/access_log
        SSLEngine on
        SSLUseStapling  on
        SSLCertificateFile /etc/apache2/ssl.crt/httpd.mydomain.com.crt
        SSLCertificateKeyFile /etc/apache2/ssl.key/httpd.mydomain.com.key
        CustomLog /var/log/apache2/ssl_request_log   ssl_combined
</VirtualHost>
</IfDefine>
</IfDefine>

Place server certificate and key in the locations specified in the configuration file.

root@httpd:~ # cp httpd.mydomain.com.crt /etc/apache2/ssl.crt/
root@httpd:~ # cp httpd.mydomain.com.key /etc/apache2/ssl.key/

Edit the APACHE_SERVER_FLAGS="SSL" line in the /etc/sysconfig/apache2 file, then start the server:

root@httpd:/etc/apache2/vhosts.d # systemctl enable --now apache2
Created symlink /etc/systemd/system/httpd.service → /usr/lib/systemd/system/apache2.service.
Created symlink /etc/systemd/system/apache.service → /usr/lib/systemd/system/apache2.service.
Created symlink /etc/systemd/system/multi-user.target.wants/apache2.service → /usr/lib/systemd/system/apache2.service.

If you don't want your server to listen on port 80 at all, comment out the Listen 80 line in the /etc/apache2/listen.conf file.

Make some content

To check access restrictions, we need some directories and files. Let's create them like this:

root@httpd:~ # echo '<html><body><h1>Public area!</h1>Welcome everyone</body></html>' > /srv/www/htdocs/index.html
root@httpd:~ # mkdir /srv/www/htdocs/{managers,users}
root@httpd:~ # echo '<html><body><h1>Restricted area!</h1>Welcome managers</body></html>' > /srv/www/htdocs/managers/index.html
root@httpd:~ # echo '<html><body><h1>Restricted area!</h1>Welcome registered users</body></html>' > /srv/www/htdocs/users/index.htm
Check that you can access to https://httpd.mydomain.com/ , https://httpd.mydomain.com/managers/ and https://httpd.mydomain.com/users/ . You should be successful without any authentication and certificate warnings.

Realm definition in Keycloak

Create a new realm or use an existing one? You can think of a realm as a single source of authority, such as Active Directory, with multiple services connected as "Clients" with individual fine-tuning. Or you can think that realm is service-oriented and acts like a proxy for various authorities such as Google, Facebook and others.

In this simple example, we'll create a new realm called mydomain and populate it with a couple of users and groups.

To create a new realm, click on the top leftmost drop-down menu. For a newly installed system, it says "master". Select and click the "Create Realm" button. The only required parameter is the name. The "Create" button will do its work. The created realm is added to the drop-down menu in the upper left corner. Select it to continue working.

Next, "Create group" at "Groups" page. Name the first one as "users" and the second one as "managers". After finishing the tests described here, I found that the "users" group is useless, because access is limited to membership in the "managers" group only.

Create a user "u1" belonging to the "users" group and a user "m1" belonging to the group "managers". Set a password for both users (on the "Credentials" tab) and deselecting the "Temporary" toggle.

If you click "Clients", you will see a list of default clients and links to test your users. For example https://kc.mydomain.com/realms/mydomain/account/. Open it in a "Private" Firefox or Chrome session. Use the logins of the newly created users. You should be able to log in and even see some information about yourself.

The next step is to create a "Client", which will be our HTTPD server. Click on "Create client" at "Clients" page. The type should be "OpenID Connect", because we are going to use mod_auth_openidc. Again, the required field is only the name or "Client ID", which must uniquely identify the service. It's a good habit to call it the DNS name of the service or server. In our case it is httpd.mydomain.com. Put https://httpd.mydomain.com as "Root URL" and "Home URL". Put https://httpd.mydomain.com/* as "Valid redirect URIs". Turn on "Client authentication" during creation or later after creation.

Once "Client authentication" enabled , an additional tab "Credentials" appear. You can find there a "Client secret".

Add Keycloak authentication to Apache server

Correct our /etc/apache2/vhosts.d/00.httpd.mydomain.com.conf file to include authentication staff:

	# This copied from Keycloak "Realm settings" -> "General" -> "Endpoints" -> "OpenID Endpoint Configuration":
        OIDCProviderMetadataURL https://kc.mydomain.com/realms/mydomain/.well-known/openid-configuration 
        OIDCCryptoPassphrase 46272d54-1b7c-4bdf-bbb1-a67dd1c31200 # <- free type string generated by uuidgen.
        OIDCClientID httpd.mydomain.com	# <- this should match to client ID we've created.
        OIDCClientSecret mJZIb7Gu8sfzUuvD2DVvBoTmNojWASXm # <- Copy it from client creadentials
        OIDCProviderTokenEndpointAuth client_secret_basic
	OIDCRedirectURI /users/callback
	# LogLevel debug
        <Location /users>
                AuthType openid-connect
                Require valid-user
        </location>

The value of OIDCRedirectURI must not point to a real file. This is a completely fake URL that will be served by mod_auth_openidc. However, this URI must be in a protected area in order for apache to request the auth_openidc module first.

Also, you must enable the module itself. In SUSE, this is done by adding auth_openidc to the APACHE_MODULES= line in /etc/sysconfig/apache2.

Alternatively you can run the command:

root@httpd: # a2enmod auth_openidc

Then reload or restart apache. Check that you must be logged in with any user to access https://httpd.mydomain.com/users/.

Miltiple protected locations and group based access

Add the following definitions after previous changes:

        <Location /managers>
                AuthType openid-connect
                Require claim groups:managers
        </location>

Note that no additional definition for OIDCRedirectURI is required, although the auth_openidc module now protects one more additional location. The reply will come to the previous definition and still get to the module, which was requested.

The "Require claim" directive is serviced by the auth_openidc module and matches against the token received from Keycloak. The default token does not include group information, so we must add one. These additional definitions are highly dependent on the client's requirements, so they are customized on a per-client basis.

In Keycloak, in the correct realm, click "Clients". Find our client and click on it. The place where you can customize the contents of the token is called "Client Scopes". There are a number of built-in defaults and one "ClientName-dedicated" that you can customize. Click on it. Mapper is a pair of the original attribute with an attribute that is rendered to the client. There are some predefined mappers, but there is no groups, so click "Configure a new mapper". Select "Group Membership" and name it "groups". "Token Claim Name" will be the name of the attribute in the token. We used "Require claim groups:" in our Apache config, then "Token Claim Name" should be "groups". In most cases, disable "Full group path". It is useful to distinguish between subgroups of the same name, for example, between /finance/managers and /hr/managers. In this case, you must compare the full paths of the groups in your apache config. "Save" changes and you are ready to test https://httpd.mydomain.com/managers/ access. Obviously, the only user "m1" should be able to connect.

While playing with groups, I found Keycloak to be more "Roles" oriented. As proof, you have a predefined mapper for Roles and not for Groups. The reason is that you can apply a role to multiple groups and thus allow access fine-tuning on the Keycloak side.

Debugging

As usual nothing works when you are trying to start something new. For example, apache failed to start after your configuration changes. To check the cause, it is enough:

root@httpd: # journalctl -u apache2.service

If everything looks good but nothing still works, turning on debug logging will help. Edit the apache config and add "LogLevel debug" to it. Restart apache. Then:

root@httpd: # tail -f /var/log/apache2/*log
and try authentication again. Each server component and module will write its own steps and decisions.

After simple authentication worked, installing php and getting phpinfo(); was very useful for debugging the contents of the token. There is a set of variables starting with OIDC_CLAIM_ that are defined by the auth_openidc module. This prefix is configurable if you don't like the defaults. The source of all variables is an OIDC_access_token in the form HEADER.JSONTOKEN.SIGNATURE. All parts are base64 encoded. HEADER describes the signing method, SIGNATURE confirms the integrity of the JSONTOKEN. The contents of the token can be viewed with:

root@httpd: # echo JSONTOKEN | base64 -d
- or in pretty view
root@httpd: # echo JSONTOKEN | base64 -d | jq . 

Configure self-registration

So far, we haven't gotten anything other than the simplest .htpasswd behaviour. You can try enabling user registration at Keycloak. To do this, it is enough to configure e-mail delivery. The further configuration is so simple that it is unnecessary to describe it.


Updated on Sat Jan 14 11:32:22 IST 2023 More documentations here