An Example Solution with Asp.net Core Identity with JWT and React

June 2, 2022
4 min read

A fair portion of each day I am learning new things in each of the languages that I use.  A lot of time is spent on stackoverflow.com, but I also will search for blogs that teach techniques as well.  Most of the blogs that I learn from only give a partial example of what I generally need, however.  I'm sure the same could be said about my posts here.  It can get frustrating to spend a day or more trying to cobble together a solution to a project that you know isn't that difficult.  I will end up using several blogs, several stack overflow posts, and a bunch of trial and error to make things work sometimes.  This post is an effort to be more holistic, at least as much as you can do with a demo project.

IDENTITY IN ASP.NET CORE WITH JWT (JAVASCRIPT WEB TOKENS)

Identity is a system that handles the management of users, passwords, roles, claims, profile data as related workflows.  It's not the same thing as IdentityServer, though they can be used together.

The code from this example can be downloaded here.

.net API setup

The solution is broken up into 3 projects:

  1. API - The controller endpoints
  2. Core - where the services are location
  3. Data - where the data model is defined via Entity Framework Core - code first.

In larger system, it's nice to have the ability to swap out pieces of the application as it grows, which is why I chose to use services architecture.  This demo system uses SQL Server for everything, but if you want to swap that out later for a nosql implementation, then it's easier if you have things separated.

In the Data layer, you'll see the various Application classes.  There are classes for User, Role, Claims, External Logins and Tokens.  By default, there are no relationships between these classes nor extra pieces of data, like first/last name.  You can add all those details however you want.  I just did a basic approach and added a few extra pieces of meta data.  I also added in all the relationships between the various Identity entities.  You can look at the example project for specifics.

Once the data model is in place, you can then open up your Startup.cs and note the various Identity additions in the ConfigureServices section:

// add identity services
services.AddIdentity<ApplicationUser, ApplicationRole>()
                .AddEntityFrameworkStores<ApplicationContext>()
                .AddDefaultTokenProviders();
                
                
// configure jwt authentication
services.AddAuthentication(options =>
{
	options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
	options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
	options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
	/*  // optionally can make sure the user still exists in the db on each call
	options.Events = new JwtBearerEvents
	{
		OnTokenValidated = context =>
		{
			var userService = context.HttpContext.RequestServices.GetRequiredService<IUserService>();
			var user = userService.GetById(context.Principal.Identity.Name);
			if (user == null)
			{
				// return unauthorized if user no longer exists
				context.Fail("Unauthorized");
			}
			return Task.CompletedTask;
		}
	};
	*/
	options.SaveToken = true;
	options.RequireHttpsMetadata = false;
	options.TokenValidationParameters = new TokenValidationParameters()
	{
		ValidateIssuer = false,
		ValidateAudience = false,
		// ValidAudience = "http://dotnetdetail.net",
		// ValidIssuer = "http://dotnetdetail.net",
		IssuerSigningKey = new SymmetricSecurityKey(key)
	};
});

// add authorization
services.AddAuthorization(options => {});                    

// handle authorization policies dynamically
services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();
services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>();

/* Authentication / users / roles */
services.AddScoped<IAuthenticationService, AuthenticationService>();            
services.AddScoped<IUserService, UserService>();
services.AddScoped<IRoleService, RoleService>();

Copy

Then in the Configure section:

app.UseAuthentication();
app.UseAuthorization();

Copy

There are lots of details above, but essentially:

  1. Add middleware support for understanding Identity.
  2. Set defaults for handling authentication including the ability to handle JWT bearer tokens.
  3. Add support for Authorization using permissions/claims/policies.

The following line created middleware where you can add in your custom support for handling claims:

services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();

Copy

The PermissionAuthorizationHandler class adds support for attribute-based policy authorization on end points such as the following:

[Authorize(Policy = Permissions.Role.View)]
[HttpGet]
public ActionResult Get() =>
	Respond(roleService.GetAll());

Copy

The Policy in [Authorize(Policy = Permissions.Role.View)] will check to see if the user accessing the endpoint has the necessary claim associated with their user in the database.

REACT APP

I used create-react-app as a starter for the react front-end.  Then I used components from Ant Design.  If you like some other component library, just swap it out for whatever you like to use, but I find that the antd components are well written, have great documentation and handle most everything I need when building out a website.

I also use:

  • React Contexts
  • sass
  • Axios for the rest calls

Go ahead and download the source for both the API and React front end.  It'll just take 2 minutes to get running, then customize to your heart's content.