Using ECDSA in IdentityServer4

Scott Brady
Scott Brady
Identity Server ・ Updated August 2021

By default, IdentityServer4 uses RS256 to sign identity tokens and JWT access tokens; however, it does also support Elliptic Curve Cryptography (ECC). Using an Elliptic Curve Digital Signing Algorithm (ECDSA), such as ES256, does have some benefits over RSA, such as shorter signature and smaller keys while providing the same level of security.

In this article, I am going to show you how to use ES256 to sign JWTs in IdentityServer4 and then how to use it alongside RS256 for backward compatibility. I contributed some of the code around ECDSA in IdentityServer4, so I figure it is time to write about it 🙂.

Loading an EC Private Key

First, you will need a private key suitable for ECC. I have a whole article about how to load ECDsa objects in .NET, but for the sake of this tutorial, let’s generate a private key using OpenSSL and use the resulting PEM or PFX file:

openssl ecparam -name prime256v1 -genkey -noout -out private-key.pem

Out-of-the-box support for PEM files is available as of .NET 5, but since IdentityServer4 only supports .NET Core 3.1, you'll need to manually load in the private key by base64 decoding the key and calling ImportECPrivateKey:

var pemBytes = Convert.FromBase64String(
    @"MHcCAQEEIB2EbKgBGbRxWTtWheDgaNw3P7TsSsMoWloU4NHO3MWYoAoGCCqGSM49
AwEHoUQDQgAEVGVVEnzMZnTv/8Jk0/WlFs9poYA7XqI7ITHH78OPenhGS02GBjXM
WV/akdaWBgIyUP8/86kJ2KRyuHR4c/jIuA==");
            
var ecdsa = ECDsa.Create();
ecdsa.ImportECPrivateKey(pemBytes, out _);

If you’ve never had to use an EC key in production before, I recommend:

  1. creating an EC key using OpenSSL
  2. loading the resulting PEM
  3. parsing an implementation of ECDsa.

This tutorial uses ES256, which uses NIST’s P-256 curve and SHA-256. Other curves are supported in .NET and are suitable for signing JWTs.

ECDSA in IdentityServer4

Now that you have a private key, you can now configure your signing key in IdentityServer4 by calling an AddSigningCredentials extension on IIdentityServerBuilder.

This will require you to create an ECDsaSecurityKey and adding it to IdentityServer:

var securityKey = new ECDsaSecurityKey(ecdsa) {KeyId = "ef208a01ef43406f833b267023766550"};

// var builder = services.AddIdentityServer();
builder.AddSigningCredential(securityKey, IdentityServerConstants.ECDsaSigningAlgorithm.ES256);

I’ve also added a random Key ID for this signing key; otherwise, no “kid” claim will be present in your JWTs.

Your ECDSA public key will have a JWK that looks something like the following. This is enough for any client application or protected resource (API) to load in your public key and start validating tokens signed using ECDSA.

{
  "kty":"EC",
  "use":"sig",
  "kid":"ef208a01ef43406f833b267023766550",
  "alg":"ES256",
  "x":"VGVVEnzMZnTv_8Jk0_WlFs9poYA7XqI7ITHH78OPeng",
  "y":"RktNhgY1zFlf2pHWlgYCMlD_P_OpCdikcrh0eHP4yLg",
  "crv":"P-256"
}

IdentityServer4 will now start signing all tokens using ECDSA:

eyJhbGciOiJFUzI1NiIsImtpZCI6ImVmMjA4YTAxZWY0MzQwNmY4MzNiMjY3MDIzNzY2NTUwIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE1OTUwODI5NzAsImV4cCI6MTU5NTA4NjU3MCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NTAwMCIsImNsaWVudF9pZCI6Im0ybS5jbGllbnQiLCJqdGkiOiIxMEY3OTI4NkQ1NkRCMzBCQTUyRjlGRkIzNUI3RjNCRSIsImlhdCI6MTU5NTA4Mjk3MCwic2NvcGUiOlsic2NvcGUxIl19.gLNTURwKYnpYEIxzUCiD9BczJjr8NDRgUTPzLELgb6dd-XXFaVCPT2eD8HR-awLBSVOVqmZ-gGQeDHY4k6Zr-Q

Choosing Which Algorithm to Use

Not all client applications or protected resources will be able to support ECDSA out of the box. As a result, you may want to support both RSA and ECDSA, choosing which algorithm to use based on the token audience.

To support multiple signing keys and algorithms, you simply call AddSigningCredential multiple times. Every key you add this way will have its public key advertised on your JWKS.

builder.AddSigningCredential(securityKey, IdentityServerConstants.ECDsaSigningAlgorithm.ES256);
builder.AddSigningCredential(new RsaSecurityKey(RSA.Create()), IdentityServerConstants.RsaSigningAlgorithm.RS256);

Whichever one comes first is used as the default signing key.

You can then override the default signing algorithm by setting the allowed signing algorithms properties on the Client or ApiResource record. This sets the identity token signing algorithm (AllowedIdentityTokenSigningAlgorithms) and access token signing algorithms (AllowedAccessTokenSigningAlgorithms), respectively.

new ApiResource("api1")
{
    Scopes = {"api1"},
    AllowedAccessTokenSigningAlgorithms = {"ES256"}
}

When this property is set, IdentityServer will use the first key suitable for one of these algorithms.

System.InvalidOperationException: Signing algorithms requirements for requested resources are not compatible.

If you find yourself in this situation, then I recommend requesting multiple access tokens from IdentityServer, one for each of the APIs you want to access and maintaining them separately. This is the approach used by Azure AD and Auth0, who only issue an access token for a single resource (they require the use of the OAuth resource parameter).

Source Code

When performing IdentityServer health checks, I always recommend that customers consider move away from RSA PKCS#1 v1.5 signing algorithms to either ECDSA or RSA-PSS, much like the Open Banking standards do. Strong cryptography is now available to us when using IdentityServer, I recommend that you start taking advantage of it.

You can find an example IdentityServer4 implementation using ECDSA on GitHub, along with an example ECC key for you to use.