Setting up for Users
Reading
Setting up for Users
To keep track of the user that created restaurants and reviews, we will need to add the idea of users and login/logout features.
We will create a user model and the associated controller to manage them. Then we will make user interface components for creating accounts and for logging in.
User Model
To begin, create a user model that stores information about the user. Each user will have the following attributes:
- Id
- Full Name
- Email Address
- Password
It is unwise to store unencrypted passwords in our database. Thus we will not keep the raw password, but a hashed password.
Hashing Passwords
The idea of a hashed password relies on the concept of a "one-way function", that is a function that is fast and easy to compute in one direction but difficult to calculate in the other.
Let us take a look at the idea of a "two-way function". A simple example would
be double
, which takes a number and multiplies it by two. If I give you the
input of this function, say 42
, you can quickly and reliably compute the
output, 84
. If I provide you with output, say 246
, it is trivial for you to
figure out what a corresponding input would be: 123
. This process would be
simple regardless of the size of the numbers. Given 24686850904684
, you can
quickly figure out what the corresponding input is.
A classic example of a one-way function is the prime factorization function.
Given two prime numbers, say 17
and 5
, it is easy to multiply them together
and get 85
. Given a number like 85
, it isn't too hard to figure out which
two prime numbers multiply together to get that number. This isn't true as the
number gets larger. If I give you the number 682654107378822049
it isn't so
trivial to compute the two numbers that are its prime factors (the answer is
982451653
and 694847533
)
For something like a password, we will use the idea of a hashing function
. A
hashing function
attempts to take an input value and compute a fixed size and
mostly unique value. Small changes in the input should make a large and
unpredictable change in the output.
A popular hashing function is SHA256
. If we take the text dotnet
and process
it with this algorithm we get back out the result:
3831fff4af76125e90081ac7eb855a1bcce0733045f9d26cd620466e0d4acf97
. If we take
the text ditnet
, just one letter different we get
fb89fe75f8be03f17435f563121e940360cd9fcfcbd3f8978b59c160fdaca711
Given a result of a SHA256
hash, it is very difficult to work out what text
generated it.
We will be using the built-in dotnet
hashing algorithm based on
PBKDF2
, a strong password hashing
algorithm.
Defining our model
We will want to treat the Id
, FullName
, and Email
as we have other fields
for our model. For the password, we will be creating and storing a
HashedPassword
in the database. Mark this field as JsonIgnore
so it skipped
by serialization and not exposed in any API results.
We also wish to be able to assign a plain text password to a user. The setting
of this plain text password should have the effect of hashing that value and
storing it in the HashedPassword
property. We will also need a way to validate
a user password.
using System.ComponentModel.DataAnnotations;using System.Text.Json.Serialization;using Microsoft.AspNetCore.Identity;namespace TacoTuesday.Models{public class User{public int Id { get; set; }[Required(ErrorMessage = "You must provide your name.")]public string FullName { get; set; }[Required(ErrorMessage = "You must provide your email.")]public string Email { get; set; }[JsonIgnore]public string HashedPassword { get; set; }// Define a property for being able to _set_ a passwordpublic string Password{// Define only the `set` aspect of the propertyset{// When set, use the PasswordHasher to encrypt the password// and store the result in our HashedPasswordthis.HashedPassword = new PasswordHasher<User>().HashPassword(this, value);}}// Add a method that can validate this user's passwordpublic bool IsValidPassword(string password){// Look to see if this password, and the user's hashed password can matchvar passwordVerification = new PasswordHasher<User>().VerifyHashedPassword(this, this.HashedPassword, password);// Return True if the verification was a successreturn passwordVerification == PasswordVerificationResult.Success;}}}
We will also mark the FullName
and the Email
as required since we'll use the
email as the login, and we want to be able to address the user by name.
This class uses a custom setter
for the Password
, allowing us to set it from
the API. That setter hashes the password and assigns the user's HashedPassword
property (stored in the database)
We also have a method IsValidPassword
that can identify if we have a valid
password.
Generating a migration
Add the Users
to the DatabaseContext
class:
// Tell the context about the User collection/tablepublic DbSet<User> Users { get; set; }
Add the migration:
dotnet ef migrations add CreateUserModel
After validating the migration looks good, run it:
dotnet ef database update