When building a REST API, you might find yourself wanting to protect resources from unauthorized users. This best way to do this is to add JWT Authentication. This allows for your server to generate a token for an authenticated user and for your user’s client to send that token to authenticate for each request. In this post, we will be setting up JWT authentication using IdentitysServer 4 and the ResourceOwnerPassword Flow. This will allow for us to just send over the username and password without having to worry about handling browser redirects. There are some downsides to this but that will be covered in a later post about Oath 2 flows. You can access to the github repo here and video courses here.

Project Setup

To get started, first, you need to create a new ASP.NET Core API project.

ASP.NET Core API StartUp

ASP.NET Core API StartUp

Next make sure to select the API Project

REST API Selection

Rest API Selection

Once your project is created, you’ll want to add the 3 Nuget Packages IdentityServer4, IdentityServer4.AspNetIdentity and Microsoft.VisualStudio.Web.CodeGeneration.Design.

Folder Structure

The next step is to create folders to house our models, controllers, data, and migrations. It’s also recommended that you create a folder for APIs called Areas. I created an Area called v1, because this is version 1 of our API. Look at the screenshot below as an example.

Folder Structure

Folder Structure for ASP.NET Core API with JWT Identity Server

Database Configuration

Now that we have the folders set up, let’s start by creating our UserStore. We’ll need this in order to authenticate our users and generate tokens. One of the best ways to do this is to create a User class that extends from IdentityUser.

using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace TokenAuthASPCore.Models
{
    public class ApplicationUser : IdentityUser
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}

Also, let’s create a Todo class for testing our REST API later on.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace TokenAuthASPCore.Models
{
    public class Todo
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public bool Finished { get; set; }
    }
}

Setting up our Database

In order to be able to use both of these, we must first connect to a database. I’ll be using MS SQL-Server.

Add a connection string to you appsettings.json file that specifies the location of the database that you want to interact with.

"ConnectionStrings": {
    "TestConnection": "Data Source=DESKTOP-GIIMG7O\\JAMES;Initial Catalog=IdentityBlog;Integrated Security=True"
  },

Then go to your Startup.cs file and add your configuration for your database connection.

services.AddDbContext<ApplicationDbContext>(options =>
               options.UseSqlServer(Configuration.GetConnectionString("TestConnection")));

            services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();

Now do your migrations in either powershell or the command line.

For PowerShell user
Add-Migration InitialCreate
Update-Database

For Console use
dotnet ef migrations add InitialCreate
dotnet ef database update

You should now see your database updated.

IdentityServer4 JWT Database

IdentityServer4 JWT Database

IdentityServer 4 Configuration

The next step is to configure IdentityServer4. IdentityServer4 is a framework that allows for us to add OIDC authentication and authorization to our APS.NET Core application. It allows for the generation of JWT tokens and supports many of the Oauth 2 flows. We’ll be using the ResourceOwnerPassword flow because, in later tutorials, we’ll be authentication with both a React and Angular app as well as a Xamarin application.

We’ll start off by creating a Config.cs file. This will hold most of our configuration for IdentityServer 4.

using IdentityServer4;
using IdentityServer4.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace TokenAuthASPCore
{
    public class Config
    {
        public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            return new List<IdentityResource>
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Email(),
                new IdentityResources.Profile(),
            };
        }


        public static IEnumerable<ApiResource> GetApiResources()
        {
            return new List<ApiResource>
            {
                new ApiResource("api1", "My API")
            };
        }

        public static IEnumerable<Client> GetClients()
        {
            // client credentials client
            return new List<Client>
            {
                
                // resource owner password grant client
                new Client
                {
                    ClientId = "ro.angular",
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,

                    ClientSecrets =
                    {
                        new Secret("secret".Sha256())
                    },
                    AllowedScopes = {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        IdentityServerConstants.StandardScopes.Email,
                        IdentityServerConstants.StandardScopes.Address,
                        "api1"
                    }
                }
            };
        }
    }
}

We have our IdentityResources, or API Resources and our Clients. The IdentityResources represent what information we can get from our Identity Store, our API Resources represent the API information we can access and the Clients represent what clients are allowed to authenticate with our API.

If we jump back to our Startup.cs file, we can now register IdentityServer4 as a service. This is configured in your ConfigureServices method.

services.AddIdentityServer()
                .AddDeveloperSigningCredential()
                .AddInMemoryPersistedGrants()
                .AddInMemoryIdentityResources(Config.GetIdentityResources())
                .AddInMemoryApiResources(Config.GetApiResources())
                .AddInMemoryClients(Config.GetClients())
                .AddAspNetIdentity<ApplicationUser>();

            services.AddMvc();


            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(options =>
                {
                    // base-address of your identityserver
                    options.Authority = "http://localhost:52718/";

                    // name of the API resource
                    options.Audience = "api1";

                    options.RequireHttpsMetadata = false;
                });

We’ll also add routing and IdentityServer to our HTTP pipeline. Inside of the Configure method add

app.UseIdentityServer();
            app.UseAuthentication();
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "areas",
                    template: "{area:exists}/{controller=Home}/{action=Index}/{id?}"
                );
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });

User Registration

Now that we have Identity Server set up, we’ll have to create a way for our users to sign-up. Let’s start off by creating a view model for user registrations.  Then we’ll create an AccountController for user signup.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace TokenAuthASPCore.Models.AccountViewModels
{
    public class RegisterViewModel
    {
        [Required]
        [StringLength(50, ErrorMessage = "The {0} must be at least {2}, at max {1} characters long and unique.", MinimumLength = 2)]
        [Display(Name = "Username")]
        public string UserName { get; set; }

        [Required]
        [StringLength(50, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 2)]
        [Display(Name = "First Name")]
        public string FirstName { get; set; }

        [Required]
        [StringLength(50, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 2)]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }

        [Required]
        [EmailAddress]
        [Display(Name = "Email")]
        public string Email { get; set; }

        [Required]
        [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }

        [DataType(DataType.Password)]
        [Display(Name = "Confirm password")]
        [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
        public string ConfirmPassword { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using TokenAuthASPCore.Areas.v1.Models.UserViewModels;
using TokenAuthASPCore.Models;
using TokenAuthASPCore.Models.AccountViewModels;

namespace TokenAuthASPCore.Areas.v1.Controllers
{
    [Area("v1")]
    public class AccountController : Controller
    {
        private readonly UserManager<ApplicationUser> _userManager;
        private readonly RoleManager<IdentityRole> _roleManager;

        public AccountController(
            UserManager<ApplicationUser> userManager,
            RoleManager<IdentityRole> roleManager
            )
        {
            _userManager = userManager;
            _roleManager = roleManager;
        }

        [HttpPost]
        public async Task<IActionResult> Register([FromBody]RegisterViewModel model)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            var user = new ApplicationUser { UserName = model.UserName, FirstName = model.FirstName, LastName = model.LastName, Email = model.Email};

            var result = await _userManager.CreateAsync(user, model.Password);

            string role = "Basic User";

            if (result.Succeeded)
            {
                if (await _roleManager.FindByNameAsync(role) == null)
                {
                    await _roleManager.CreateAsync(new IdentityRole(role));
                }
                await _userManager.AddToRoleAsync(user, role);
                await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("userName", user.UserName));
                await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("firstName", user.FirstName));
                await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("lastName", user.LastName));
                await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("email", user.Email));
                await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("role", role));

                return Ok(new ProfileViewModel(user));
            }

            return BadRequest(result.Errors);


        }
    }
}

Inside of the controller, we have a register method that takes in the data from a validated ViewModel, creates the user with a basic role, then adds claims to that user.

We’ll also create a ProfileViewModel creating a custom output of user data.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using TokenAuthASPCore.Models;

namespace TokenAuthASPCore.Areas.v1.Models.UserViewModels
{
    public class ProfileViewModel
    {
        public string Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }

        public ProfileViewModel()
        {

        }

        public ProfileViewModel(ApplicationUser user)
        {
            Id = user.Id;
            FirstName = user.FirstName;
            LastName = user.LastName;
            Email = user.Email;
        }

        public static IEnumerable<ProfileViewModel> GetUserProfiles(IEnumerable<ApplicationUser> users)
        {
            var profiles = new List<ProfileViewModel> { };
            foreach (ApplicationUser user in users)
            {
                profiles.Add(new ProfileViewModel(user));
            }

            return profiles;
        }
    }
}

If we run our application and try to create that user, the API will return the user’s profile information.

Profile of user

User Profile Information after creation.

JWT Generation

Once that you’ve verified that you can create new users, let’s try creating a JWT. Inside of you Rest API tester, place in the user’s credentials along with the credentials for the API.

User Credentials for IdentityServer 4 Authentication

User Credentials for IdentityServer 4 Authentication

If successful, you should get a token return back as a response.

JWT Generated after Identity Server Authentication.

JWT Generated after Identity Server Authentication.

Conclusion

In this tutorial, we went over how to create JWT with IdentityServer4. We setup user registration and created a custom class with additional user information. In Part 2, we’ll go over modifying the token with custom claims. Check out our git repo and courses on full stack development with technologies like React, Angular and ASP.NET Core.

Codebrains Newsletter

Get weekly dev news and tutorials.

Powered by ConvertKit