IdentityServer In Docker Containers – Handle Logout (Part 4)

IdentityServer In Docker Containers – Handle Logout (Part 4)

In this final post in this series, we’ll now resolve logout challenges you might run into with IdentityServer, ensure proper sign-out redirects, and summarize the key takeaways from the series. Let’s complete the setup and finalize our IdentityServer configuration!

This blog has been broken up into four separate posts:

Logout from IdentityServer (Attempt #10)

With the client application and IdentityServer successfully set up, communicating, and allowing users to log in, it’s time to ensure that sign-outs work securely and as expected.

When you run the system and log in at https://localhost:5001/, you should be redirected to the IdentityServer login page at https://localhost:7001/Account/Login.

From there, you can log in using the credentials bob/bob. You’ll be redirected back to the client application after successfully logging in and granting any necessary consent.

In the top-right corner, you should see the name of the logged-in user:

Clicking on the username will display detailed information about the current user, including their claims and tokens.

What the client looks like after signing in with IdentityServer
Displaying the user details for the currently signed in user in ASP.NET Core, all details are provided by IdentityServer.

Additionally, this sample application provides a tool that shows all the details about the current request, such as the request headers:

Example view of the included "View Current Request" viewer tool. Included in the tool that depends on IdentityServer for authentication.

However, there is still one final issue: when attempting to log out, you’re redirected to this URL:

http://identity/connect/endsession?post_logout_redirect_uri…

This URL is incorrect because the identity service name is only accessible within the Docker network.

Fortunately, we can fix this by adding a custom event handler to AddOpenIDConnect to adjust the logout URL.

				
					AddOpenIdConnect(options =>
{
   ...
   options.Events.OnRedirectToIdentityProviderForSignOut = context =>
   {
       context.ProtocolMessage.IssuerAddress =
                                "https://localhost:7001/connect/endsession";
       return Task.CompletedTask;
   };
});

				
			

Now, when you try to sign out, you’ll first be prompted to confirm the logout:

Duende IdentityServer logout page.

If you confirm, you’ll be presented with a screen that confirms you have successfully logged out:

After logging out fro Duende IdentityServer page.

Great! You’ve successfully logged out, but we can improve the logout experience even more.

IdentityServer Redirect After Sign Out (Attempt #11)

The current sign-out experience can be improved by automatically redirecting the user back to the client application after logging out. How do we achieve this?

Start by locating the LogoutOptions class in IdentityServer, which is somewhat tucked away in the \Pages\Logout folder. It would be more intuitive if this configuration were placed in a more central location, but for now, we’ll modify it as follows:

				
					public static class LogoutOptions
{
    public static readonly bool ShowLogoutPrompt = false;
    public static readonly bool AutomaticRedirectAfterSignOut = true;
}

				
			

However, after restarting the application, nothing has changed! That’s odd. How do we solve this?

JWT token validation error: IDX10205: Issuer validation failed.

Checking the IdentityServer logs, you should see this error:

JWT token validation error: IDX10205: Issuer validation failed. Issuer: 'http://identity'. 
Did not match: validationParameters.ValidIssuer: 'https://localhost:7001' or
validationParameters.
ValidIssuers: 'null' or validationParameters.ConfigurationManager.CurrentConfiguration.Issuer:
'Null'. For more details, see https://aka.ms/IdentityModel/issuer-validation.

Wait, why is there a token issue when we sign out? We’re not calling an API? When the client requests IdentityServer to sign out, it sends a request like this:

Request starting HTTP/2 GET https://localhost:7001/connect/endsession
?post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A5001%2Fsignout-callback-oidc
&id_token_hint=eyJhbGciOiJSUzI1NiIsImtpZCI6IkNEODRGQTgzNEY...
&state=CfDJ8KWm8...
&x-client-SKU=ID_NET8_0
&x-client-ver=7.1.2.0

What is the Purpose of id_token_hint?

According to the OpenID Connect specification:

The ID Token previously issued by the OP to the RP is passed to the Logout Endpoint as a hint about the end user’s current authenticated session with the Client. This indicates the end-user’s identity that the RP requests to be logged out by the OP.

This token serves several important purposes:

  • Session Identification:
    This helps the Identity Provider identify which session to log out of, especially if multiple sessions are active.
  • Logout Context:
    Ensures the logout request is associated with a valid session.
  • Security:
    The token mitigates risks like Cross-Site Request Forgery (CSRF) attacks by verifying that the request comes from a trusted client.
  • User Experience:
    This improves the flow by allowing the Identity Provider to make informed decisions about logging out of specific clients or all sessions.

What’s Inside the ID Token?

If you decode the id_token using https://jwt.io, you’ll see something like this:

				
					{
  "iss": "http://identity",
  "nbf": 1728670667,
  "iat": 1728670667,
  "exp": 1728670967,
  "aud": "localhost-addoidc-client",
  "amr": [
    "pwd"
  ],
  "nonce": "638642674649534072.ZDJlZWY0YzAtZjdmMi00ZTQ0LWIxZTAtOTM1ZjBiMThkMDNkZTM0ZTMxM2UtOGQ3My00ZTZlLTk3ZTktYWZhNDBhMmExOTVk",
  "at_hash": "kGT-YFIlnrw1AsguWas2yw",
  "sid": "F410E3EB5B7B5B0E6A8C8F3AC16BBA8F",
  "sub": "2",
  "auth_time": 1728670667,
  "idp": "local",
  "name": "Bob Smith",
  "given_name": "Bob",
  "family_name": "Smith",
  "email": "BobSmith@email.com",
  "email_verified": true,
  "website": "http://bob.com"
}

				
			

The problem is that the token’s issuer is http://identity, while IdentityServer expects it to be https://localhost:7001. This discrepancy stems from the dynamic discovery document issue we encountered earlier.

How to fix the issuer in IdentityServer

To fix this, we can set a static issuer in IdentityServer by adding the following to its Program.cs:

				
					var isBuilder = builder.Services.AddIdentityServer(options =>
{
    options.IssuerUri = "https://identity";
    ...
}

				
			

This change ensures that the tokens issued have a consistent issuer value no matter where the request originates. For example, with this change, the discovery document will contain:

Within Docker (http://identity/.well-known/openid-configuration)

{
   "issuer":"http://identity",
   "jwks_uri":"http://identity/.well-known/openid-configuration/jwks",
   "authorization_endpoint":"http://identity/connect/authorize"
   ...
}

From the Host (https://localhost:7001/.well-known/openid-configuration):

{
   "issuer":"https://identity",
   "jwks_uri":"https://localhost:7001/.well-known/openid-configuration/jwks",
   "authorization_endpoint":"https://localhost:7001/connect/authorize"
   ...
}

By setting the IssuerUri to a static value (https://identity), we ensure the issuer remains consistent across all environments while the other parameters adjust based on the request’s origin.

When we try to sign out now, we are automatically redirected back to the client application.

Summary and Feedback

Writing this blog series has been both challenging and rewarding. Learning how to containerize IdentityServer and connect it with a client application in Docker taught me a lot about IdentityServer and OpenID Connect—even some details I didn’t fully understand.

I might have made mistakes or missed some important points. I would love to hear your feedback and suggestions on improving these posts. Your thoughts will help me make this content better for everyone.

We also deliberately chose not to modify the host file, which could have resolved some issues faster. However, taking the harder route allowed us to learn more and better understand the process.

Source Code

You can find all the source code for each step, along with the final solution, on my GitHub repository.

What’s next for me?

I’m planning to turn this blog series into a live webinar. If you’re interested in learning more about IdentityServer and want to join an interactive session, sign up for my newsletter to stay updated on the event details.

What Should I Blog About Next?

I’m always looking to create content that helps the developer community. What topics would you like me to cover in future blog posts? Your suggestions are invaluable, and I’d love to hear your ideas! You can find my contact details here.

Do You Need Help with Authentication?

I offer training and consulting services to help teams implement secure and scalable authentication solutions, including IdentityServer and OpenID Connect. I also specialize in ASP.NET Core development, architecture design, and code reviews.

Feel free to reach out if you need support with authentication, ASP.NET Core, or architectural guidance. I’m here to help you build robust and secure solutions.

Share This Story

Related Posts

About Tore Nestenius - Freelance software development instructor and consultant.

About me

My name is Tore Nestenius and I’m a trainer and senior software developer focusing on Architecture, Security and Identity, .NET, C#, Backend, and Cloud, among other things.

Do You Want Tore To Be Your Mentor?

Categories