2.9: Adding Login Functionality

Add UserForLogin DTO

In your Models/User folder - add another class called UserForLogin.cs.

This will be the same as our the register DTO we created earlier. We could use that here - but, we will be making changes later, so it's better to go ahead and have two.

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

Adding Login Method to Controller

Now we're finally ready to write our Login() method.

In this method, we don't need to validate the user's input. If their username and/or passwords don't match - we don't want to give them too much information about what was wrong. We'll just return Unauthorized().

[HttpPost("Login")]
public async Task<IActionResult> Login([FromBody] UserForLogin userForLogin)
{
    var userFromDb = await _authService.Login(userForLogin.Username.ToLower(), userForLogin.Password);

    if (userFromDb == null)
        return Unauthorized();

    //  GENERATE TOKEN
    var tokenHandler = new JwtSecurityTokenHandler();
    var key = new byte[0];                               // we'll add this after adding DI later
    var tokenDescriptor = new SecurityTokenDescriptor    //  Describes information we want to include in our token
    {
        Subject = new ClaimsIdentity(new Claim[]        //  Payload
        {
            new Claim(ClaimTypes.NameIdentifier, userFromDb.Id.ToString()),
            new Claim(ClaimTypes.Name, userFromDb.Username)
        }),
        Expires = DateTime.Now.AddDays(1),
        SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
            SecurityAlgorithms.HmacSha512Signature)
    };
    var token = tokenHandler.CreateToken(tokenDescriptor);  //  Create token
    var tokenString = tokenHandler.WriteToken(token);       //  to string (from byte[])

    return Ok( new { tokenString });                        //  Return 200, passing along tokenString
}

There is a lot going on here. Some comments have been added to help make it more clear what's going on.

We're getting the user from the database and checking if it's null -- if so, we return Unauthorized().

We're using a SecurityTokenDescriptor to format our token - right now we're giving our payload two pieces of information: the Id and username.

We're using JwtSecurityTokenHandler to actually create the token with the descriptor we made and also to write it to a string.

Register Authentication and Add Middleware in Startup Class

Now we need to register our key in our DI container and configure our middleware to use authentication.

In your Startup.cs class add the following to the ConfigureServices() method:

public void ConfigureServices(IServiceCollection services)
{
    var key = Encoding.ASCII.GetBytes(Configuration.GetSection("AppSettings:Token").Value);         // <---- Added
    services.AddDbContext<EFConnectContext>(x => x.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
    services.AddMvc();
    services.AddScoped<IAuthService, AuthService>();
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)                              // <---- Added
            .AddJwtBearer(options => {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(key),
                    ValidateIssuer = false,
                    ValidateAudience = false
                };
            });
}

Next, to add authentication to the middleware - add this above the call to UseMvc()

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    app.UseAuthentication();                //  <---- Added
    app.UseMvc();
}

Adding Key to AuthController and Login() Method

Back in our AuthController, we can now access our key.

First, let's inject it into our constructor (remember ctrl + . to create a field from parameter):

private readonly IConfiguration _config;
public AuthController(IAuthService authService, IConfiguration config)
{
    _config = config;
    _authService = authService;
}

Again - we're only injecting interfaces. This is easily swapped out in our Startup class.

Now, we can use this in our Login() method:

var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_config.GetSection("AppSettings:Token").Value);   // <---- Added
var tokenDescriptor = new SecurityTokenDescriptor

Last updated