Running KeyCloak on Azure App Service and Azure SQL using managed identities
I’ve recently been seeing a lot of developers diving into the world of authentication and asking for recommendations on what to use as an Identity Provider (IdP). There’s plenty of IdP out there. If you’ve been following me, I’ve done some posts on Identity Server (before it was Duende). I’m a big fan of Microsoft Entra (for the workforce) and Entra External ID (successor of Azure AD B2C), for external identities not related to your organization, for it’s simplicity and for it’s SaaS abilities, as IdPs are, in my eyes, mission critical parts of your ecosystem: if it goes down, your whole system is down. Any SaaS IdP allows me to focus on my application and not managing an IdP along with not having to think of the pillars of the WAF (Well Architected Framework) for this IdP in question, that is Reliability, Security, Cost Optimization, Operational Excellence, and Performance Efficiency. I do, however, recognize that some organization have different requirements and I would like to show you today how you can run KeyCloak, on Azure App Service, using containers, without having to take out the big guns and deploy a big Azure Kubernetes Cluster (AKS).
Before we begin, there’s a few considerations I would like you to be aware of:
- The steps highlighted here do no take in consideration some of the WAF pillars, such as Reliability, Performance Efficiency and so on. You will need to make sure to have all of those checked and followed if you plan on going into production with this setup.
- Managed Identities for Azure in KeyCloak is not built-in. As I will show below, you will need to build and maintain a custom image of KeyCloak which contains the required packages for MSI (Managed System identities, which is Managed Identities old name) to work.
Building the custom image
As mentioned above, while KeyCloak does support natively MSSQL, it, however, does not have the necessary packages to do the Managed Identities authentication using the JDBC connection string as shown in Microsoft’s documentation. This is, I believe and my humble opinion, because the team does not want to add extra packages to the image, packages that may change or even bloat the image. This would mean that they would have to be responsible for keeping them up to date. The KeyCloak team aren’t Azure experts and thus this could become a burden for them to maintain long term. As shown in their documentation, you can customize/optimize KeyCloak to add anything that is missing. As such, we will create a Dockerfile which will install what is required for MSAL to work with Managed Identities.
All the following was discussed and part of the GitHub discussion in KeyCloak’s GitHub repository
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
ARG APP_VERSION=latest FROM maven:3.8-eclipse-temurin-17-alpine AS dependency-builder COPY pom.xml pom.xml RUN mvn dependency:copy-dependencies -DoutputDirectory=/tmp/dependencies FROM quay.io/keycloak/keycloak:${APP_VERSION} AS builder COPY --from=dependency-builder /tmp/dependencies/* /opt/keycloak/providers/ WORKDIR /opt/keycloak ENV KC_DB=mssql RUN /opt/keycloak/bin/kc.sh build FROM quay.io/keycloak/keycloak:${APP_VERSION} COPY --from=builder /opt/keycloak/ /opt/keycloak/ ENTRYPOINT ["/opt/keycloak/bin/kc.sh"] |
Note the environment variable KC_DB which instructs KeyCloak which database vendor to use. In our case, since we’re using Azure SQL, we set it to MSSQL.
In order to install the missing packages, we need a pom.xml
, as we’re relying on maven to install what is missing.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <!-- see https://github.com/keycloak/keycloak/issues/22830#issuecomment-1812546369 and https://github.com/keycloak/keycloak/issues/22830#issuecomment-1857498127 --> <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.custom.keycloak</groupId> <artifactId>keycloak-dependencies</artifactId> <version>1.0</version> <packaging>jar</packaging> <name>Keycloak dependencies</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>com.microsoft.azure</groupId> <artifactId>msal4j</artifactId> <version>1.17.2</version> <exclusions> <exclusion> <artifactId>slf4j-api</artifactId> <groupId>org.slf4j</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.azure</groupId> <artifactId>azure-identity</artifactId> <version>1.13.3</version> <exclusions> <exclusion> <artifactId>msal4j-persistence-extension</artifactId> <groupId>com.microsoft.azure</groupId> </exclusion> <exclusion> <artifactId>slf4j-api</artifactId> <groupId>org.slf4j</groupId> </exclusion> </exclusions> </dependency> </dependencies> </project> |
Once the image is built, you can push it to your repository of choice; if you are using Azure, I recommend Azure Container Registry (ACR).
Deploying the Web App
There’s a few things to keep in mind when deploying the web app:
- Configure the proper environment variables for App Service along with KeyCloak
- Set the proper startup command for KeyCloak to start. The container has an entrypoint that requires parameters to start and be optimized when it’s behind a proxy (i.e. Azure App Service)
Configuring the environment variables
The following environment variables are the basic variables you will need to get it up and running. The environment variables for custom containers for App Service can be found here and the KeyCloak ones, as described earlier, can be found here.
Environment Variable | Value | Description |
WEBSITES_CONTAINER_START_TIME_LIMIT | 180 | I increased the default time to 3 minutes in case KeyCloak needs perform some operation before booting (such as when not using the –optimized flag in the startup command) |
WEBSITES_PORT | 8080 | KeyCloak when running in proxy mode (that is the TLS connection is terminated at the edge), it automatically enables HTTP which listens on port 8080. Thus we have to tell Azure App Service to listen on the container on that port |
KC_DB | mssql | Since we have it built within the image it is not necessary, but putting it for good measure |
KC_HOSTNAME | the name of azurewebsites.net or custom domain name | This is required for KeyCloak to construct its URIs properly |
KEYCLOAK_ADMIN | The username of the administrator. Here I use a KeyVault reference where the value is stored in a KeyVault. Example: @Microsoft.KeyVault(SecretUri=<keyvault-uri>/secrets/keycloak-admin-username/) | |
KEYCLOAK_ADMIN_PASSWORD | The password of the administrator. Here I use a KeyVault reference where the value is stored in a KeyVault. Example: @Microsoft.KeyVault(SecretUri=<keyvault-uri>/secrets/keycloak-admin-password/) | |
KC_DB_URL | jdbc:sqlserver://<sqlServerFQDN>:1433;database=<database-name>;encrypt=true;trustServerCertificate=false;hostNameInCertificate=*.database.windows.net;loginTimeout=30;authentication=ActiveDirectoryMSI | The JDBC connection string which includes the authentication method, in our case managed identity |
Configuring the startup command
The startup command is the command that will be passed to the container upon boot.
Here, as you see, we use the proxy parameter to remove HTTPS routing within app service to the KeyCloak container.
Conclusion
With the following, you should be able to have a KeyCloak instance running on Azure App Service. If you have problems with the Managed Identity and want to debug, do read my post to help you troubleshoot your setup.