Today, I am going to build a Web API project using Asp.Net Core and implement authentication with JWT Web Token. Furthermore, I will build a Web Application using Angular 6 which will have a couple of pages and a restricted area.
I know that there are lots of posts about it and it is a pretty simple project. Actually, this post is dedicated to my co-workers, VB.Net Developers, who are looking to get out from their comfort zone digging into new technologies. In part 1 of this article, I will focus on backend project. In part 2, let's build the frontend project.
That said, let's get started.
Steps:
1 - Creating the Web API project
2 - Configuring JWT
3 - Installing and configuring Swagger
4 - Installing and using Dapper
5 - Creating classes to authenticate users from an existing database
1 - Creating the Web API project
From Visual Studio, select "New Project". Select the Visual C# | ASP.NET Core Web Application
In the next dialog, select the API project type. Uncheck the Configure for HTTPS checkbox(for instance, our project will not be deployed using SSL).Click OK.
With just a couple of mouse clicks, the API is ready!!! If we run the application, we will get something like this...
By default, ValuesController has added automatically to the project. Feel free to remove it if you want.
So far so good! Let's configure JWT Web Token.
2 - Configuring JWT
To configure JWT in WebAPI .NET Core, we just need to modify Startup.cs file, which is a class that runs when the application starts.
Option 1 - The following configuration is pretty simple. There is no Issuer and Audience validation. Everything that we need is to check the token's expiration date and the secret key. By specifying a key, the token can be validated without any need for the issuing server.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = true, ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("MySecretKey010203")) }; }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } |
Line 3 - Registering JWT authentication middleware by calling AddAuthentication method
Line 12 - Bad practice - Do not hardcode your secret key. I would recommend keeping it in appsettings.json. Or, could just come from a certificate store instead of a file(best way!).
Option 2 - Validating Issuer, Audience and specifying the certificate location.
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 | public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = "http://localhost", // could be a domain, server name, whatever... ValidateAudience = true, ValidAudiences = new List<string> // multiple audience { "http://localhost", "http://client11.mydomain.com", "http://client22.mydomain.com", "clientId_xxx", "clientId_yyz", "whatever" }, ValidateLifetime = true, ValidateIssuerSigningKey = true, IssuerSigningKey = new X509SecurityKey(new X509Certificate2("MySelfSignedCertificate.pfx", "password")) }; }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } |
In order to make our authentication middleware available, let's add the app.UseAuthentication() in the Configure method.
1 2 3 4 5 6 7 8 9 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseAuthentication(); app.UseMvc(); } |
It's done!!! With few steps, we get a Web API running with JSON Web Token authentication.
3 - Installing and configuring Swagger
Swagger is an open source project for generating useful documentation and help pages for Web API projects. Swashbuckle.AspNetCore is a package tools for documenting APIs built on ASP.NET Core. You can install it by executing the following command inside Package Manager Console.
Now, let's add the Swagger generator in the Startup.ConfigureServices method.
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 | public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = true, ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("MySecretKey010203")) }; }); services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new Info { Title = "Demo JSON Web Token", Version = "v1", Description = "Web API Asp.Net Core + JSON Web Token + Angular 6 Web App", Contact = new Contact { Name = "Alexandre Miranda", Url = "http://alexandreomiranda.com" } }); }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } |
In the Startup.Configure method, let's enable the middleware for serving the generated JSON document and the Swagger UI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseAuthentication(); app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "Web API V1"); }); app.UseMvc(); } |
That's all. Run the application and access the URL: http://localhost:21018/swagger (maybe another port number...)
5 - Installing Dapper
Dapper is a micro ORM which helps to map the native query output to a domain class. To install it, execute the following command inside Package Manager Console.
6 - Creating classes to authenticate users from an existing database
In order to make our project structure a little more organized, let's create the folders "Repository" and "Models". By way of example only, our project contains a simple structure and the Repository layer will be called directly from Controllers. A Service layer between both would be the best approach in a complex scenario.
Database
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | CREATE TABLE [dbo].[Users]( [Id] [int] IDENTITY(1,1) NOT NULL, [Username] [varchar](50) NOT NULL, [Password] [varchar](50) NOT NULL, [IsAdmin] [bit] NOT NULL, [Status] [int] NOT NULL, CONSTRAINT [PK_dbo.Users] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] INSERT INTO Users (Username,Password,IsAdmin,Status) VALUES('admin','1234',1,1); INSERT INTO Users (Username,Password,IsAdmin,Status) VALUES('user','1234',0,1); |
Add two new solution folders: "Models" and "Repositories". Under the Models folder, create a new class "User", which represents the Users table in the database
1 2 3 4 5 6 7 8 | public class User { public int Id { get; set; } public string Username { get; set; } public string Password { get; set; } public bool IsAdmin { get; set; } public int Status { get; set; } } |
Under the Repositories folder, create a new class "UserRepository".
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //add namespace using Dapper; public class UserRepository { private IConfiguration _configuration; public UserRepository(IConfiguration configuration) => _configuration = configuration; public User Login(string username, string password) { using (SqlConnection conn = new SqlConnection( _configuration.GetConnectionString("WebApiJWT"))) { var qry = "SELECT * FROM Users WHERE Username = @usr AND Password = @pwd AND Status = 1"; return conn.QueryFirstOrDefault<User>(qry , new { usr = username, pwd = password }); } } } |
Create a new controller - "AuthenticationController"
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 | [Route("api/[controller]")] [ApiController] public class AuthenticationController : ControllerBase { private UserRepository _userRepository; public AuthenticationController(UserRepository userRepository) => _userRepository = userRepository; [HttpPost("login")] [ProducesResponseType(200, Type = typeof(JwtSecurityTokenHandler))] [ProducesResponseType(400)] public IActionResult Login([FromBody] User userParam) { var user = _userRepository.Login(userParam.Username, userParam.Password); if (user == null) return BadRequest(new { message = "Username or password is incorrect" }); var userRole = (user.IsAdmin) ? "admin" : "user"; var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("MySecretKey010203")); var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256); var tokenOptions = new JwtSecurityToken( claims: new List<Claim> { new Claim(ClaimTypes.Name, user.Username), new Claim(ClaimTypes.Role, userRole) }, expires: DateTime.Now.AddDays(2), signingCredentials: signinCredentials ); var tokenString = new JwtSecurityTokenHandler().WriteToken(tokenOptions); return Ok(new { Token = tokenString }); } } |
Lines 5 and 6: We are injecting UserRepository into AuthenticationController constructor. In this case, the framework takes on the responsibility of creating an instance of the dependency and disposing of it when it's no longer needed.
So, let's register this dependency in the service container. Change Startup.ConfigureServices method adding the following line:
1 | services.AddTransient<UserRepository, UserRepository>(); |
This is the final project structure:
Final Step: Running and testing authentication controller
Run the application and execute a test using Swagger.
Access the URL: http://localhost:21018/swagger and expand "/api/Authentication/login" method. Click on button "Try it out".
Change JSON text and click on "Execute"
Received token:
That's all for now. In the next part, I will build a frontend application and consume the generated token by this API.
Project available on my Github.
Source Code - Github
See you!!!