Repository

Repository classes

In the Data folder create a new IAuthRepository interface.

Add the following methods declarations to the interface

  Task<User> Register(User user, string password);
  Task<User> Login(string username, string password);
  Task<bool> UserExists(string username);

In the Data folder create a new AuthRepository class. Sepcify that the class implements IAuthRepository and use right click on the interface name to Implement Interface. Then mark all the methods async.

Create a constructor that takes a DatingContext parameter and set it to a private field.

Implementation

To register a user we need a private method to hash a plaintext password.

We use theĀ Hash Based Message Authentication Code class System.Security.Cryptography.HMACSHA512.

using(var hmac = new System.Security.Cryptography.HMACSHA512() )
{
  passwordSalt = hmac.Key;
  passwordHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
}

To Login a user we need another private method to check that password, passwordHash and passwordSalt match. Again we use HMACSHA512, then compare byte to byte the two hashes.

using (var hmac = new System.Security.Cryptography.HMACSHA512(passwordSalt))
{
    byte[] computedHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));

    if (passwordHash.Length != computedHash.Length) return false;

    for (int i = 0; i < computedHash.Length; i++)
    {
        if(passwordHash[i] != computedHash[i]) return false;
    }

    return true;
}

The implementation of the three methods of the interface is trivial.

using System;
using System.Threading.Tasks;
using DatingApp.API.Models;
using Microsoft.EntityFrameworkCore;

namespace DatingApp.API.Data
{
    public class AuthRepository : IAuthRepository
    {
        private readonly DatingContext _context;

        public AuthRepository(DatingContext context)
        {
            _context = context;
        }

        public async Task<User> Login(string username, string password)
        {
            var user = await _context.Users.FirstOrDefaultAsync(x => x.Username == username);

            bool isPasswordValid = CheckPasswordHash(password, user.PasswordHash, user.PasswordSalt);

            if (isPasswordValid)
            {
                return user;
            }
            else
            {
                return null;
            }
        }

        public async Task<User> Register(User user, string password)
        {
            // The password must be a not empty string
            if (string.IsNullOrWhiteSpace(password)) return null;

            (user.PasswordHash, user.PasswordSalt) = ComputeHashSalt(password);
            await _context.Users.AddAsync(user);
            await _context.SaveChangesAsync();

            return user;
        }

        public async Task<bool> UserExists(string username)
        {
            return await _context.Users.AnyAsync(x => x.Username == username);
        }

        private (byte[] passwordHash, byte[] passwordSalt) ComputeHashSalt(string password)
        {
            using (var hmac = new System.Security.Cryptography.HMACSHA512())
            {
                byte[] passwordSalt = hmac.Key;
                byte[] passwordHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));

                return (passwordHash: passwordHash, passwordSalt: passwordSalt);
            }
        }

        private bool CheckPasswordHash(string password, byte[] passwordHash, byte[] passwordSalt)
        {
            using (var hmac = new System.Security.Cryptography.HMACSHA512(passwordSalt))
            {
                byte[] computedHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));

                if (passwordHash.Length != computedHash.Length) return false;

                for (int i = 0; i < computedHash.Length; i++)
                {
                    if(passwordHash[i] != computedHash[i]) return false;
                }

                return true;
            }
        }
    }
}

Registration

We need to register the repository as a service for dependency injection in Startup.cs inside the ConfigureServices method.

Add the repository as a Scoped service, it’s lifetime will be that of a single request.

The interface will be used in the controller constructors or in other DI points and a single instance of the concrete implementation will be created for each request.

public void ConfigureServices(IServiceCollection services)
{
  ...
  services.AddScoped<IAuthRepository, AuthRepository>();
  ...
}