Angular and the REST - Authentication with JWT

bhaidar

Bilal Haidar

Posted on July 19, 2019

Angular and the REST - Authentication with JWT

In this second installment of the series on Angular and the REST, I implement authentication on the backend ASP.NET Core Web API using JWT (JSON Web Token). In addition, I add a new authentication module on the Angular app side, so access is restricted to authenticated users only by way of a Login.

Let's recap what we have achieved so far in this series. We built the backend ASP.NET Core Web API app that defines a Movie Tracker Endpoint with a full implementation of the CRUD actions and movie search. On the client-side, we built an Angular v8 app that communicates with the backend RESTful Web API by means of a Movie Tracker Service that we generated using Swagger API Documentation, NSwag and NSwagStudio. The app displays all movies, allows the user to filter the movies, adds a new movie, edits an existing movie and can delete a movie.

Here’s a link to the first article in this series Angular and the REST.

Today, let's start first by adding JWT authentication to our ASP.NET Core Web API.

Adding JWT Authentication to ASP.NET Core Web API

To start building Web APIs with ASP.NET Core, make sure you have the following tools and frameworks locally installed on your machine.

We, for all intents and purposes, will be developing this app on a Windows machine. Feel free to use a Macbook or any other machine you like.

In the remaining part of this section, we will go through a step by step guide on how to add JWT authentication to the ASP.NET Core Web API app.

The source code for the backend Web API can be found on this Github repo.

Step 1
To start authenticating users in our app we need to first start managing users. Therefore, let's amend the MovieTrackerContext to cater to Users in our app.

Change the header of the context class to look like this:



public class MovieTrackerContext : IdentityDbContext<ApplicationUser>


Enter fullscreen mode Exit fullscreen mode

By inheriting from IdentityDbContext class we guarantee that Entity Framework (EF) Core will create all the necessary User-related tables in the database.

The IdentityDbContext requires a User class defined in the application in order to create the Users table in the database. By default, the IdentityDbContext class makes use of the IdentityUser that defines a number of standard User fields as follows:

In general, you can use the IdentityUser as long as you are satisfied by the fields defined. However, if you want to manage more fields, you can create a new class, inherit from the IdentityUser and add any additional fields.

For the sake of this demonstration, let's create a new ApplicationUser class inheriting from IdentityUser as follows:



public class ApplicationUser : IdentityUser
{ }


Enter fullscreen mode Exit fullscreen mode

For this article, we won't be adding any additional fields.

Step 2
Let's register the User Management services inside the Startup class as follows:

With this registration, the UserManager class is now available to use in the application to manage and authenticate users. The UserManager will use the MovieTrackerContext class to connect to the database and store all users' data.

Step 3
Creating users is outside the scope of this article. Therefore, I will seed a user record and base our tests on this user.

Add a new Models\SeedUser.cs file with the following content:


The code defines a static Initialize method that starts by retrieving a MovieTrackerContext and UserManager instance respectively from the ASP.NET Core Dependency Injection system.

Then, it checks the Users table to confirm whether or not that user exists. If there is no record, it creates and adds a new User.

Let's call the SeedUser.Initialize() method inside the ASP.NET Core Pipeline:

The moment we run the application, a new user is added to the system.

Step 4
Now that the User Management is properly configured in our app, let's add a new migration so that all User-related tables are created in the database.

Open the Package Manager Console and run the following command:



Add-Migration AddUserManagement


Enter fullscreen mode Exit fullscreen mode

The command generates the following migration:

Now run the following command to create all the above tables in the database:



Update-Database


Enter fullscreen mode Exit fullscreen mode

Step 5
To allow the user to login and authenticate with our app, we need to add a new AuthController Endpoint with a single Login action for now:

The AuthController is an ApiController with a default Endpoint route as /api/auth.

It requests a UserManager instance in its constructor via Dependency Injection.

On the other hand, the Login action accepts as input a LoginModel instance. The LoginModel class is defined as follows:



public class LoginModel
{
    public string Username { get; set; }
    public string Password { get; set; }
}


Enter fullscreen mode Exit fullscreen mode

Inside the Login action, the code queries the database for a user by a given username. If the user doesn't exist, a proper error message is returned to the caller.

In case the user exists, the code then checks if the password matches, the one received from the HTTP Post request versus the one stored in the database. If this check fails, an Unauthorized response is generated and returned.

On the other hand if the user exists and the password matches, the code uses the JwtSecurityToken class to create a new token. A token usually defines a set of standard fields like the Issuer of the token, Audience, Claims (if any), Expiration Date and others.

In addition, a token is usually signed and encrypted and never sent as plain text. Therefore, the JwtSecurityToken requires that you define an instance of the SigningCredentials class that basically wraps a secret key (of your choice) that is used eventually to sign a token.

Finally, the Login action returns a new object containing the token, expiration date and the username associated with the token.

To read more about JWT and understand the details, I recommend checking out this article: JWT Introduction

Step 6
So far, the application can login a user and generate a respective JWT. However, we want to configure our app to also check and validate a JWT so that if an HTTP Request contains a header key for Authentication with a value in the form of Bearer {TOKEN}, we want the app to automatically check and validate the token and if everything is okay, to automatically login the user and mark the current HTTP Request as authenticated.

To do so we start by registering the Authentication services in the ASP.NET Core Dependency Injection system inside the Startup class as follows:

To register the Authentication services in an ASP.NET Core Web API, you add a call to services.AddAuthentication() method specifying that JWT Authentication Scheme will be used.
In addition, you need to configure the ASP.NET Core Pipeline to authenticate a given request just before hitting the ASP.NET Core MVC engine:

Step 7
To test our work, let's open a new Postman session and try to login to the application using the single user credentials we have created so far.

Start by running the application by hitting F5 and then open the Postman application and send the following request:

Test JWT Backend

You send an HTTP Post request to the URL /api/auth/login with a request body of:



{
  "username": "bhaidar",
  "password": "MySp$cialPassw0rd"
}


Enter fullscreen mode Exit fullscreen mode

The backend API returns a valid Token together with the expiration date and the username associated with the token as follows:



{
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjMxMzgwMDcsImlzcyI6Imh0dHBzOi8vbG9jYWxob3N0OjQ0MzQyIiwiYXVkIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NDQzNDIifQ.OZ_0cIt1Df3Wq6TFoyJDItXJZ04vjo3jmcEQmRGiCpA",
    "expiresIn": "2019-07-14T21:00:07Z",
    "username": "bhaidar"
}


Enter fullscreen mode Exit fullscreen mode

You can now grab this token, appended to any HTTP Request you want to be authenticated and you're done.

Finally, to protect a RESTful API Endpoint in ASP.NET Core, you simply decorate a Controller with the [Authorize] attribute. This attribute applies to an entire Controller or to a specific action inside a Controller.

In the next section, we add the authentication feature for the Angular app and start authenticating users before allowing them to manage movies. Stay tuned!

Integrating Authentication in the Angular app

To start working on the Angular app, make sure you have the following tools and frameworks locally installed on your machine.

In the remaining part of this section, we will go through how to integrate authentication in the Angular app and connect it to the backend Web API Auth Endpoint we just developed.

The source code for the Angular app can be found on this Github repo.

Step 1
Create an Auth Module to hold all the services related to authenticating users in the Angular app. Run the following command in a terminal window:



ng generate module app-auth


Enter fullscreen mode Exit fullscreen mode

This command creates a new app-auth.module.ts file inside the path \src\app\app-auth.

Step 2
Let's create the authentication service that handles signing in the user and managing the token state across the application.

Run the command inside a terminal window to generate a new service:



ng g service app-auth/authentication --skipTests=true


Enter fullscreen mode Exit fullscreen mode

The above command creates a new authentication.service.ts file inside the path \src\app\app-auth\.

To login a user, we use:

The login() method uses the HttpClient class to send a POST request to the backend Web API Endpoint /api/auth/login with a request payload in the body containing the username and password of the user trying to login.

Upon a successful login, the token returned from the backend API is stored inside LocalStorage to be used on subsequent requests to send it over as a Bearer authentication header.

In addition, the currentUserSubject of type BehaviorSubject is triggered with the user payload. This subject is defined as follows inside the authentication service:

The currentUserSubject is defined as a BehaviorSubject with an initial value equal to whatever user is stored in the LocalStorage.

Also, the currentUservariable is defined as an Observable on top of the currentUserSubject.

Other components and services in the application can now subscribe to the currentUser Observable since every time a successful login operation executes, it is guaranteed that this Observable will execute with the latest user signed in as a payload.

In addition, the currentUserValue is defined as a public getter to retrieve the value of the currentUserSubject variable. Later on we will use this value inside the Auth Guard as you will see very soon.

To logout a user, we simply delete the entry from the LocalStorage and populate the currentUserSubject with a null value signaling to all components and services that the user has signed out of the application.

Step 3
To restrict access to the MovieTrackerList component let's rearrange the routing a little bit in our application as follows:

The default route now lives under admin/movie-tracker.

The path /admin now has a related component the AppMainComponent. This component renders an Angular Material layout for the entire application.

In addition, any access to /admin is now restricted by the AuthGuard defined by using the canActivate handler that is executed before navigating to the requested route.

The movie-tracker route is now defined as an admin child route.

Finally, a new route is added to route requests to the Login component that we will build shortly.

All routing configurations have been removed from within the MovieTracker module and have been centralized inside the AppModule.

Step 4
Let's define the AuthGuard by first creating a new guard by running this command in a terminal window:



ng generate guard app-auth/auth --skipTests=true


Enter fullscreen mode Exit fullscreen mode

The command creates a new auth.guard.ts file inside the path \src\app\app-auth\.

Paste in the following code:

The AuthGuard implements the CanActivate and provides implementation for the CanActivate handler.

The handle calls the authenticationService.currentUserValue getter to check if there is an existing user (stored in the LocalStorage). If there is one, the code returns true meaning that it is safe to allow the route navigation.

Otherwise, the user is redirected to the Login route.

This AuthGuard is provided at the root of the application and hence it is accessible everywhere in the application.

Step 5
Let's create a new Shared module inside the path \src\app\shared to hold a couple of components that are shared across the application.

Then, you can import this module inside the MovieTracker module or any other module that needs to use any of the components defined in this module.

Then, let's add the *AppMainComponent that contains HTML Markup which guarantees a standard UI layout for the entire app.

Paste in the following inside the newly created app-main.component.ts file:

The component uses the Angular Material Toolbar component, to define a toolbar header for the entire application. Remember, this component is rendered for any route starting with /admin.

Then, the actual route the user is navigating to, gets rendered under the <router-outlet></router-outlet> marker component. This guarantees a standard UI across all routes and components in the application.

The component also defines a Logout button so that the user can logout from the current session anytime they decide so.

Step 6
It's time to build the Login component. Generate a new Login component inside the Shared module by running this command in a terminal window:



ng g component shared/login --module=shared --skipTests=true --inlineTemplate=true --inlineStyle=true


Enter fullscreen mode Exit fullscreen mode

Paste in the following code:

The HTML markup of the Login component defines a simple form to collect the username and password from the user. It also validates the form by requiring the user to enter a username and password. In addition, it notifies the user of any errors returned from the backend Web API in case the login process fails.

This component makes use of the ReactiveFormsModule module from Angular. It defines the form as an object and binds it to the HTML form element.

On form submission, it calls on the authenticationService.login method defined above to actually login the user.

In the case a user is successfully authenticated, the code redirects the user to the home page of the application or to whatever URL the user had an intention to visit prior to being redirected to the Login route. Otherwise, if the user was not successfully authenticated, the errors returned from the backend Web API are displayed to the user.

Step 7
Before running the application and trying things out, two things remain pending. We need to add an HTTP Interceptor to inspect a 401 Unauthorized response Status Code and redirect the user to the Login route accordingly. This occurs when a token is still available at the Angular app level, while in fact this token has expired on the backend Web API and the Angular app has no clue that it has expired.

Create a new errors.interceptor.ts file inside the app-auth module with the following content:

An HTTP Interceptor is defined as a Service and provided at the root module of the application.

After a response is received from the backend server, the interceptor checks if the Status Code equals to 401. It first logs out the user (clear the LocalStorage) and then it refreshes the entire page causing the user to be redirected to the Login route.

Finally, the service is provided in the HTTP_INTERCEPTORS collection.

Switch back to the AppModule and add this interceptor provider as follows:




providers: [
    errorInterceptorProvider
  ],

})
export class AppModule { }


Enter fullscreen mode Exit fullscreen mode

Step 8
Lastly, we need to guarantee that for each and every request sent to the backend Web API, an Authentication Bearer header is attached to the request when a user is authenticated to the application.

Let's start by adding a new Jwt Interceptor that inspects the request before sending it to the server. In case there is a valid user with a valid token stored locally, an Authentication Bearer header is added to the current request and its value is set to Bearer {TOKEN}. Where token is being retrieved from the LocalStorage (if any).

Create a new jwt.interceptor.ts file inside the app-auth module with the following content:

The interceptor above clones the current request and sets the Authentication Bearer header on the new request before sending it to the server.

Finally, the service is provided in the HTTP_INTERCEPTORS collection.

Switch back to the AppModule and provide this interceptor provider as follows:




providers: [
    jwtInterceptorProvider,
    ...
  ],

})
export class AppModule { }


Enter fullscreen mode Exit fullscreen mode

The Angular app is ready to start authenticating users. Let's give it a try.

Step 9
Let's run the application using the command yarn run start. Make sure the backend API is also running.

Once the application starts, the first thing you are redirected to is the Login route:

Login Route

Let's try an invalid combination of a username and password and see how the Login component behaves.

Login Failed

Now let's try to sign in with the user credentials that we have previously seeded into the backend Web API and notice how the application now redirects us to the Movie Tracker route:

Movie Tracker - Logged in

You are now redirected successfully. You can see the new UI layout and also click the Logout button whenever you want to sign out of the application. You may now continue managing your movies after you've signed in to the application.

Conclusion

This second article concludes the first series of articles on Angular and the REST. The next series will cover the same stuff covered so far in the first two installments however by using a Node.js backend Web API instead of ASP.NET Core Web API and more specifically, it will use Nest.js to develop the backend Web API.

This post was written by Bilal Haidar, a mentor with This Dot.

You can follow him on Twitter at @bhaidar.

Need JavaScript consulting, mentoring, or training help? Check out our list of services at This Dot Labs.

💖 💪 🙅 🚩
bhaidar
Bilal Haidar

Posted on July 19, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related