Published: May 27 2021

.NET 5.0 - Hash and Verify Passwords with BCrypt

Tutorial built with .NET 5.0

Other versions available:

This is a quick example of how to hash and verify passwords in .NET 5.0 using the BCrypt.Net-Next password hashing library which is a C# implementation of the bcrypt password hashing function.

For more info on the BCrypt.Net-Next password hashing C# library see https://www.nuget.org/packages/BCrypt.Net-Next.

For more info on the underlying bcrypt password hashing function, see https://en.wikipedia.org/wiki/bcrypt.


Installing BCrypt.Net-Next via NuGet

.NET CLI: dotnet add package BCrypt.Net-Next

Visual Studio Package Manager Console: Install-Package BCrypt.Net-Next


Hashing a password in .NET 5.0

This code hashes the password "Pa$$w0rd" using bcrypt and stores the result in the passwordHash string variable.

string passwordHash = BCrypt.Net.BCrypt.HashPassword("Pa$$w0rd");


Verify a password against a hash in .NET 5.0

This code verifies the password "Pa$$w0rd" using bcrypt against the hash stored in the passwordHash variable.

bool verified = BCrypt.Net.BCrypt.Verify("Pa$$w0rd", passwordHash);


Example usage in a .NET User Service

Below is an example user service with a Register() method that saves an account with a hashed password and an Authenticate() method that verifies a provided password against the PasswordHash of a saved account. The password is hashed on line 57 and verified on line 38.

The service is a cut down version of the user service from a .NET API tutorial I posted recently, for more info or to download and test the API locally see .NET 5.0 - Simple API for Authentication, Registration and User Management.

using AutoMapper;
using BCryptNet = BCrypt.Net.BCrypt;
using System.Linq;
using WebApi.Authorization;
using WebApi.Entities;
using WebApi.Helpers;
using WebApi.Models.Users;

namespace WebApi.Services
{
    public interface IUserService
    {
        AuthenticateResponse Authenticate(AuthenticateRequest model);
        void Register(RegisterRequest model);
    }

    public class UserService : IUserService
    {
        private DataContext _context;
        private IJwtUtils _jwtUtils;
        private readonly IMapper _mapper;

        public UserService(
            DataContext context,
            IJwtUtils jwtUtils,
            IMapper mapper)
        {
            _context = context;
            _jwtUtils = jwtUtils;
            _mapper = mapper;
        }

        public AuthenticateResponse Authenticate(AuthenticateRequest model)
        {
            var user = _context.Users.SingleOrDefault(x => x.Username == model.Username);

            // verify password
            if (user == null || !BCryptNet.Verify(model.Password, user.PasswordHash))
                throw new AppException("Username or password is incorrect");

            // authentication successful
            var response = _mapper.Map<AuthenticateResponse>(user);
            response.JwtToken = _jwtUtils.GenerateToken(user);
            return response;
        }

        public void Register(RegisterRequest model)
        {
            // validate
            if (_context.Users.Any(x => x.Username == model.Username))
                throw new AppException("Username '" + model.Username + "' is already taken");

            // map model to new user object
            var user = _mapper.Map<User>(model);

            // hash password
            user.PasswordHash = BCryptNet.HashPassword(model.Password);

            // save user
            _context.Users.Add(user);
            _context.SaveChanges();
        }
    }
}


C# Using Alias Directive

One small thing to note in the above file is the using alias directive on the second line (using BCryptNet = BCrypt.Net.BCrypt;). This is to avoid having to enter the full path to the class for every call to a BCrypt method (e.g. BCrypt.Net.BCrypt.HashPassword()) due to the namespace and the class both having the same name (BCrypt). Another way around this is to move the using statement using BCrypt.Net; inside the namespace (namespace WebApi.Services) which would allow you to call BCrypt.HashPassword() directly. I chose the first approach to keep all of the using statements grouped together at the top of the file.

 


Need Some .NET Help?

Search fiverr for freelance .NET developers.


Follow me for updates

On Twitter or RSS.


When I'm not coding...

Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!


Comments


Supported by