Setup Keycloak & Oauth2-Proxy via Docker Compose & NPM (Nginx Proxy Manager)

Keycloak is a well developed and solid self-hosted authentication system that comes with great features and even has some nice community extensions. Though, the one thing it doesn't handle built-in is being an all-in-one proxy service (such as what Authentik does).

Thus, we have to use an external tool or service to handle not just proxy requests, but authentication / authenticated proxy requests. For this, we'll be use a small but very handle tool called Oauth2-Proxy.

This little service sits inbetween your desired app or service that doesn't have built-in Oauth2 security, and 'wraps' around like the service like a glove and becomes a proxy with forced authentication (if told to do so).

Here's the link to the self-hosted service:

On their site you'll find their documentation as well as their GitHub link.

Tools

For this setup to work, you'll need the following:

  • Keycloak
  • OAuth2 Proxy
  • Docker Compose
  • A proxy service, such as NGINX via NGINX Proxy Manager (NPM)
  • Redis

This could all work without docker and by using something other than NPM, so feel free to adjust all of or any of this to match your needs and configuration.

Before you begin

This process will probably take you anywhere between 20 minutes to an hour depending on how knowleadable you are with all these topics and tools. It took me a good while to figure out how to get all the pieces working together properly, so don't fret if your setup takes a while to tinker with as well.

Ensure that you have a (sub) domain ready as well as your choice of proxy service available and handy. While Docker compose is what I used here, this certainly works on a bare-metal / non-docker install.

You will want either-or docker-compose (the app) or docker compose (the docker plugin); both will work just fine for this and make it much easier to work with rather than pure docker commands. This can also most likely work via a visual app like Portainer but I've found docker compose files easier to work with and maintain.

Having something like NPM is likewise important to help ease the actual domain setup required to make this work. You can use NGINX and create your own domain and vhost files and config, or use Apache or even Treafix. Though my preference, so far, is Nginx managed via NPM as it has a visual interface and works well (for me).

As such, in this article I'm going to assume you already have a domain, a working vhost setup and know how to edit it and know how to use docker / docker compose.

Process overview

Getting this to all work has three high level steps:

  1. Configure OAuth2 Proxy
  2. Add appropriate oauth2-proxy info into Keycloak (explained below)
  3. Update vhost configuration to support authentication and redirects

Step 1. Keycloak with OAuth2 Proxy as new Client

Inside of your Keycloaks (non-admin) realm, setup OAuth2 Proxy as a new client (assuming you haven't already). Thankfully the instructions are documented rather well here and are very simple :

I would to point out two items, first here is a copy of the official steps (which are correct and simple):

  1. Create new client in your Keycloak realm with Access Type 'confidental' and Valid Redirect URIs 'https://internal.yourcompany.com/oauth2/callback'
  2. Take note of the Secret in the credential tab of the client
  3. Create a mapper with Mapper Type 'Group Membership' and Token Claim Name 'groups'.

The second item is I want to clarify is three things for you:

A) The Valid Redirect URIs has to be set PER APP/SERVICE you want to have this proxy service working on/around. Meaning: you only need to do the main setup once, but you will need to log in into Keycloak and add a new Valid Redirect URI into the oauth2-proxy client (in keycloak).

B) This URI is of the format:

https://SUBDOMAIN-IF-ANY.YOUR-DOMAIN-HERE.EXTENSION/oauth2/callback

subdomain could be www if that floats your boat, but in my case all my services run on the same main domain but on their own subdomain such as: project, chat, tracker, storage and the like. So, I 'wrap' each sub-domain individually with this OAuth proxy and thus each has to be added individually to the keycloak client's redirect uri section.

extension simply being the .com / .net / .io or whatever your domain is.

👉
Mini clarification on the /oauth2/route: this doesn't have to be "oauth2", you can set that to whatever you want ... as long as you're consistent with it. I used oauth2 becausethat's what the docs said and logically the name works for me. But feelfree to set your own route if you'd like. BUT the /callback part cannot be changed, so keep that part as is.

C) From their step 2, keep that client secret very handy
(such as saved in a text file temporarily) as you'll be using it in the next step which is to configure the OAuth2 proxy.

Step 2.  OAuth2 Proxy

OAuth2 Proxy has a handy official docker image, though it wasn't working for me so I used the Bitnami image instead:

bitnami/oauth2-proxy:latest

Just starting this container as it will help us in this situation as there is no visual interface nor command line tool to edit it. This container is setup via both environment variables and customizing the command input. Why both?

For whatever reason, I couldn't get either docker image to recognize and thus adjust all the options I wanted with just the environment vars. Thus, I went with the solution that worked for me the fastest to get this running.

First, here is the docker compose file, after that I'll explain the options:

version: "3"

services:
  oauth2-proxy:
    container_name: oauth2-proxy
    image: bitnami/oauth2-proxy:latest
    command:
      - --upstream=http://npm_container_name:80
      - --http-address=0.0.0.0:4180
      - --reverse-proxy=true
      - --cookie-httponly=true
      - --cookie-secure=true
      - --cookie-domain=.whatevermydomainishere.com
      - --whitelist-domain=[.whatevermydomainishere.com,.otherdomain.com,.foobardomain.io]]
      - --request-logging=false
      - --auth-logging=false
      - --standard-logging=true
      - --session-store-type=redis
      - --redis-connection-url=redis://a_redis_container_name/2
    entrypoint:
      - oauth2-proxy
    environment:
      - OAUTH2_PROXY_CLIENT_ID=oauth2-proxy
      - OAUTH2_PROXY_CLIENT_SECRET=PUT_SECRET_HERE
      - OAUTH2_PROXY_COOKIE_SECRET=ANOTHER_DIFFERENT_SECRET_HERE
      - OAUTH2_PROXY_EMAIL_DOMAINS=mydomainhere.com
      - OAUTH2_PROXY_PROVIDER=keycloak-oidc
      - OAUTH2_PROXY_OIDC_ISSUER_URL=https://keycloak.mydomain.site/realms/myrealnamehere
      - OAUTH2_PROXY_HTTP_ADDRESS=0.0.0.0:8097
      - http_address=0.0.0.0:8097

networks:
  default:
      name: npm_default

OAuth2 Proxy Environment Variables

OAUTH2_PROXY_CLIENT_ID is the 'id' you will be passing into KeyCloak. This here and in Keycloak should be the same thing. So keep this info handy

OAUTH2_PROXY_CLIENT_SECRET and OAUTH2_PROXY_COOKIE_SECRET should be randomly generated secret keys. These help with security and encryption. They should be different values from each other.

OAUTH2_PROXY_EMAIL_DOMAINS the domain where emails will come from and thus this app is approved to send from

OAUTH2_PROXY_PROVIDER Here we set the actual provider we'll be using, in this case Keycloak

OAUTH2_PROXY_OIDC_ISSUER_URL The full url of the REALM in Keycloak that will be doing said authentication via OAuth2 Proxy.

OAUTH2_PROXY_HTTP_ADDRESS Address and port this service will be deployed on when using the proxy service (more on this later)

http_address The URL this service will be accessed, should be identical to the proxy address. I don't remember why I had to set it, but I did and it works so it says (for me). Fore on this item below

OAuth2 Proxy Command options

upstream Where to look for getting domain info and redirection to/from. In my case, this was the NGINX Proxy Manager docker container which serves up Nginx via port 80

http-address This is the actual local IP address and port (assuming it's being deployed to be using 'via' local access only (such as behind a proxy). I've set it to accept connections on all IP addresses at port 4180. That's where we 'access' OAuth2 Proxy on.

reverse-proxy This is set to true so that the app understands that it'll be running and working in conjunction with a seperate proxy service, in this case NGINX.

cookie-httponly and cookie-secure are true so that we use cookies!

cookie-domain setting the domain the cookie will write as.

whitelist-domain These are the domains allowed to use this authentication service. In my case, I run most things via sub-domains, and thus I only had to add the main domains while specifying they had sub-d0mains (notice the . in-front of each domain).

request-logging auth-logging are false as I don't need extra logging

standard-logging This is the only logging I need in production

session-store-type is set to redis as the standard cookie session storing method will not work in many cases. Storing the session information is Redis is the only proper way (with this app) to have it store the large amount of data needed and keep working. If you use the default methods, it will fail and error out tell you each authentation session does not have enough memory availble and/or that the data being passed via cookies is too large and cannot be stored (and thus won't work). Thus you need redis in-order for this to work as it can store the data we need.

redis-connection-url Put your redis connection string here. In my example, I use redis database 2 as in my case, database-1 was being used by something else.

OAuth2 Proxy Network

As this service needs to connect and talk to Nginx, it has to be on the same network (in Docker) as NPM. Technically speaking, in this setup OAuth2 Proxy is never 'actually' public in and of itself, instead it's all fully routed to via NPM ... and thus, our NPM docker instance needs to be able to connect to it via the internal private network.

Thus, I've set both the NPM docker instance and OAuth2 Proxy instance to be on the same network to talk to each other.

Step 3. Put OAuth2 Proxy Live

Using docker compose (or other) bring the service online. Keep an eye out on the logs to ensure it's starting properly and no errors are reported. If you followed the above and have all your settings right it should start properly.

Assuming it's running without errors, it's time to attach a (sub) domain to it. And yes, it does need to be outward facing so that whatever app/service you're using can connect to it to authenticate. As all authentication is initially done via a proxy authentication behind the scenes, this service needs to be facing the public.

Thankfully, all that you need to bring this live is to give it a vhost and point a (sub) domain towards it. It does need: SSL and just in case I do have web-sockets enabled.

In our case, I've pointed the vhost to the docker container name and internal port (see above configuration options for that info).

If you try to visit this page directly, it will not work as there is no 'interface' for humans for this. This service runs behind the scenes so there's nothing to see as such.

Step 4. Adjust You Service's Domain To Use Auth

Now that you have OAuth2 Proxy running, Keycloak is connected up to it as a client app and it has a domain ... it's time to adjust your app / service domain and vhost settings to use authentication.

There are two things to change, but before we go into that let me explain an important bit.

👉
Thevhost/domain should stay pointed to the app directly. Authentication ishandled 'behind the scenes' with a secondary HTTP call from your connection to the authentication service. The settings we will be putting in below will be the things handling the auth redirects.

As a small reminder, this needs to be done for any/every domain and service you want to use this authentication.

If you haven't already setup your regular vhost settings for this domain, do so now. What we have to do for authentication to work is setup two (2) additional locations for this vhost

/
/oauth2

The  / location is to setup the authentication checking.  If you're using NPM then add the following settings in the advanced tab for this additional location. If you're using vhost's directly, then you can simply add it into the location settings.

👉
The /location needs to be setup in-addition to the regular (normal) setup for this vhost. The information cannot go into the 'advanced' tab for the whole (sub) doamin as this breaks authentication rules, so this is why inside of the sub-domain vhost we setup a root (/) location and add the auth redirect settings to just that.
auth_request /oauth2/auth;
error_page 401 = /oauth2/start; 
auth_request_set $token  $upstream_http_x_auth_request_access_token;
proxy_set_header X-Access-Token $token;

Setup domain OAuth route

Next, you'll want to create one additional location: /oauth2

This is a simple location that points to the OAuth2 Proxy service (internally in my case. In the following screenshot ignore that I have a second path in that as I had that setup incorrectly before I cleaned things up though the main oauth location is correct:

The Scheme should be set to whatever the final destination container or app uses on when talking to NGINX normally. In this case: via HTTP. Nginx handles HTTPS between the user and the app, but between Nginx and the app on the internal network it is just http.

Set Forward Hostname / IP to be the address of the container or app (be it local or otherwise). In my case, I used the container name.

Forward Port is simply the port the container or apps uses.

The /oauth2 path (locations) does not require any settings other than pointing it to the oauth2-proxy app or instance.

🥰
Thesetwo locations must be setup for EVERY domain or sub-domain you wish to use authentication on (plus adding that callback uri to keycloak).

Step 5. Enjoy Domain Level Authentication

Assuming there were no errors during setup, everything should be working now. If you go to your (sub) domain, it should now ask you to log in and authenticate via Keycloak.

Let me know in the comments if this has worked for you and if you any ideas on how to improve this process.

Great! Next, complete checkout for full access to Piotr Krzyzek.
Welcome back! You've successfully signed in.
You've successfully subscribed to Piotr Krzyzek.
Success! Your account is fully activated, you now have access to all content.
Success! Your billing info has been updated.
Your billing was not updated.