Share

Using ECDSA in IdentityServer4

Scott Brady
Identity Server

By default, IdentityServer4 uses RS256 to sign identity tokens and JWT access tokens; however, it does also support Elliptical Curve Cryptography (ECC). Using Elliptical Curve Digital Signing Algorithms (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. There are a few ways to do this, for instance, you could 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 coming in .NET 5. Until then, you can simply 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 _);

Or you could even generate one in code like so:

var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);

If you’ve not had to generate an EC private key before, I recommend checking out my article on how to do so using OpenSSL.

Choosing the Correct Elliptical Curve

When using ECDSA for JWT signing, you’ll typically be using one of the below configurations, unless you want to use a curve that wasn’t defined by NIST by using something like ES256k.

JOSE Algorithm Curve Hashing Algorithm
ES256 P-256 (secp256r1) SHA-256
ES384 P-384 (secp384r1) SHA-384
ES512 P-521 (secp521r1 - 521 is not a typo) SHA-512

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. If you loaded in your EC key as an x509 certificate, then I recommend calling GetECDsaPrivateKey on X509Certificate2 and using the below code, until IdentityServer4 updates to version 6 of the Microsoft.IdentityModel libraries.

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

These days, when performing IdentityServer health checks, I do recommend that customers consider moving 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.