Angular 6 with JWT and Refresh Tokens and a little Rxjs 6

First Adjustments

So before we get to the Angular code, I did update my Api code just a bit to both enhance our Values calls a little and also had to add to add some Cors configuration in order for the Angular UI to call into my Api and last, to update the Refresh Token call to send back a BadRequest (with some “invalid_grant” text) instead of the Unauthorized status code I was originally sending back.

Setting Up Cors

If we tried to run our Angular app and have it call into the Api, we would get a 404 error telling us that the endpoint is not available.  Because the Angular app is running as localhost:4200 and the Api is running as localhost:54172, the Api will see the Angular app as an outside resource and not originating from localhost:54172.  The fix is to simply add a Cors configuration in our Startup class and then add that Cors Configuration to the MVC Options Filters.

So back in the Startup.cs file, in the ConfigureServices method, add these sections before you call the services.AddMvc().

and

The first section sets the Cors configuration.  We add a new policy and set the IP where we will accept incoming calls from.  This way we serve data to our friends but still keep out the riff-raff.  You can also put constraints on the headers and method calls here but I left that open for this example work.

Once we have a Cors Configuration, we then add that configuration to the MVC Options Filters and this will intercept the requests coming in and check that the origin is an IP that we will accept.

Refresh Token Call Return Values

Back in the Account Controller, in our RefreshToken method, I have update the invalid return type from an Unauthorized (401) with a BadRequest (400) error response.  I have also added the “invalid_grant” text to the bad request response.  After searching around a bit, I found that this is the most preferred way to notify Api consumers that credentials are no longer valid to log in with.  All your tokens be no good to us.

So, here are the adjustments.

To Note:  I know this isn’t proper code form with all the same return statements and multiple if statements, again though, just to keep it clearer and more illustrative.

Values Controller Update

As we will see in the client code, we will have a button on one of our pages to call into the Values Controller and have it return some data to the page.  I a have just changed the controller method to return a string with 2 random values.  This way, we can hit the button on the client and see that the calls are being made successfully and then be alerted when the values stop because of bad or expired tokens.

Shut Up And Get To To The UI Already!

Ok, ok.  So here will be the layout for our small Angular application.  I have downloaded the latest version of Angular 6 as of this article.

  • app
    • _guards
      • auth.guard.ts
    • interceptors
      • token.interceptor.ts
    • _models
      • currentUser.ts
    • _services
      • auth.service.ts
      • values.service.ts
    • home
      • home.component.html
      • home.component.ts
    • login
      • login.component.html
      • login.component.ts
    • app.component.ts
    • app.module.ts
    • app.routing.ts
    • index.html

App Module

In the app module, we declare our components and then bring in any outside modules that we will need.  After that, we declare our providers.  Here, we have a Guard that will be using to help protect our home page from having the ability for a user to route to the page if the Auth Guard criteria is not met.  We also declare our services here and then register that we will be using an Http Interceptor and our interceptor’s name is “TokenInterceptor”.  The last is to set the bootstrap to the component to fire off at the start and then, of course, declare our Module.

App Component


Pretty normal stuff here.  Keeping the selector just to the cli default of “app-root”.

App Component Html


Here we output the components that will be presented by our Router.  We will see the router in just a bit.

Index


Show the output of our App Component here.

App Routing

In the routing component we set the normal (empty) path to display the Home Component.  The Guard on here will not allow the navigation to this home page without certain criteria being met.  That criteria is configured in our auth.guard.ts component.  If the guard’s criteria is met, it will return a true statement and the canActivate method will allow the navigation to our home page.  As you will see in the auth.guard.ts component code, we will not return a false here if the criteria isn’t met.  We don’t want the user to sit around wondering why their page won’t do anything.  We will, instead, navigate them to the login page.  Here we also set our login path.  Without a guard here, the user can navigate to the login page any time they need.

Auth Guard


The guard implements the CanActivate interface that allows this component to act as a Guard. Here we check local storage if we have a currentUser stored there.  The currentUser has some user information but more importantly, it has our Authentication Token and our Refresh Token.  If the currentUser is not present, we consider the user not logged in an then navigate them to the login page.  If we do have a currentUser in local storage, the Router will get a response of “true” from our guard and allow the user to continue their navigation to the home page.

Current User


Out Current User interface matches up with the TokenResponse class that our Api returns.

Login

When logging in, we will call our Authentication Service and send in the user’s credentials.  We then subscribe to the observable that will be returned from service call.  If we go get data back from a successful call, we navigate the user to which ever page they requested prior to being sent to the login page.

Login Html

Home

Our home page will just display the object that comes back from the Values Controller calls.  In our case, this will be a string value.  We also capture a button click to resend a get values request to the Values Controller.  This way, we can see that our Authentication and Authorization works.  Each button press will result in a new set of values displayed on our page.

Home Html

Values Service

The Values Service just returns the result we get back from our Values Controller call.  For us, it will just look like “value1 – 50, :: value2 – 23”.

Auth Service

Our Auth Service starts with the login capability.  After the call to get a valid token and user data back, the login method will check that we got an object back and that the object does have an accessToken property.  Then the method will save this currentUser to the local storage and also return the currentUser to the caller.  I have not implemented exception handling in here yet.

When we need to call for a refresh, the refreshToken call will grab our currentUser from local storage and look for the Refresh Token in our currentUser object.  We then can make the call to refresh the token with our refresh token.  Upon sucess of our refresh call, we will be given a new currentUser object with the same data except for a new Authentication Token.  We save this all off to local storage again and return our refreshed currentUser object.

Token Interceptor

The file has all the real meat.  Let’s look at the intercept method here.  Here we are going to call the local “addTokenToRequest” to inject the Bearer Authorization header into our http call.  In this same call, we are going to our local storage to grab our Authentication Token (accessToken) and then using that token to make our request.

If we get an error back after that call, we have some work to do, otherwise the request to the Values Controller (or any other protected endpoint) will go on unabated.

If we are returned a 401 (unauthorized), we know there was something wrong with our token.  In this case, we call a “handle401Error” method.  Should we get back that 400 error, we simply call the Auth Service’s logout method.  The logout method will remove the currentUser from the local storage and redirect the user to the login page.  To also note here, I probably should do one further check in the 400 error that would look for the “invalid_grant” text.  This way we would know for sure if we have expired tokens.  This 400 check is really for our Refresh calls and knowing if our Refresh Token has finally expired.  Then we will be forced to log in again.  Finally, if we do get this far, we have some uncaught exception happening and we throw the error back up.

In the 401 handling method, we check if we are doing a refresh call. We do this because other calls using this interceptor may start to queue up other calls while we are trying to make our refresh call.  In that case, we want the calls to fall through to our else statement and add the calls to the TokenSubject (Rxjs’ BehaviorSubject).  The BehaviorSubject allows the new token, that we will eventually get from the Refresh call, to be injected into all of the calls that have come in during our refresh period.  It will send the result of the “addTokenToRequest” to all calls that have been subscribed and waiting (in our else statement).  Then we can just get our token from local storage and have those calls continue without having to keep calling the refresh.

If we are not in a Refresh already, we want to be!  Then we set the isRefreshingToken to true.  After this setting we put the call to the TokenSubject to hold off any other requests through our 401 method here until after we have our initial return from our Refresh call.  Without this, any new incoming calls will all try to hit the Refresh endpoint.  What a mess this would create.  We only want to make just that one initial Refresh call.

Now we finally make our call to RefreshToken in the Auth Service.  This method will make the call back to the Account Controller and send in our Refresh Token and, if everything is correct, we will get back a new Auth Token (accessToken).  Upon the response from the refresh token call, we grab the currentUser object that comes back  and we alert the TokenSubject that we have a return value.  We also set the local Storage currentUser to the new object that we got back from our Refresh call.  The last part of our successful call is to return, not the user we got back, but the new Auth Token we received and send that new token back to the initial call that failed and have that failed call use the new Auth Token to make its call.  That is why we use the Rxjs’ switchMap operator here.  We are returning something other that what our call brought back.  After this, your call to the Values Controller will work again.  If you time that right and watch the calls in the Network tab of your browser’s debugging plugin, you can see two calls in succession to the Values Controller.  The first call comes back with a 401 and the second calls then comes back with a 200.  We were successfully refreshed!

We then need to set that isRefreshingToken back to false if all went well.  We then call a finalize method (Rxjs 6) to reset our boolean value to false.  Any other paths here result in an error and we want to logout the user and force them to try to log in again.

I think next I would like to see if I can apply this to React.

Hope to see you then!

59 Comments

    1. @Juri, I agree. Please stay tuned for a follow-up post on using security cookies to store the auth and refresh tokens while storing user info in localStorage.

      1. When will you create a follow-up post on using security cookies to store the auth and refresh tokens while storing user info in localStorage?

        1. Hi Julia,

          Check back in a few weeks. It has been a bit, but I have been able to get back to looking at this and have made some discoveries on working with storing the Auth Token in cookies and also fighting off CSRF attacks.

          Stay tuned…

  1. Hi, I’m really new to rxjs and angular. When trying to run this code, it always gets me to the same intercepting point where this.authService.getAuthToken() returns the regular token and not the refresh one, adding it to the headres, thus resulting in a 401 again. I don’t understand how this works, could you please help me ? Thanks in advance.

  2. Basically, the refresh call gets intercepted as well. Hence the problem I’m facing. Any help would be appreciated. Thanks again.

      1. Hi Eric, I’m sorry it took so log to reply. I’ve been working on other areas of the app. I finally got it working, using something similar to your code on github (unfortunately, refresh token endpojnt is the same as the token one, I’m not getting a 400, etc.. but those are thing I cannot change since I don’t own the api). However, whan I’m facing now is different: whenver there’s a single call to be made to the api, and the token is expired, I get a 401, the refreshToken gets called and the token gets refreshed…. all good. The problem arises when there’s more than one simultaneous call… I’m loosing the token in the process and getting errors from the backend. How can I “channel” all calls though the refresh process, or better, “en-queue” calls and wait for the refresh process to finish before the rest are resolved?.
        I’m so sorry for my ignorance, I’m completely green on angular and reactive programming.
        Thanks in advance.

  3. Great post mate! congrats./

    I have changed my code to work like yours, but a strange thing is happening. The request that triggers the refreshing token is not being called again, even though I have called next.handle(), but the ones that goes to tokenSubject are working fine.

    Any ideas?

    1. Joao Paulo,

      Not sure if you are still waiting for the answer. I got the same problem and sorted out by changing Promise type to Observable type as per below code. My refreshToken method returns promise which I think may be the case with you.

      //Convert to Observable
      from(this.authService.refreshToken())

      Thanks,
      Gopi

  4. Your code works! It queues multiple requests until the refresh token returns a new JWT and even integrates with NGXS. Thank you, thank you!

    1. Hi Sean,

      I am glad that worked for you. I am in the works of a solution that uses cookies to store the JWT and not using localStorage. Watch for that!

      Best,
      Eric

    1. I tried doing it but the new issue is that, if the refreshToken() is called and the token is set, I have to refresh the page in browser, to execute the request with new token

  5. Let’s assume an access token expired and app sends two parallel request: 1st request is slow (10s), 2nd is fast (1s).
    So while 1st request pending, 2nd one was completed, “refresh token” request sended and token updated.
    1st request completed with 401 and tries to refresh token again (because this.isRefreshingToken=false).
    So app sends two refresh token request instead one.

    You can fix this by adding “isTokenExpired()” function and send refresh token request only if it’s needed.
    so string
    if(!this.isRefreshingToken) {
    changed to
    if(!this.isRefreshingToken && isTokenExpired()) {

    1. Ivan,

      This is a great addition to the code and a scenario that I overlooked. I will add this to the codebase on github.

      Thanks again!

      Eric

  6. While getting a new token more requests are coming in. They all fail. Is there a way of reprocessing all failed requests? Or the solution is doing it already?

    1. Hi Mark,

      The requests should be getting queued up and waiting until the refresh token comes back.

      Does your refresh token make it back to the client? Is there an error that you are seeing?

      Cheers,
      Eric

  7. After npm install I am running ng serve. I am getting the following error:
    ERROR in src/app/_services/authentication.service.ts(6,26): error TS2307: Cannot find module ‘@angular/http’.

    1. Hi Mark,

      I think you may already fixed this issue. Did you run “npm install” after downloading the project? This should have brought all of the dependencies into the app.

  8. I have added the missing package and now UI runs fine. But how do I start the backend? I am not a .net developer I might be missing something obvious.

    1. Hi Mark,

      Sounds like you have made it past this hurdle also. Have you thought about rewriting the backend in a language and toolset that you would be more familiar with?

      You can take a look at this video on YouTube for more assistance. It is only about 2.5 minutes. https://www.youtube.com/watch?v=hhl8gikVSJI

      Also you can try google with “Getting Started with Visual Studio” and you should be well on your way.

      Eric

  9. At the beginning you are saying that the backend is running at 54172. Well, when I start it as a console application the Browser shows 32557. The UI code sems like looks for 53217. Can you please help understand what I am missing?

    1. Hi Mark,

      Visual Studio will run the Api project with its IIS Express web server. The web server will select a port for your project to run on. This port can be random and selected by Visual Studio.

      You can change this in 2 ways.

      1. You can change the port that the Api project runs on. I would check out this Stack Overflow on how to do that (https://stackoverflow.com/questions/38755516/how-to-change-the-port-number-for-asp-net-core-app).

      2. Look through the UI code (Angular App) and look for any place where I make an api call. Look for places that have something similar to this: “http://localhost:53217/api/Account/Token” and just change the 53217 to 32557 and you should be good. To keep things simple, I just kept the URLs in the places where they will make their calls from. Most cases they will be in the Services files for this app.

      Eric

  10. One more thing. After starting the backend in short while it fails with the following error:

    System.Data.SqlClient.SqlException: ‘A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: Named Pipes Provider, error: 40 – Could not open a connection to SQL Server)’

    do I also need to have SQL running?

    1. Hi Mark,

      Yes, you will have to have Sql Server running. This is where your user account information will be stored. You can use a different database server if you need. You will have to go through and change the connection string and the server in the startup.cs file.

      You should be able to download a free “Developer” version of Sql Server to use. Sql Server Express should also work for this.

      I run my Sql Server in a Docker Container and connect to the IP address of that container. If you look into this, I have another post (http://ericsmasal.com/2018/06/22/sql-server-2016-on-docker-for-development/) that shows how to set that up.

      Hope this helps!

      Eric

  11. I am using my own database. I am getting the following error:

    System.Data.SqlClient.SqlException: ‘Invalid object name ‘AspNetTenants’.’

    at the line:
    Tenant tenant = dbContext.Tenants.FirstOrDefault(a => a.DomainName.ToLower() == urlHost);

    Am I missing some tables in my DB?

    1. Yes. you will need the proper tables set up. You will need to run the migrations against that database to get the additional tables set up.

      -Eric

        1. Finally, figured out how to run migration. After running it a number of empty tables are created. Seems like I have to populate at least some of them with data. Do I have to run some script?

          1. Have you entered a value in your AspNetTenants table? You should add in a record here for your local host. [Some Guid, Some Code like ‘LOCAL’, ‘localhost’, 1, ‘Some Name You Want To Call It’. After that, you need to call the Register endpoint with valid User information and the Id you used for the Tenant that you just created. Or else, just call Register with the User Information and then go in and update that record in the AspNetUsers table to put the Id from the Tenant table in there.

    1. Yes, the backend (API project) must be running and listening. Your UI project makes requests to this service.

      -Eric

  12. If I dont start the backend but simply click Login I get the following:
    core.js:7187 ERROR TypeError: You provided ‘undefined’ where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
    at subscribeTo (subscribeTo.js:27)
    at subscribeToResult (subscribeToResult.js:11)
    at CatchSubscriber.error (catchError.js:38)
    at XMLHttpRequest.onError (http.js:1988)
    at ZoneDelegate.invokeTask (zone-evergreen.js:391)
    at Object.onInvokeTask (core.js:30873)
    at ZoneDelegate.invokeTask (zone-evergreen.js:390)
    at Zone.runTask (zone-evergreen.js:168)
    at ZoneTask.invokeTask [as invoke] (zone-evergreen.js:465)
    at invokeTask (zone-evergreen.js:1603)
    defaultErrorLogger @ core.js:7187
    handleError @ core.js:7239
    next @ core.js:31602
    schedulerFn @ core.js:27834
    __tryOrUnsub @ Subscriber.js:183
    next @ Subscriber.js:122
    _next @ Subscriber.js:72
    next @ Subscriber.js:49
    next @ Subject.js:39
    emit @ core.js:27796
    (anonymous) @ core.js:30931
    invoke @ zone-evergreen.js:359
    run @ zone-evergreen.js:124
    runOutsideAngular @ core.js:30818
    onHandleError @ core.js:30928
    handleError @ zone-evergreen.js:363
    runTask @ zone-evergreen.js:171
    invokeTask @ zone-evergreen.js:465
    invokeTask @ zone-evergreen.js:1603
    globalZoneAwareCallback @ zone-evergreen.js:1629
    error (async)
    customScheduleGlobal @ zone-evergreen.js:1742
    scheduleTask @ zone-evergreen.js:378
    onScheduleTask @ zone-evergreen.js:272
    scheduleTask @ zone-evergreen.js:372
    scheduleTask @ zone-evergreen.js:211
    scheduleEventTask @ zone-evergreen.js:237
    (anonymous) @ zone-evergreen.js:1911
    (anonymous) @ http.js:2054
    _trySubscribe @ Observable.js:42
    subscribe @ Observable.js:28
    call @ catchError.js:16
    subscribe @ Observable.js:23
    subscribeToResult @ subscribeToResult.js:9
    _innerSub @ mergeMap.js:59
    _tryNext @ mergeMap.js:53
    _next @ mergeMap.js:36
    next @ Subscriber.js:49
    (anonymous) @ subscribeToArray.js:3
    _trySubscribe @ Observable.js:42
    subscribe @ Observable.js:28
    call @ mergeMap.js:21
    subscribe @ Observable.js:23
    call @ filter.js:13
    subscribe @ Observable.js:23
    call @ map.js:16
    subscribe @ Observable.js:23
    call @ map.js:16
    subscribe @ Observable.js:23
    login @ login.component.ts:28
    (anonymous) @ LoginComponent.html:3
    handleEvent @ core.js:34777
    callWithDebugContext @ core.js:36395
    debugHandleEvent @ core.js:36031
    dispatchEvent @ core.js:22519
    (anonymous) @ core.js:24416
    schedulerFn @ core.js:27877
    __tryOrUnsub @ Subscriber.js:183
    next @ Subscriber.js:122
    _next @ Subscriber.js:72
    next @ Subscriber.js:49
    next @ Subject.js:39
    emit @ core.js:27796
    onSubmit @ forms.js:5283
    (anonymous) @ LoginComponent.html:3
    handleEvent @ core.js:34777
    callWithDebugContext @ core.js:36395
    debugHandleEvent @ core.js:36031
    dispatchEvent @ core.js:22519
    (anonymous) @ core.js:33709
    (anonymous) @ platform-browser.js:1789
    invokeTask @ zone-evergreen.js:391
    onInvokeTask @ core.js:30873
    invokeTask @ zone-evergreen.js:390
    runTask @ zone-evergreen.js:168
    invokeTask @ zone-evergreen.js:465
    invokeTask @ zone-evergreen.js:1603
    globalZoneAwareCallback @ zone-evergreen.js:1629
    Show 47 more frames
    zone-evergreen.js:2952 OPTIONS http://localhost:32557/api/Account/Token net::ERR_CONNECTION_REFUSED

    1. If you look at your browser’s dev tools, and look at the Console in there, you will see an error. Most likely that error is a connection refused error.

      You probably have to update the url’s in the UI project and update the ports to match the ports that your API project is running under.

      Eric

      1. The errors in the console are these:
        http://localhost:32557/api/Account/Token:1 POST http://localhost:32557/api/Account/Token 500 (Internal Server Error)

        and:
        Access to XMLHttpRequest at ‘http://localhost:32557/api/Account/Token’ from origin ‘http://localhost:4200’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

        I have a break point at this line:
        var user = await userManager.FindByNameAndTenantAsync(login.Email, Tenant.Id);
        after pressing F10 it goes to this line:
        return next(httpContext); at line 44 in TenantProviderMiddleWare.cs

        1. You may not have a Tenant set up in the database. I don’t have a script for that. You will need to fill in that record. If you are running locally, you need to make the Domain Name field ‘localhost’.

  13. Perhaps you can elaborate!?

    When you run the API project, make sure you note the port that it uses and put that in the UI project’s api url’s.

    Make sure your database is up an running and the connection string is pointing to your database server.

    I brought down the code from GitHub and it all worked as of 7/3/2019.

    -Eric

  14. Ok, I got everything working now. But what I see is that when a token is expired and UI receives “invalid_grant” it doesn’t retry failed calls automatically but instead presents the Login form. Is that by design?

  15. I can provide a bit more detail. After letting a token to expire the first hit produces 401 but new values are received successfully. Any consecutive call for new values before the token expires again works fine. But if I let the token expire again and call values I get 401 followed by 400, and I presented with the Login form. Not sure what to do at this point.

    1. Well, now, after waiting for token expiration before getting any values I get 401/400. As one guy said it here, it doesn’t work.

      1. Have you tried to debug at all? If you follow the code, you can see how it works. Did you look at the expiration times in the API settings file at all? For a test, you set the Token timeout short and I also have a timeout for the Refresh Token. You will get a 401 error after a certain time and then the code will hit the Refresh endpoint and you get a new Token back. If the Refresh token is expired, then all calls will fail and you will be sent to the Login screen again. Each login will create a new Refresh token.

        Look in the API and set the Token Expiration and the Refresh Expiration to something larger. When I use this in production, I set the Token Expiration to an hour and the Refresh Token to expire in 24 hours. Thus, I force the user to have to log back in every 24 hours. I do not have non-expiring Refresh Tokens. You can add that in if you would like.

        This code is not a production, drop-in, solution for you to use. This is for working through and understanding it. You need to have some critical thinking skills and the ability to debug and follow code.

  16. I will try following your recommandations but, once everything is set properly why would it fail on a first call? Like I said, I have registered a user successfully, then I have successfully logged in with that user. After waiting a bit more than a minute and placing a call for values I am getting 401/400. So, before even going into debugging why would it work that way?

    1. And yes, I have populated AspNetTenants with some data that looks like this:
      1 1 localhost 1 localhost
      I have specified tenandid = 1 in aspnetusers table.

      1. I am getting values fine as long as I am not letting token to expire. So I have a feeling that I have my configuration done correctly.

  17. Try to change line 84 in AccountController.cs. Looks like I have the Refresh Token expiring in 3 minutes. Make sure your expiration in the appsettings.json file is always shorter than what the Refresh Token expiration is. If you don’t want the Refresh Token to expire right away, change line 84 to something like 60. Then the Refresh Token will not expire for an hour. I don’t have the code set up for the Refresh Token to never expire. If that is what you are looking for, you will have to adjust the code or set the expiration to some minutes like 525600 for a year….or something like that.

    If you log in now and not hit anything and you wait for more than 3 minutes, you will be forces to log in again as the code sits now. You need to adjust that line of code, or you could mimic what I did for the Access Token and put that value in the appsettings.json file and read that new value in at line 84.

    – Eric

Leave a Reply

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