.NET Core IssuerSigningKey from file for JWT Bearer Authentication

25,162

Solution 1

Oh dear, that simple:

SecurityKey key = new X509SecurityKey(cert);

Or as complete sample from above:

X509Certificate2 cert = new X509Certificate2("MySelfSignedCertificate.pfx", "password");
SecurityKey key = new X509SecurityKey(cert); //well, seems to be that simple
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
    AutomaticAuthenticate = true,
    AutomaticChallenge = true,
    TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidIssuer = "MyIssuer",
        ValidateAudience = true,
        ValidAudience = "MyAudience",
        ValidateLifetime = true,
        IssuerSigningKey = key
     }
});

Solution 2

A very important point, if you are using certificate files, that while the server requires the file with the private key, the client should only use the public key.

You do not want to be giving your private key file to anyone; they only ever need the public key.

// On client
var publicCert = new X509Certificate2("MySelfSignedCertificate.cer");
var publicKey = new X509SecurityKey(publicCert);
...
    IssuerSigningKey = publicKey

The simplest way to convert the PFX (private) to CER (public) may be to import into the Windows certificate manager, then export with the public key only.

From the command line, you can create also use PowerShell 5 (not yet in PowerShell 6):

Get-PfxCertificate -FilePath MySelfSignedCertificate.pfx | Export-Certificate -FilePath MySelfSignedCertificate.cer

Alternatively, you can install and use OpenSSL to convert it from the command line.

Note 1: As you found, once you set the Authority, the auto-discovery may be able to find the public key from the server.

Note 2: Rather than store the certificate in a file, you can also store it in the Windows certificate store, and reference it by thumbprint (both PFX and CER files can be imported).

Share:
25,162
monty
Author by

monty

Updated on June 24, 2020

Comments

  • monty
    monty almost 4 years

    I am struggling with the implementation (or the understanding) of signing keys for JWT Bearer Token authentication. And I hope somebody can help me or explain me what I am missunderstanding.

    The last few weeks I crawled tons of tutorials and managed to get a custom Auth-Controller running which issues my tokens and managed to set up the JWT bearer authentication to validate the tokens in the header.

    It works.

    My problem is that all examples and tutorials either generate random or inmemory (issuer) signing keys or use hardcoded "password" strings or take them from some config file (look for "password" in the code samples).

    What I mean for validation setup (in the StartUp.cs):

    
      //using hardcoded "password"
      SecurityKey key = new SymmetricSecurityKey(System.Text.Encoding.ASCII.GetBytes("password"));
    
      app.UseJwtBearerAuthentication(new JwtBearerOptions
      {
        AutomaticAuthenticate = true,
        AutomaticChallenge = true,
        TokenValidationParameters = new TokenValidationParameters
        {
          ValidateIssuer = true,
          ValidIssuer = "MyIssuer",
          ValidateAudience = true,
          ValidAudience = "MyAudience",
          ValidateLifetime = true,
          IssuerSigningKey = key
        }
      });
    

    In the AuthController creating the token:

    
      //using hardcoded password
      var signingKey = new SymmetricSecurityKey(System.Text.Encoding.ASCII.GetBytes("password"));
      SigningCredentials credentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);
    
      var jwt = new JwtSecurityToken     // Create the JWT and write it to a string
      (
        issuer: _jwtTokenSettings.Issuer,
        audience: _jwtTokenSettings.Audience,
        claims: claims,
        notBefore: now,
        expires: now.AddSeconds(_jwtTokenSettings.LifetimeInSeconds),
        signingCredentials: credentials
      );
    

    In this question they used:

    RSAParameters keyParams = RSAKeyUtils.GetRandomKey();
    

    My (current) assumptions was that in production you should not use hardcoded strings or strings from config files for the token signing keys. But instead use some certificate files??? Am I wrong?

    So I tried to replace the strings with a certificate which works in the auth controller:

    
      //using a certificate file
      X509Certificate2 cert = new X509Certificate2("MySelfSignedCertificate.pfx", "password");
      X509SecurityKey key = new X509SecurityKey(cert);
      SigningCredentials credentials = new SigningCredentials(key, "RS256");
    
      var jwt = new JwtSecurityToken      // Create the JWT and write it to a string
      (
         issuer: _jwtTokenSettings.Issuer,
         audience: _jwtTokenSettings.Audience,
         claims: claims,
         notBefore: now,
         expires: now.AddSeconds(_jwtTokenSettings.LifetimeInSeconds),
         signingCredentials: credentials
      );
    

    But there seems no way to get the validation using a certificate.

    
      X509Certificate2 cert = new X509Certificate2("MySelfSignedCertificate.pfx", "password");
      SecurityKey key == // ??? how to get the security key from file (not necessarily pfx)
    
      app.UseJwtBearerAuthentication(new JwtBearerOptions
      {
        AutomaticAuthenticate = true,
        AutomaticChallenge = true,
        TokenValidationParameters = new TokenValidationParameters
        {
          ValidateIssuer = true,
          ValidIssuer = "MyIssuer",
          ValidateAudience = true,
          ValidAudience = "MyAudience",
          ValidateLifetime = true,
          IssuerSigningKey = key
        }
      });
    

    Am I wrong that I should use certificates for the signing keys? How else would I change the signing keys in production when the auth controller is on a different server than the consuming/secured web api (may one time, not now)?

    It seems I also miss the point (required steps) to get the answer of this question working.


    Now I got it running I am still missing the point if it should be like that?


    Noteworthy: File not found (after deployment to server)

    For all those using this and it works when started from Visual Studio but after deployment to a server / azure it says "File not found":

    read and upvote this question: X509 certificate not loading private key file on server


    Noteworthy 2: one actually doesn't need it for token based authentication

    The public key does not need to be on the API side. It will be retrieved via the discovery endpoint of the authentication server automatically.

  • granadaCoder
    granadaCoder over 5 years
    I think you can use public key too. var cert = new X509Certificate2(File.ReadAllBytes(Configuration[$"{YourConf‌​ig1PathHere}:YourCon‌​fig2PathHere"])); X509SecurityKey key = new X509SecurityKey(cert); (where the config values points to a .cer file, not a .pfx file) see stackoverflow.com/questions/12957209/…
  • aruno
    aruno over 5 years
    What was the conclusion on whether to just use a certificate vs. a 'secret key' in production. I suppose the nice thing about a certificate is you can install it on a server and it's locked there - whereas secret key could be stolen by any rogue employee that had access to that server at all. But then we now have these 'secret' stores for keys so that's probably as good no?
  • aruno
    aruno over 5 years
    This article seems to be doing the same thing (search for TokenValidationParameters): blogs.msdn.microsoft.com/webdev/2017/04/06/…