Apache OpenID authentication with Azure

Matthias Fleschütz
9 min readJan 1, 2021

Well, why should I write an article about how to configure Apache with Azure AD authentication? There are some reasons, as I struggled quite a while myself configuring an application that way:

  1. Microsoft’s documentation is very generic, so it is hard to find specific documentations how to solve this issue
  2. The documentation of mod_auth_openidc (which seems to be the module of choice for Apache and OpenId) is unfortunately not that detailed and at some points not correct when it comes to actual Azure (portal) versions
  3. Even the best tutorial how to set up this configuration seems to be outdated and at some point too short and too little detailed
  4. To be mentioned: yes, nginx is much cooler and modern and bla bla bla…but: there are several applications out there which still have some issues to be used with nginx and several “old-school” engineers and admins maybe still feel more comfortable with Apache (which, honestly, has proven to be really sufficient and great for decades)
  5. …I needed some reason to publish my very first article at medium.com ;-)
Photo by Markus Spiske on Unsplash

TL;DR

  • at latest Ubuntu version you simply need to install the package libapache2-mod-auth-openidc
  • register a new app in Azure (not an “Enterprise Application”!) and be aware of changed actions in Azure Portal
  • add Redirect URI of registered app, matching the OIDCRedirectUri on your server
  • change Token Configuration of registered app and add optional claims upn, email (I recommend groups claims as well)
  • check the API permissions to have at least User.Read, profile, email
  • configure the module on your server and add OIDCRedirectURI, OIDCCryptoPassphrase, OIDCProviderMetadataURL, OIDCClientID and OIDCClientSecret. Set OIDCProviderAuthRequestMethod to POST, set OIDCRemoteUserClaim to upn. Adjust OIDCCookieDomain to your external servers FQDN. Increase OIDCCacheShmEntrySizeMax to a reasonable high value (I took 66065).
  • Depending to your application it might be necessary to include OIDCAuthNHeader and set it to a reasonable value (e.g. X-Forwarded-User)

Why Using Apache Authentication Module?

There are tons of applications out there which do not have there own SSO authentication module or just legacy protocols as LDAP. And not to forget all those nitty-gritty custom applications that were developed and deployed without any authentication at all.

If you want to put them on some cloud-based servers and rely on those fancy new authentication and authorization protocols you are really lost, especially if you are not a developer.

Good for us that Apache offers plenty of authentication modules that just need to be installed and configured for your needs. If they are not passed, the requesting user will receive a 401 — Unauthorized message.

Error 401 — Unauthorized message from Apache server

Additionally those modules add session information so your application may have chance to read values like authenticated user’s id, name, email and so on, depending on used module and capabilities of your application.

In our case we had a monitoring application which could make use of LDAP for single sign on, but did not support any modern authentication protocols. When we decided to move this server into our Azure environment, we also wanted to use Azure Active Directory to check user logins.
We could have used LDAP connected via VPN from on-premise to Azure but as soon as connection to our on-premise environment would fail, no logins would be possible anymore. Especially in case of issues and failures not a good combination to start investigations.

But which Apache module to use with Azure AD?

mod_auth_openidc

After looking around I quickly found mod_auth_openidc of ZmartZone IAM which convinced me with impressive flexibility regarding numerous Identity Providers (IdP). It turns your Apache-based application into a “Relying Party” (RP) easily.

It authenticates users against an OpenID Connect Provider, receives user identity information from the OP in a so called ID Token and passes on the identity information (a.k.a. claims) in the ID Token to applications hosted and protected by the Apache web server.
The protected content and/or applications can be served by the Apache server itself or it can be served from elsewhere when Apache is configured as a Reverse Proxy in front of the origin server(s).

It supports plenty of specifications, is OpenID certified and for those who are interested: even commercial support is available.

Register Application in Azure

  1. Login to Azure Portal
  2. Register a new app in Azure
    Navigate to “App Registrations”, click on “New registration”.
    Enter a name and select the supported account types of your needs (for our use we chose “Single tenant”).
    For now we leave the “Redirect URI” empty as this depends on your application.
  3. Add Client Secret
    After adding the app registration, go to “Certificates & secrets” and click on “New client secret”.
    Chose an expiration period and click “Add”.
    Save the value displayed for later configuration of the Apache module.
  4. Token Configuration
    Add optional claims for Token Type “Id” and select email and upn.
    Add groups claim with “Id”, “Access” and “SAML” set to “Group Id”. Accept permissions that need to be added to this app.
  5. Review API Permissions
    Select “API Permissions” of the registered app. Make sure that the following three API permissions are added:
    → Microsoft Graph > email
    → Microsoft Graph > profile
    → Microsoft Graph > User.Read
  6. Save GUIDs for module configuration
    Go to “Overview” of the registered app and write down “Application (client) ID” and “Directory (tenant) ID”.
    We need those GUIDs together with the client secret already saved in step 3 for the module configuration.

Redirect URI

As you remember we left out the configuration of “Redirect URI” in our app registration in Azure. This configuration value depends heavily on the application you want to secure.
In this article I will focus on standard web applications, not on modern Single-Page-Applications (SPA) or direct application implementations.

This configuration value must match the configuration of Apache module directive OIDCRedirectURI.
One limitation of mod_auth_openidc is that the redirect URI must not be addressed directly. So if you want to secure the folder “/myapp” and this URL may be entered by your users in their browser it will not work.
Whether you need to implement an Apache-based Rewrite or you have to use a different RedirectURI, e.g. /myapp/login

The redirect_uri for this OpenID Connect client; this is a vanity URL that must ONLY point to a path on your server protected by this module but it must NOT point to any actual content that needs to be served.

  1. Go to “Authentication” of the registered app
  2. Click on “Add platform” and select “Web”
  3. Enter the Redirect URI and click on configure

Install Apache Module

At this point I assume that you are familiar with general Apache setup, SSL configuration and so on…

The most easy way is to install the default Ubuntu package, supplied by the system.

$ apt install libapache2-mod-auth-openidc

When writing this for Ubuntu 20.04 LTS the package version is 2.4.1, so quite up-to-date as the 2.4.5 is latest and only minor changes seem to be added in the meantime.

Alternatively you can install it manually from module’s GitHub page or compile it from source. But when doing so be aware of library version dependencies.

Configure mod_auth_openidc

If you have installed the module from distribution package no further steps should be necessary. If you have installed it by a different method the module maybe needs to be enabled and the configuration file may be different.

By default mod_auth_openidc places a separate configuration file for the module. It contains all possible configuration directives and explanations. All settings placed in this file will be used by the whole Apache Instance / default VirtualHost. Some of them can be overwritten for individual VirtualHosts or even Locations.

Edit the modules configuration file at /etc/apache2/mods-enabled/auth_openidc.conf and perform the following configurations.

  • OIDCRedirectURI
    As mentioned before this value must match the Redirect URI already configured before (see above).
  • OIDCCryptoPassphrase
    This is just a random string that is used by mod_auth_openidc to encrypt (temporary) state cookies and cache entries. I will not explain here why passphrases should be long and most random with use of as many different characters as possible.
  • OIDCProviderMetadataURL
    A mandatory setting which is pointing to a .well-known-URI in Azure. To put it the simple way: Microsoft is publishing the supported configuration values of your tenant: https://login.microsoftonline.com/<YOUR-TENANT-GUID>/.well-known/openid-configuration
    You hopefully saved the GUID of your tenant when registering the app (see above). If not, check the “Overview” page of the registered app.
  • OIDCProviderAuthRequestMethod
    Set this value to POST. If you forget this, you will receive an error on login: “The endpoint only accepts POST requests. Received a GET request.
  • OIDCClientID
    The unique id (GUID) of the registered app, you also saved before.
  • OIDCClientSecret
    The client secret you have created before in Azure for the registered app.
  • OIDCCookieDomain
    This setting is optional but very useful if the external address you use to access your application differs from the name of the Apache webserver. In our case we use DNS CNAME records for the application (considered as a service) that is served by a machine (hostname).
  • OIDCStateMaxNumberOfCookies
    When digging through the first issues and nearly resigned I came along the Cookies FAQ of mod_auth_openidc. The default configuration will save seven state sessions and will not delete the oldest ones. Within the FAQ Hans Zandbelt (Founder of ZmartZone) recommends to set OIDCStateMaxNumberOfCookies 7 true so the module will delete the oldest ones.
  • OIDCCacheShmEntrySizeMax
    Depending on the information passed by the IdP in the Token the memory size needed may be larger as the default value (16913 bytes), especially including group membership information may enlarge the footprint of your token quite a lot. Set a reasonable value, in our case I used 66065 bytes (65536 bytes + 512 bytes key + 17 bytes overhead).
  • OIDCRemoteUserClaim
    Set the OIDCRemoteUserClaim upn. mod_auth_openidc uses “sub” as default, but this is a not really human readable string. Especially if passed to your application you will not really like the result “Eo6WGomm0Az6tLZ2QOOV2qNkHKLLVkkhr-RnPTpAy3Y@sts.windows.net/af2835a4–2e34–49e1–96de-6a16fce3ac3c”…
    I decided to use “upn” instead of “email” for “standardization reasons”: Yes, the email is a globally unique identifier. Microsoft also says that in Microsoft 365 the primary email address should always be the same as the username for several reasons. But technically the unique user name is not the email (in on-premise Active Directory this is usually the userPrincipalName).
  • OIDCAuthNHeader
    This is an optional setting that again depends on your application. For our application we had to set X-Forwarded-User.

Restart the Apache (usually by $ systemctl restart apache2).

At this point you have integrated mod_auth_openidc but Apache still needs to know which locations (or “folders”) need to be secured by OpenID Connect.

Secure Your Application

Edit the configuration file which holds the Apache directive values for your application and add the following directives on that level:

<Location /myapp>
AuthType openid-connect
Require valid-user
Order allow,deny
Allow from all

SSLRequireSSL
SSLOptions +StdEnvVars
</Location>

In our case I decided to require only a valid (authenticated) user as the application itself had some internal access management. With this standard approach you can choose to separate address paths and require dedicated user names (require user my.user@tenantdomain.tld).

Special Case JavaScript

When following the recommendations of Cookies FAQ, you should separate the locations of your JavaScript and set the UnAuthAction to 401. This will manage that only a HTTP response code “401 Unauthorized” is sent to the client.

<Location /myapp/js>
OIDCUnAuthAction 401
</Location>

Restart your Apache now:

$ systemctl restart apache2

That’s it! It should work now. No? Well…I am sorry but now you really have to dig into the dirt as I did.

TODO: Group Membership in Apache

Some domain I did not need to touch so far: checking group memberships of the authenticated user for authorization based on the Apache configuration (require group …).
Check out this Wiki page for further authorization hints on setting up requirements on claim information.

Troubleshooting

Unfortunately OpenID Connect and Azure configuration can be really a mess and time consuming. Additionally because of the different providers and applications there is no one-way which is straight-forward…

The good side is that there are some resources and configurations that might help out:

  • Debug LogLevel
    One of the basic principals but still often forgotten: activate Apache Debug Loglevel! Just put the directive LogLevel debug in the scope of your virtual host which serves your content.
  • Wiki, Issues and Discussions
    As mod_auth_openidc is hosted on GitHub, check the Wiki, the Issues and Discussions. I spent about two hours digging through those resources. There is also a Google Groups at mod_auth_openidc@googlegroups.com
    I couldn’t find a direct solution for my exact problem, but I could gather that many knowledge to resolv the issue myself.
  • Commercial Support
    If nothing helps or you have to ensure stable support conditions, ZmartZone is offering commercial support (no, I do NOT have any stakes there, I do not even know them).

--

--

Matthias Fleschütz

20+ yrs. IT administration, management and consulting experience. Project management, old-fashioned and agile. But also interested in politics…