<p>I have followed the <a href="https://github.com/IdentityServer/IdentityServer4.Samples/tree/release/Quickstarts/7_JavaScriptClient" rel="noreferrer">IdentityServer4 quickstarts</a> and am able to authenticate my javascript web page (almost the same as that provided in the quickstart) with a localhosted instance of IdentityServer using the Implicit grant. Again, my IdentityServer is almost exactly the same as that provided in the quickstart mentioned above - it just has some custom user details.</p>

<p>I then moved my application (C# .NET Core) into a docker container and have hosted one instance of this within a Kubernetes cluster (single instance) and created a Kubernetes service (facade over one or more 'real' services) which lets me access the identity server from outside the cluster. I can modify my JavaScript web page and point it at my Kubernetes service and it will still quite happily show the login page and it seems to work as expected.</p>

<p>When I then scale the IdentityServer to three instances (all served behind a single Kubernetes service), I start running into problems.
The Kubernetes service round-robins requests to each identity server, so the first will display the login page, but the second will try and handle the authentication after I press the login button. This results in the following error:</p>

<blockquote>
  <p>System.InvalidOperationException: The antiforgery token could not be
  decrypted. ---> System.Security.Cryptography.CryptographicException:
  The key {19742e88-9dc6-44a0-9e89-e7b09db83329} was not found in the
  key ring.    at
  Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.UnprotectCore(Byte[]
  protectedData, Boolean allowOperationsOnRevokedKeys, UnprotectStatus&amp;
  status)    at
  Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.DangerousUnprotect(Byte[]
  protectedData, Boolean ignoreRevocationErrors, Boolean&amp;
  requiresMigration, Boolean&amp; wasRevoked)    at
  Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.Unprotect(Byte[]
  protectedData)    at
  Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgeryTokenSerializer.Deserialize(String
  serializedToken)    --- End of inner exception stack trace ---
          ... And lots more......</p>
</blockquote>

<p>So - I understand that I am getting this error because the expectation is that the same IdentityServer should service the request for a page that it has shown (otherwise how would the anti-forgery token work, right?), but what I am trying to understand is how I can make this work in a replicated environment.</p>

<p>I don't want to host multiple identity servers on different IP's/ports; I'm trying to build a HA configuration where if one IdentityServer dies, nothing calling the endpoint should care (because requests should be serviced by other working instances).</p>

<p>I said i was using the quickstart code - this means that in the startup of the IdentityServer, there is code that looks like this...</p>

<pre><code>    public void ConfigureServices(IServiceCollection services)  
    {
        services.AddMvc();

        services.AddIdentityServer(options =&gt;
            {
                options.Events.RaiseSuccessEvents = true;
                options.Events.RaiseFailureEvents = true;
                options.Events.RaiseErrorEvents = true;
            })
            .AddTemporarySigningCredential()
            .AddInMemoryIdentityResources(Config.GetIdentityResources())
            .AddInMemoryApiResources(Config.GetApiResources())
            .AddInMemoryClients(Config.GetClients())
</code></pre>

<p>I am assuming that I need to replace the <code>.AddTemporarySigningCredential()</code> logic with a certificate that can be used by all instances of the IdentityServer which are running in my Kubernetes cluster. Not knowing how MVC really works (MVC6 is used to generate the login pages in the IdentityServer service, which I got from the example code - link above) - I want to know if just changing the code to use a proper certificate which is shared between all services will be sufficient to get a prototype HA IdentityServer cluster working?</p>

<p>By working, I mean that my expectation is that I can have n number of IdentityServer instances running in a Kubernetes cluster, have a Kubernetes service to act as a facade over however many IdentityServer's I have running, and be able to authenticate using multiple IdentityServer instances which can share data to the extent that they all provide exactly the same authority to my calling web applications, and can handle each other's requests in the event that one or more instances die.</p>

<p>Any help or insight would be appreciated.</p>

<p>I think I have worked this out. To resolve my issue, I have done two things:</p>
<ol>
<li><p>Create my own X509 certificate and shared this certificate between each of my IdentityServer's. There are lots of examples of how to create valid certificates on the net; I just used</p>
<pre class="lang-cs prettyprint-override"><code>services.AddIdentityServer(...).AddSigningCredential(new X509Certificate2(bytes, &quot;password&quot;)
</code></pre>
<p>in my startup class.</p>
</li>
<li><p>Dug into the MVC framework code and worked out that I needed to implement a <a href="https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/implementation/key-storage-providers" rel="nofollow noreferrer">Key storage provider</a> in order to share state between different instances of the MVC part of Identity Server which serves up the login page.</p>
</li>
</ol>
<p>It turns out that there is a <a href="https://www.nuget.org/packages/Microsoft.AspNetCore.DataProtection.Redis/" rel="nofollow noreferrer">Redis backed KSP available from NuGet</a>, which means that I just need to spin up a private redis instance in my Kube cluster (which isn't accessible outside of my cluster) to share decryption secrets.</p>
<pre class="lang-cs prettyprint-override"><code>/* Note: Use an IP, or resolve from DNS prior to adding redis based key store as direct DNS resolution doesn't work for this inside a K8s cluster, though it works quite happily in a Windows environment. */  
var redis = ConnectionMultiplexer.Connect(&quot;1.2.3.4:6379&quot;);
services.AddDataProtection()
        .PersistKeysToRedis(redis, &quot;DataProtection-Keys&quot;);
</code></pre>
<p>I can now scale my identity service to 3 instances and have a Kube service acting as a facade over all the available instances. I can watch the logs as Kubernetes round-robin's requests between the identity service, and my authentication happens just as I expect.</p>
<p>Thanks to those who commented on the question prior to this post.</p>
