Getting Duende IdentityServer and a client application up and running in separate containers can be challenging. This blog post will provide a step-by-step guide for a smooth setup and show you how to resolve common challenges along the way. We will also learn about security, cookies, ports, containers, and certificates.
This is a big topic, so this blog is divided into four posts. You can get to where you need to be below, but for handy context, it may be helpful to read this post first.
⦁ IdentityServer in Docker: Adding Containers (Part 1)
⦁ IdentityServer in Docker: Networking (Part 2) (Coming soon)
⦁ IdentityServer in Docker: HTTPS and SameSite (Part 3) (Coming soon)
⦁ IdentityServer in Docker: Handle Logout (Part 4) (Coming soon)
If you want to view the final solution, the code for each step and the final code can be found on GitHub here.
Background – containerizing a Duende IdentityServer
I spend quite a bit of my time helping developers on Stack Overflow, and I’ve noticed that many struggle with containerizing a Duende IdentityServer solution. There are numerous pitfalls, gotchas, and misconceptions that we need to understand and address.
The series focuses on deploying Duende IdentityServer locally using Docker Compose, emphasizing the fundamentals rather than a production-ready implementation.
This series takes a step-by-step approach to tackle the challenge, highlighting common mistakes and pitfalls along the way. Rather than simply presenting the final solution, it encourages a shared learning experience, and troubleshooting issues together to build a strong understanding of the fundamentals for containerizing IdentityServer with Docker Compose.
Important – Not for Production
The focus is on getting the application to work locally in a containerized environment using Docker Compose. Making this setup production-ready involves additional considerations beyond the scope of this post.
The Setup – IdentityServer and the Client Application
We have two ASP.NET Core 8 applications: one client application and one IdentityServer application. Both applications have been tweaked to make them more educational.
We name the IdentityServer project IdentityService to avoid potential naming collisions in our code.
The complete source code for this setup and each attempt to create it can be found in my public GitHub repository. The source code in the Start project folder contains the start code, which you can run from within Visual Studio and authenticate using the credentials bob/bob.
The Goal – containerized IdentityServer solution
This setup aims to package two applications into separate containers and run them locally using Docker Desktop with Docker Compose. Authentication against IdentityServer should still work seamlessly after containerization.
Containerize IdentityServer and The Client (Attempt #1)
In our first attempt, we’ll create the following setup using HTTP. Starting with HTTP is beneficial for its simplicity and highlighting some gotchas we’ll explore later in this blog post series. We’ll introduce and use HTTPS later.
We add two Dockerfiles (Dockerfile_Client and Dockerfile_Identity) to containerize the client and IdentityServer projects. The code for them can be found on GitHub.
Next, we add a Docker compose file to launch the two containers and expose ports 5000 and 7000 to the host machine.
services:
client:
build:
context: .
dockerfile: Dockerfile_Client
ports:
- "5000:80"
identity:
build:
context: .
dockerfile: Dockerfile_Identity
ports:
- "7000:80"
In the client application (program class), we modify the OpenID Connect authority as follows:
}).AddOpenIdConnect(options =>
{
options.Authority = "http://localhost:7000";
...
};
To build and deploy the containers, we use the following Docker Compose command:
docker-compose up --build
A simple batch file (RunCompose.bat) has been added that runs the command for us.
When we run the containers, we can observe that both are up and running and can be accessed using http://localhost:5000/ and http://localhost:7000/.
However, when we click on the login button, we get the following error:
IOException: IDX20804: Unable to retrieve document from: 'http://localhost:7000/.well-known/openid-configuration'.
This error occurs because the client application tries to download the IdentityServer’s discovery document and public signing keys from the specified URL but fails to do so. The discovery document is crucial as it contains endpoints and configuration data that the client needs to communicate with IdentityServer securely.
The URL works fine when I go to http://localhost:7000/.well-known/openid-configuration in my browser!
So, why does this not work when it works from the browser?
The problem with Localhost and Containers
In our setup, the client attempts to make a cross-container HTTP request like this:
However, when requesting http://localhost:7000/, it is crucial to understand that this URL is resolved within the client container itself, not to the IdentityService that we intended to reach.
The image below shows what happens in our current setup. Obviously, nothing is listening on port 7000 inside the client container.
The issue arises from having multiple isolated localhost instances in the setup, each tied to a specific container:
⦁ Client container has its own localhost.
⦁ Identity container has its own localhost.
⦁ Host machine has yet another localhost.
As a result, when the client container tries to access http://localhost:7000/, the request is confined to the client container itself and never reaches the IdentityServer container.
The image below illustrates how these three separate localhost environments exist in our setup:
The key here is Docker’s network isolation, which prevents containers from communicating with one another using localhost.
What’s next
We’ll resolve this further in my next post, which you can see here (Coming Soon); we’ll continue refining the communication between the two containers as we work toward building a fully functional, containerized IdentityServer solution.
About the author
Hi, I’m Tore! I have been fascinated by computers since I unpacked my first Commodore VIC-20. Today, I enjoy providing freelance development and developer training services, focusing on ASP.NET Core, IdentityServer, OpenID Connect, Architecture, and Web Security. You can connect with me on LinkedIn and Twitter, and you can find out more about me and my services here, as well as my courses for developers, including my course, Introduction to IdentityServer and OpenID-Connect.