Create User Login and Registration and Secure Asp.Net Core Web API

In this article I will show you to create user login and registration and how to secure your web api.

This article is a continuation of my previous articles Creating a new ASP.NET Core Application Project and Setting Up Asp.Net Core Identity and How to Seed Users and Roles in Asp.Net Core if you haven’t seen them yet, please take a look at them then come back at this article.

Advertisements

In the solution explorer. Create a new folder and call it Dto. Then inside the Dto folder add new class LoginDto and RegisterDto. Then in Controllers folder add new controller and call it AccountControoler.

LoginDto class is a data transfer object that we will use for our login endpoint. It only have Email and Password properties on it.

using System.ComponentModel.DataAnnotations;

namespace PayrollApp.Api.Dto
{
    public class LoginDto
    {
        [Required]
        [DataType(DataType.EmailAddress)]
        public string Email { get; set; }
        [Required]
        public string Password { get; set; }
    }
}

The RegisterDto is for user registration. We will also make use of data annotation for object our validation.

using System.ComponentModel.DataAnnotations;

namespace PayrollApp.Api.Dto
{
    public class RegisterDto
    {
        [Required]
        public string FirstName { get; set; }
        [Required]
        public string LastName { get; set; }

        [Required]
        [DataType(DataType.EmailAddress)]
        public string Email { get; set; }

        [Required]
        [StringLength(255, ErrorMessage = "Must be between 5 and 255 characters", MinimumLength = 5)]
        [DataType(DataType.Password)]
        public string Password { get; set; }

        [Required]
        [StringLength(255, ErrorMessage = "Must be between 5 and 255 characters", MinimumLength = 5)]
        [DataType(DataType.Password)]
        [Compare("Password")]
        public string ConfirmPassword { get; set; }
    }
}
Advertisements

We will only have Register and Login method in our AccountController. Copy the code below to AccountController.

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using PayrollApp.Api.Data.Entities;
using PayrollApp.Api.Dto;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using System.Linq;

namespace PayrollApp.Api.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class AccountController : ControllerBase
    {
        private readonly ILogger<AccountController> logger;
        private readonly SignInManager<User> signInManager;
        private readonly UserManager<User> userManager;
        private readonly IConfiguration config;

        public AccountController(ILogger<AccountController> logger,
            SignInManager<User> signInManager,
            UserManager<User> userManager,
            IConfiguration config)
        {
            this.logger = logger;
            this.signInManager = signInManager;
            this.userManager = userManager;
            this.config = config;
        }

        [HttpPost("register")]
        public async Task<IActionResult> Register([FromBody]RegisterDto model)
        {
            if (ModelState.IsValid)
            {
                var existingUser = await this.userManager.FindByEmailAsync(model.Email);
                if(existingUser == null)
                {
                    User user = new User();
                    user.UserName = model.Email;
                    user.Email = model.Email;
                    user.FirstName = model.FirstName;
                    user.LastName = model.LastName;

                    IdentityResult result = userManager.CreateAsync(user, model.Password).Result;

                    if (result.Succeeded)
                    {
                        await userManager.AddToRoleAsync(user, "User");
                        return Created("", model);
                    }
                }

            }

            return BadRequest();
        }

        [HttpPost("login")]
        public async Task<IActionResult> Login([FromBody]LoginDto model)
        {
            if (ModelState.IsValid)
            {
                var user = await this.userManager.FindByEmailAsync(model.Email);
                if(user != null)
                {
                    var passwordCheck = await this.signInManager.CheckPasswordSignInAsync(user, model.Password, false);
                    if (passwordCheck.Succeeded)
                    {
                        var claims = new List<Claim>
                        {
                            new Claim(JwtRegisteredClaimNames.Sub, user.Email),
                            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                            new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName)
                        };
                        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this.config["Tokens:Key"]));
                        var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
                        var token = new JwtSecurityToken(
                            this.config["Tokens:Issuer"],
                            this.config["Tokens:Audience"],
                            claims,
                            expires: DateTime.UtcNow.AddHours(3),
                            signingCredentials: credentials                            
                            );

                        return Ok(new
                        {
                            token = new JwtSecurityTokenHandler().WriteToken(token),
                            expiration = token.ValidTo
                        });
                    }

                }
                else
                {
                    return Unauthorized();
                }
            }

            return BadRequest();
        }

    }
}

In the register method we used the ModelState.IsValid to check if all the validation that we put in our RegisterDto has been met. If there is no error then we will check if the email is already register. If not we will add the user then assign a role to the new user.

In our Login method, after we verify the user credentials we generate a jason web token that the client application can use to authenticate its request.

Notice that we use values from configuration in Login method. Those configuration is not yet in our config file. Open appsettings.json and add config for tokens. Your appsettings should look like the code below. For the tokens key, you can put a random string in there.

{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=PayrollDb;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Tokens": {
    "Key": "adjas@$sds&&*(@@#$?>;;0adasaddFDA&^3ll;sada62;;;2%%^#$%^2",
    "Issuer": "localhost",
    "Audience": "localhost"
  }
}

Open Startup.cs file and update ConfigureServices method. We need to set default authentication scheme to JwtBearerDefaults.AuthenticationScheme and configure JWTBearer settings.

public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<PayrollContext>(options => options
            .UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
            services.AddIdentity<User, IdentityRole>().AddEntityFrameworkStores<PayrollContext>()
                .AddDefaultTokenProviders();
            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters()
                {
                    ValidIssuer = Configuration["Tokens:Issuer"],
                    ValidAudience = Configuration["Tokens:Audience"],
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"]))
                };
            });
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }
Advertisements

Now to try user authentication. Let’s add security in ValuesController. Add Authorize attribute in your ValuesController.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

namespace PayrollApp.Api.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    [Authorize]
    public class ValuesController : ControllerBase
    {
        // GET api/values
        [HttpGet]
        public ActionResult<IEnumerable<string>> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/values/5
        [HttpGet("{id}")]
        public ActionResult<string> Get(int id)
        {
            return "value";
        }
    }
}

Now run the application. We will use postman to test our api. You can download postman for free from postman website.

Open your postman and let’s try to register a new user.

Then let’s login the new user by posting to login endpoint.

Advertisements

Now to demonstrate our web api security. Let’s first call our values api with out using the token that we receive from login endpoint.

As you can see we get an unauthorized result. We can’t get the data from our values controller.

To get the data from values controller, we need to include the token in the request header. To do that click the Headers tab then add Authorization in the Key column the value is “Bearer <token>”.

Now you should be able to get the values from ValuesController.

Advertisements

Related Articles:

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s