JWT RSA & HMAC + ASP.NET Core

Recently, I was struggling with the SSO authentication. At first I did pick up JSON Web Token which of course is a legitimate option, however, I was forced to share the secret key between different parties, as I decided to use HMAC. Not so long ago I decided to switch to the RSA instead and I’d like to present you both solutions using ASP.NET Core.


 

I will not dive into the details of how HMAC or RSA work as I’m not an expert in that matter, yet there’s at least this main difference that you should be aware of. If you stick to the HMAC, you’ll be forced to share the so-called secret key among different applications (e.g. services in a microservices architecture) given that you’d like to have the same valid token for different apps. And that’s not really the best idea, which is where RSA comes in handy – the service responsible for the token generation will use the private key for signing the JWT and all of the interested parties may use the public key to ensure the token’s validity. I did implement this solution for our open source platform Collectively that we’re building in a distributed way and we need sort of SSO mechanism in place.

Before we implement anything using C#, let’s prepare all of the stuff. In order to generate the secret key for HMAC, you could use e.g. this website. Just generate some random key, so it may look like this: GRQKzLUn9w59LpXEbsESa8gtJnN3hyspq7EV4J6Fz3FjBk994r.

Next, let’s create the RSA keys. In order to do that, we’ll use openssl tool. Open your terminal and type the following commands:

It will result in creating 2 files, where the private key may look like this:

And the public one like this:

There’s one more thing before we can actually use our RSA keys within .NET Core application. We need to convert them into XML. It can be done here and before we copy our shiny XML into private-rsa-key.xml and public-rsa-key.xml files, let’s format them a little bit by using this tool. Eventually, we should have the following 2 files that we will deploy with our application:

Just keep in mind, though, that the private key is required only for the service responsible for generating the token – the other parties should only use the public key.

Eventually, we can create a new Web API project and add the following packages into the .csproj file:

Next, let’s create a new section within the apssettings.json and the C# class.

We should also create the token model itself:

Now, let’s take care of the actual JWT handler, I’ll just paste the code here and it should be rather straightforward:

As you can see, such handler could be used by all of the services, as the private RSA key part is an optional one. Inside the payload you might notice a custom claim unique_name – this one is actually required if you want to get the current username using User.Identity.Name within ASP.NET Core application.

The FromXmlString() is an extension method defined in a following way:

Credits for this one go to that guy.

Let’s move forward as we’re about to finish the whole sample. We’ll start with creating a new controlle:

Where the SignIn is a typical reqeust:

We’re almost done. Let’s open the Startup class and firstly extend the ConfigureServices():

And move on to the Configure():

That’s it. Now you should be able to send the following requests in order to obtain the token and use it to access the secured endpoint. These are the samples using cURL:

You can switch between HMAC and RSA simply be setting useRsa boolean flag to either true or false. The actual server response for the successful sign in operation should be the following:

or

The tokens can be valited here and the expiration date can be checked here, simply by coping the expires property being represented as the EPOCH ticks.

The full application and all of the source code can be downloaded from the GitHub repository. I hope that this one will get you started with JWT and SSO that can be used to achieve the robust token based authentication mechanism in your application.

28 Comments JWT RSA & HMAC + ASP.NET Core

  1. Pingback: JWT RSA & HMAC + ASP.NET Core - How to Code .NET

  2. Damian

    Masz w planach rozszerzyć samą walidację JWT o refresh_token i skrócić czas tokena uwierzytelniającego ?
    Gdy obecnie zmienimy jakieś dane w DB np zablokujemy usera to mimo wszystko będzie miał dostęp przez 3 dni zgodnie z powyższym przykładem ?

    Myślałem o użyciu Identity Server 4 obecnie aczkolwiek jeszcze nie ma pełnego wsparcia na .NET Core 2.0 ;/ I mam tutaj zgrzyt o ile rozumiem cały schemat generowania tokena, wystawienie nowego tokena na podstawie refresh_token to mimo wszystko magia dla mnie, byłbyś w stanie jakoś to opisać jakie czynności należało by wykonać aby wystawić nowy token na podstawie refresh_tokena ? W jaką strukturę tutaj uderzyć ? Lub ewentualnie może masz w planach jakiś post na blogu ?

    Reply
    1. Piotr Gankiewicz

      Identity Server 4 jest dla mnie zbyt duży i zrobienie czegoś niestandardowego wymaga masę pracy. Zastanawiałem się ostatnio nad mechanizem odświeżania tokena – na pewno jeszcze się rozpatrzę w temacie jak to inni implementują ale wstępny pomysł mam już gotowy. W bazie można trzymać prostą strukturę na zasadzie np. {token_hash, expires, refreshed}, czyli unikalny hash generowany na bazie tokena, data ekspiracji oraz flaga czy został odświeżony. Następnie pojedynczy endpoint typu POST gdzie użytkownik przekazuje token, następuje walidacja i dostaje w zwrotce nowy token. Jak to zaimplementuję to na pewno pomyślę nad postem na bloga :).

      Reply
      1. Damian

        Czyli taki schemat:
        1 Aplikacja loguje się przez API
        2 API generuje token + refresh token
        oraz zapisuje do db jako:
        – token_hash (refresh_token),
        – expires ( czas wygasniecia tokena )
        – refreshed – czy token został już odswierzony

        zwracamy token userowi
        3. User wysyła żadanie o dostęp do danych
        4. Następuje sprawdzenie czasu głównego tokena jeśli nie wygasł zwracamy dane
        – jeśli wygasł sprawdzamy czy refresh token jeszcze nie wygasł w DB jeśli tak to zwracamy użytkownikowi brak autoryzacji
        5. Jeśli token usera wygasł Aplikacja wysyła żądanie aby otrzymać nowy token
        6. API sprawdza czy refresh token nie wygasł i zwraca nowy token ?

        A co w przypadku jeśli ktoś fizycznie z przeglądarki wykradnie nasz token autoryzacyjny ? W takim przypadku dane użytkownika są narażone ?

        Czy troszkę się pogubiłem ? Mógłbyś poprawny schemat taki rozpisać jak Ty to widzisz ?

        Pozdrawiam 🙂

        Reply
        1. Piotr Gankiewicz

          1. Logowanie przez API -> zwrócenie tokena i zapisanie jakiegoś unikalnego hasha w bazie (aby nie trzymać faktycznego tokena).
          2. Prośba o odświeżenie – walidacja przez API czy token jest poprawny -> przekazanie do bazy, sprawdzenie czy refreshed == false i jeśli tak to zwrócenie nowego tokena (+ zapisanie w bazie jak w punkcie 1). W sumie w bazie tutaj już nie trzeba trzymać expires, bo to zweryfikuje API.
          3. Jeśli token wygasł to trzeba zalogować się ponownie – wygasa po prostu na bazie “expires”, nie ma różnych dat do tego co jest w tokenie, a tego co w bazie.

          Do tego stosuje się SSL, żeby nikt niczego nie wykradł.

          Reply
  3. Pingback: Dew Drop - July 24, 2017 (#2526) - Morning Dew

  4. Andrew de Rozario

    Great post! In my opinion RSA works best in scenarios where not all parties using the identity provider are going to be internal i.e third parties.

    I think everyone having the same key is less of a concern if the correct dev ops/deployment practices are in place.

    Reply
    1. Piotr Gankiewicz

      Thank you, Andrew. For sure, sharing a secret key is not a big deal if it’s used internally by the whole system, however, since e.g. only a single service should be responsible for the authentication, there’s no need to even give a chance to the other parties to be able to do the same just because they own the same secret key. And like you said RSA solves this issue and even more importantly you are able to share the public key with anyone else outside of your environment as well.

      Reply
  5. John

    Hello Piotr,

    Do you have any plans on doing tutorial how to do JWT authentication with refresh tokens on .NET Core 2.0 ?

    Greetings John

    Reply
    1. Piotr Gankiewicz

      Hi John,

      I think it should be pretty much the same with .NET Core 2.0. Speaking of refreshing the tokens, it’s something that I’m going to implement and share on my blog in a near future.

      Reply
      1. Damian

        W zasadzie w .net core 2.0 trochę zmieniła się struktura startup.cs jak i również zmieniła się trochę struktura własnych atrybutów autoryzujących.
        Tutaj 1 z wątków odnośnie autoryzacji (breaking change)
        https://github.com/aspnet/Announcements/issues/262
        Tak samo zmieniła się kwestia jak wyżej napisałem własnych atrybutów autoryzujących. Nie mogłem niestety wątku na githubie znaleźć. Ale tam jest troszkę bardziej zawiła sprawa, gdyż trzeba teraz dodać własną politykę bezpieczeństwa.

        Pozdrawiam Damian.

        Reply
          1. Piotr Gankiewicz

            Policy omawiałem w jednym z odcinków kursu na YT, natomiast cokolwiek jest związane z .NET Core 2.0 na ten moment odpada, nie ufam niczemu co jest w wersji preview i pochodzi od MS. Odświeżanie tokenów jeśli już to zrobię dla 1.1, bo logika pod spodem będzie identyczna.

  6. Damian

    Mogę zapytać dlaczego ” natomiast cokolwiek jest związane z .NET Core 2.0 na ten moment odpada, nie ufam niczemu co jest w wersji preview i pochodzi od MS” ?
    W kwartale 3 ma być pełna publikacja i w sumie niewiele czasu już zostało i zbliżamy się tak na prawdę do wersji finalnej. 2.0 Więc skąd taki dystans ? Kwestia bezpieczeństwa na produkcji, czy w samym momencie developmentu problemy i jeszcze niewielkie wsparcie community ?

    Reply
    1. Piotr Gankiewicz

      Już niejednokrotnie się przejechałem na ich wersjach alpha/beta/preview i nie zamierzam powtarzać tego błędu ponownie, więc dopóki nie wyjdzie oficjalny release to nawet nie ruszam 2.0.

      Reply
      1. Tomasz

        .NET Core 2.0 już wyszedł oficjalnie – https://blogs.msdn.microsoft.com/dotnet/2017/08/14/announcing-net-core-2-0/

        Przymierzam się do przejścia na .NET Core, jednak na razie jestem na etapie prototypownia. Trochę informacji na temat refresh token’a znalazłem tu: http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/ jednak w oparciu o WebAPI 2. Implementacja w .NET Core 2.0 na pewno by pomogła 🙂

        Reply
        1. Piotr Gankiewicz

          Tak, wiem ale jeszcze trochę czasu upłynie zanim w pełni zmigruję moje projekty na 2.0 żeby zacząć go w pełni używać, chociaż postaram się to zrobić możliwie szybko.

          Reply
  7. Kenneth

    Awesome tutorial. I have a quick question. So when you set the expiry of JWT, how does it validate if the token is expired? I tested it, the old jwt tokens still work. Does it re validate a new token?

    Reply
    1. Piotr Gankiewicz

      Thanks. Expired tokens should not be accepted at all – once the “exp” field is no longer valid, the API should return 401 by default. Make sure that you have enabled the expiry validation in the JWT settings.

      Reply
  8. Grant

    Hi Piotr,

    Firstly thanks for some awesome work, I have however been struggling with porting this to dotnetcore 2.0 (the porting process was okay, minor changes) – however most of the RSA requirements do not work on MacOS/Linux excepting for generating the JWT – during validation I get:

    Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSignatureException: IDX10503: Signature validation failed. Keys tried: ‘Microsoft.IdentityModel.Tokens.RsaSecurityKey , KeyId:
    ‘.
    Exceptions caught:
    ”.

    It appears as though the RSA key is not being read back from the JWT token (blank) ? Any ideas

    Reply
    1. Piotr Gankiewicz

      Hi Grant,
      Thanks, I’m happy to hear that.
      Speaking of RSA – I also ran into issues with .NET Core 2.0 – you can take a look at the code here https://github.com/noordwind/Collectively.Common/blob/master/src/Collectively.Common/Security/JwtTokenHandler.cs
      Within InitializeRsa() method I’m using the PemReader that comes from the ThirdParty.BouncyCastle.OpenSsl namespace. It seems to be working with typical certificates that are not transformed into XML files (as opposed to the public key above), yet I can’t guarantee that it will solve your issue.

      Reply
  9. Emre

    Great Tutorial Piotr!

    I wonder -since it has been a long time- are you planing to update RSA implementation with .NET Core 2.0 ?

    I did everything as I can to convert it to NET Core 2.0, But I am getting silly 401 Unauhtorized errors. Maybe even some tips are appriciated.

    thanks!

    Reply
    1. Piotr Gankiewicz

      Thank you, Emre!
      Actually, I stopped using RSA as I had some issues with it back then. I’m not sure whether there are any improvements, as there were quite a few hacks (as you can read in the post) needed to make it work.

      Reply
  10. Andrey Verbin

    I’ve just spend good portion of my day debugging this code. Summary is this, don’t do this
    using(RSA publicRsa = RSA.Create())
    {
    var publicKeyXml = File.ReadAllText(_settings.RsaPublicKeyXML);
    publicRsa.FromXmlString(publicKeyXml);
    _issuerSigningKey = new RsaSecurityKey(publicRsa);
    }
    Instead do this
    RSA publicRsa = RSA.Create()
    var publicKeyXml = File.ReadAllText(_settings.RsaPublicKeyXML);
    publicRsa.FromXmlString(publicKeyXml);
    _issuerSigningKey = new RsaSecurityKey(publicRsa);

    Otherwise you get dysfunctional _issuerSigningKey which has disposed RSA inside it. This leads to all sorts of undefined behaviour on Mac and maybe on Linux.

    Reply
  11. Amir

    Hi PIOTR,
    Thanks for your helpful article.
    I’m trying to implement your code in MVC 5 for an SSO project and so far everything has been working well and I’v reached to the point that authorization is successful.
    However I’m a little confused. I’ll be grateful if you help me to solve my challenges.
    1- First, I cannot understand where and how the public key is introduced to the system (OWIN), i.e. how does the system find and get the public key to do the authorization. Are the key-values of “rsaPrivateKeyXml” and “rsaPublicKeyXml” reserved for this or what?
    2- My second question is somehow related to the first one. In my case, I need to separate the project that generates the token from other projects that consume the token and I don’t want the consumers to have the secret key (Private Key). Therefor, I need to know which portion of the code is necessary for consumers and which parts are necessary for the token producer.
    3- My third question might seem silly because my cryptography information is not good. What confuses me is that, what I have read about RSA says that encryption in RSA is done with public key and the decryption is done with private key, but in your approach it is vice versa. Although your approach suits my case better since I don’t want other systems to be able to generate token, I would like to know why the definitions don’t match.

    Reply
  12. mikitaka hayashi

    This article helped me a lot.

    Could you update this portion of the code? In Core 2.1, an error occurred because the syntax is obsolete.

    var jwtHandler = app.ApplicationServices.GetService();
    app.UseJwtBearerAuthentication(new JwtBearerOptions
    {
    AutomaticAuthenticate = true,
    TokenValidationParameters = jwtHandler.Parameters
    });

    Thank you!

    Reply

Leave a Reply to Damian Cancel reply

Your email address will not be published. Required fields are marked *