Retrofitting OIDC to legacy systems via reverse proxy
Obviously we want systems that use modern authentication. In the case of Azure AD, you get the good stuff, like Conditional Access and a whole host of new authentication mechanisms, like Windows Hello & FIDO2.
This is a big jump, however, especially for old apps. And by old, I mean old - running on unsupported or deprecated platforms (or versions of those platforms), where the skillset doesn’t exist in your circle to update it, or where the vendor who created the platform has ceased to exist. Consider too that some legacy systems may be on-deck for total replacement, either with custom dev or off-the-shelf systems, where we want to keep costs as low as possible.
Like most modernization projects, there are different ways to approach the problem - in this case, I’m focused on the ‘moat-building’ method, where we dig out and isolate the problem systems and proxy access through more modern systems. This is a bit quick-and-dirty, but for systems that are slated for replacement or just can’t be touched, it’s a low-impact way to wrap new functionality around an old problem. You can apply this to all sorts of parts of a system as well - e.g., extracting and shipping data out of an old system into a new system and schema while slowly moving referencing bits over to the new system.
Let’s dig in.
Since we’ve got a web app and we want to add only authentication, it’s relatively straightforward. We need to:
- Authenticate the user, using a typical oidc-tango
- Redirect to identity provider
- Consume and validate issued token
- Read claim data
- Transform some claim data before forwarding along
- In our specific case, we need specific values from the claims to be forwarded in a specific header
Our layout is straightforward. We have our original old app (we’ll call it
old-app.example.com) and our front-end (
myapp.example.com). We’ll need to make sure DNS resolves on the Apache host for
old-app.example.com and that our old app server is configured for that host header or to accept all names. If it’s not in your local DNS, you could add it to
/etc/hosts for local resolution.
Next we’ll want to make sure
myapp.example.com resolves to our Apache server. You would likely want to use named virtual hosts here, although you could get away with
* if you wanted. My virtual host on
:80 is just redirecting to
:443 - yours may need to do something different.
Lastly you’ll want to make sure you have any TLS certs available and installed on the Apache host.
I’m using Apache 2.4.* on Ubuntu 18.04, with binaries for
mod_auth_openidc 2.3.3, which is available in the universe repositories.
Since we’re usurping the user path to the app, we’ll need to make sure we manipulate the network environment & DNS in a way to make the app either inaccessible or unusable if someone was to hit it directly. If hosted in Azure, this could be something like a two-subnet VNet, one subnet with the app and the other with the proxy, with NSGs locking down the app subnet to only allow traffic from the proxy subnet. On-prem there are myraid ways to segment networks and restrict access.
You’ll also want to host with TLS, Azure AD reply URLs will require
https except in the case of localhost.
Let’s take a look at the config.
mod_auth_openidc package includes all the claims as passthrough headers, in addition to our custom header with our transformed value. My source claim in this case was
preferred_username, which we transformed via apache to
The advantage to this method is potentially many apps could live behind this proxy, with very little additional effort to onboard more. Of course the tradeoff with proxying is a single choke point for traffic, so carefully consider which apps should be grouped behind specific instances.