Home Angular 6 with JWT and Refresh Tokens and a little Rxjs 6
Post
Cancel

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

Alt text

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().

1
2
3
4
5
6
7
services.AddCors(options =>
{
  options.AddPolicy("ChiroWareAppCors",
      policy => policy.WithOrigins("http://localhost:4200")
        .AllowAnyHeader()
        .AllowAnyMethod());
});

and

1
2
3
4
services.Configure<MvcOptions>(options =>
{
  options.Filters.Add(new CorsAuthorizationFilterFactory("ChiroWareAppCors"));
});

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.

1
2
3
4
5
6
7
8
9
10
11
if (refreshTokenFromDatabase == null)
return BadRequest("invalid_grant");

if (refreshTokenFromDatabase.ExpiresUtc < DateTime.Now.ToUniversalTime())
return BadRequest("invalid_grant");

if (!await signinManager.CanSignInAsync(refreshTokenFromDatabase.User))
return BadRequest("invalid_grant");

if (userManager.SupportsUserLockout && await userManager.IsLockedOutAsync(refreshTokenFromDatabase.User))
return BadRequest("invalid_grant");

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.

1
2
3
4
5
6
7
8
9
10
[HttpGet]
[Authorize]
public IEnumerable<string> Get()
{
  Random r = new Random();
  int val1 = r.Next(0, 100);
  int val2 = r.Next(0, 100);

  return new string[] { "value1 - " + val1.ToString(), " :: value2 - " + val2.ToString() };
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
.....
@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    LoginComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpClientModule,
    routing
  ],
  providers: [
    AuthGuard,
    AuthenticationService,
    ValuesService,
    {
      provide: HTTP_INTERCEPTORS,
      useClass: TokenInterceptor,
      multi: true
    }
  ],
  bootstrap: [AppComponent]
})
 
export class AppModule { }

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

1
2
3
4
5
6
7
8
9
10
.....
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent {
  title = '\ApothecaricNgUI';
}

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

App Component Html

1
2
3
<div>
  <router-outlet></router-outlet>
</div>

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

Index

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!doctype html>
<html lang="en">
  <head>
    <metacharset="utf-8">
    <title>Apothecaric Angular UI</title>
    <basehref="/">

    <metaname="viewport" content="width=device-width, initial-scale=1">
    <linkrel="icon" type="image/x-icon" href="favicon.ico">
  </head>
  <body>
    <app-root></app-root>
  </body>
</html>

Show the output of our App Component here.

App Routing

1
2
3
4
5
6
7
8
9
.....
const appRoutes: Routes = [
  { path: '', component: HomeComponent, canActivate: [AuthGuard] },
  { path: 'login', component: LoginComponent },

  { path: '**', redirectTo: '' }
];

export const routing = RouterModule.forRoot(appRoutes);

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.....
@Injectable()
export class AuthGuard implements CanActivate {

  constructor(private router: Router){ }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) : boolean {

    if(localStorage.getItem('currentUser')) {
      return true;
    }

    this.router.navigate(['/login'], { queryParams: { returnUrl:state.url }});

  }
}

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

1
2
3
4
5
6
7
8
export interface ICurrentUser {
  accessToken: string;
  refreshToken: string;
  firstName: string;
  lastName: string;
  tenantCode: string;
  tokenExpiration: string;
}

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

Login

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
.....
@Component({
  moduleId: module.id.toString(),
  templateUrl: 'login.component.html'
})

export class LoginComponent implements OnInit {
  model: any = {};
  loading = false;
  returnUrl: string;

  constructor(
    privateroute: ActivatedRoute,
    privaterouter: Router,
    privateauthenticationService: AuthenticationService
  ) { }

  ngOnInit() {
    this.returnUrl = this.route.snapshot.queryParams['returnUrl'] ||'/';
  }

  login() {
    this.loading = true;
    this.authenticationService.login(this.model.username, this.model.password)
      .subscribe(
        data=> { this.router.navigate([this.returnUrl]); }
      );
  }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div>
  <h2>Login</h2>
  <form name="form" (ngSubmit)="f.form.valid && login()" #f="ngForm" novalidate>
    <div [ngClass]="{ 'has-error': f.submitted && !username.valid }">
      <label for="username">Username:</label>&nbsp;&nbsp;
      <input type="text" name="username" [(ngModel)]="model.username" #username="ngModel" required />
      <div *ngIf="f.submitted && !username.valid" style="color: red;">Username is required</div>
    </div>
    <div [ngClass]="{ 'has-error': f.submitted && !password.valid }" style="margin-top: 5px;">
      <label for="password">Password:</label>&nbsp;&nbsp;&nbsp;
      <input type="password" name="password" [(ngModel)]="model.password" #password="ngModel" required />
      <div *ngIf="f.submitted && !password.valid" style="color: red;">Password is required</div>
    </div>
    <div style="margin-top: 10px;">
      <button [disabled]="loading">Login</button>
      <img *ngIf="loading" src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==" />
    </div>
  </form>
</div>

Home

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.....
@Component({
  moduleId: module.id.toString(),
  templateUrl: 'home.component.html'
})

export class HomeComponent {

  constructor(private valuesService: ValuesService) {}

  values: any =  {};

  onButtonClick() {
    this.valuesService.getValues()
      .subscribe(
        data=> { this.values = data }
      );
  }
}

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

1
2
3
4
5
<h2>You have reached me!!</h2>
<button (click)="onButtonClick()" style="margin-top: 10px;">Get Values</button>
<div style="margin-top: 20px;">
  <span></span>
</div>

Values Service

1
2
3
4
5
6
7
8
9
10
11
12
.....
@Injectable()
export class ValuesService {

  constructor(private http: HttpClient) { }

  getValues() : Observable<any> {
    return this.http.get<any>("http://localhost:53217/api/Values")
    .pipe(
        map(result=> { return result }));
  }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
.....
@Injectable()
export class AuthenticationService {

  constructor(private http: HttpClient, private router: Router) { }

  login(email: string, password: string) : Observable<ICurrentUser> {
    return this.http.post<ICurrentUser>("http://localhost:53217/api/Account/Token", { 'email': email, 'password': password })
      .pipe(
        map(user => {

          if (user && user.accessToken) {
            localStorage.setItem('currentUser', JSON.stringify(user));
          }

          return <ICurrentUser>user;
      }));
  }

  logout() {
    localStorage.removeItem('currentUser');
    this.router.navigate(['/login']);
  }

  refreshToken() : Observable<ICurrentUser> {
    let currentUser = JSON.parse(localStorage.getItem('currentUser'));
    let token = currentUser.refreshToken;

    return this.http.post<ICurrentUser>("http://localhost:53217/api/Account/Token/Refresh", { 'token': token })
      .pipe(
        map(user => {

          if (user && user.accessToken) {
            localStorage.setItem('currentUser', JSON.stringify(user));
          }

          return <ICurrentUser>user;
      }));
  }

  getAuthToken() : string {
    let currentUser = JSON.parse(localStorage.getItem('currentUser'));

    if(currentUser != null) {
      return currentUser.accessToken;
    }

    return '';
  }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
.....

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

  constructor(private authService: AuthenticationService) { }

  isRefreshingToken: boolean = false;
  tokenSubject: BehaviorSubject<string> = new BehaviorSubject<string>(null);

  intercept(request: HttpRequest<any>, next: HttpHandler) : Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any> | any> {

    return next.handle(this.addTokenToRequest(request, this.authService.getAuthToken()))
      .pipe(
        catchError(err => {
          if (err instanceof HttpErrorResponse) {
            switch ((<HttpErrorResponse>err).status) {
              case 401:
                return this.handle401Error(request, next);
              case 400:
                return <any>this.authService.logout();
            }
          } else {
            return throwError(err);
          }
        }));
  }

  private addTokenToRequest(request: HttpRequest<any>, token: string) : HttpRequest<any> {
    return request.clone({ setHeaders: { Authorization: `Bearer ${token}`}});
  }

  private handle401Error(request: HttpRequest<any>, next: HttpHandler) {

    if(!this.isRefreshingToken) {
      this.isRefreshingToken = true;

      // Reset here so that the following requests wait until the token
      // comes back from the refreshToken call.
      this.tokenSubject.next(null);

      return this.authService.refreshToken()
        .pipe(
          switchMap((user: ICurrentUser) => {
            if(user) {
              this.tokenSubject.next(user.accessToken);;
              localStorage.setItem('currentUser', JSON.stringify(user));
              return next.handle(this.addTokenToRequest(request, user.accessToken));
            }

            return <any>this.authService.logout();
          }),
          catchError(err => {
            return <any>this.authService.logout();
          }),
          finalize(() => {
            this.isRefreshingToken = false;
          })
        );
    } else {
      this.isRefreshingToken = false;

      return this.tokenSubject
        .pipe(filter(token => token != null),
          take(1),
          switchMap(token => {
          return next.handle(this.addTokenToRequest(request, token));
        }));
    }
  }
}

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!

This post is licensed under CC BY 4.0 by the author.