Adding authentication in AspNetCore 2.0

<EDIT> A few days ago they changed it again….
Instead of .AddCookieAuthentication(….
It’s now just .AddCookie();
</EDIT>

Or rather Aspnetcore 2.0.0-preview2-006497 since they changed it again….

First, download the latest bits from .NET Core 2.0 and install it.
Open a developer command prompt and check version with

dotnet –version

It should say 2.0.0-preview2-006497 for you to be sure that my instructions will work 🙂
Create a new folder for your project and create a new mvc project with

dotnet new mvc

After it is done we will add the dependencies for authentication

dotnet add package Microsoft.AspNetCore.Authentication -v “2.0.0-preview2-final”

and

dotnet add package Microsoft.AspNetCore.Http -v “2.0.0-preview2-final”

Now we add, one by one the authentication providers we want. In Startup.cs in ConfigureServices:


services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookieAuthentication(CookieAuthenticationDefaults.AuthenticationScheme, option =>
{
option.LoginPath = "/home/login";
})
.AddTwitterAuthentication(o =>
{
o.ConsumerKey = Configuration["Authentication:Twitter:ConsumerKey"];
o.ConsumerSecret = Configuration["Authentication:Twitter:ConsumerSecret"];
});

view raw

add-auth.cs

hosted with ❤ by GitHub

The biggest change in this version is perhaps that you only add

app.UseAuthentication();

To the pipeline in the Configure method.

So. Done with that part. Oh, forgot the usings. Add these to the top


using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Rewrite;
using Microsoft.AspNetCore.Mvc;

view raw

using.cs

hosted with ❤ by GitHub

A few more than necessary but I will get to them. Now the project should start with

dotnet run

But it still allows for anonymous access.

Add the attribute [Authorize] to your HomeController together with a matching using like so:


using Microsoft.AspNetCore.Authorization;
namespace [namespace].Controllers
{
[Authorize]
public class HomeController : Controller
{

view raw

controller1.cs

hosted with ❤ by GitHub

So. Again start the project and browse to http://localhost:5000/. You will be redirected to /home/login and get an error since that page does not exist (yet).

In the HomeController.cs add this code


[AllowAnonymous]
public async Task<IActionResult> Login(string username, string password)
{
if (IsValidUser(username, password))
{
var claims = new List<Claim>(2);
claims.Add(new Claim(ClaimTypes.Name, username));
claims.Add(new Claim(ClaimTypes.Role, "GroupThatUserIsIn",
ClaimValueTypes.String, "IHaveIssuedThis"));
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(new ClaimsIdentity(claims,
CookieAuthenticationDefaults.AuthenticationScheme)));
return RedirectToAction("Index");
}
return View();
}
private bool IsValidUser(string username, string password)
{
return username == "foo" && password == "bar";
}

This is used when you manage all the users and passwords yourself (please don’t).
But seriously, sometimes you have an old back-end system that you are building a new front-web for and it has all the user info.

I created a super simplistic view for this action. Create a new file in the folder Views/Home called Login.cshtml with this content


<form action="/home/login" method="post">
<input name="username" />
<input name="password" type="password"/>
<input type="submit" value="Go"/>
</form>
<a href='/login-twitter' \">I prefer Twitter</a>

view raw

Login.cshtml

hosted with ❤ by GitHub

Told you. Simplistic. Make sure these “usings” are in place in your HomeController.cs


using Microsoft.AspNetCore.Authorization;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Authentication;

Now, go back up to the project root folder and run the application again. This time the login page is displayed. If you try to login with your hard coded username and password you will be logged in and redirected to /. If you inspect the ClaimsPrincipal when debugging you will see that your claims are visible under the Identity-property.

cookieinfo

Great. Let the user logout as well.


public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return RedirectToAction("Index");
}

Add a link somewhere that redirects the user to /home/logout. Done.

So. How about Twitter?

First, add a new app at apps.twitter.com . Click “Create new app” and fill out the form. You can set callback url to localhost:5000. Go to the Keys and Access Tokens-tab and copy them to your appSettings.json file


},
"Authentication": {
"Twitter": {
"ConsumerKey": "<Your key here>",
"ConsumerSecret": "<Your secret here>"
}
}
}

Please note that if you intend to publish the code somewhere, don’t store these credentials here. Use secrets instead.

Remember the fancy looking login page? The Twitter-link was /login-twitter. Just because it is fun I will hard-wire this url into the processing pipeline.

So. Head over to Startup.cs and paste this code into the Configure-method.


app.Map("/login-twitter", login =>
{
login.Run(async context =>
{
await context.ChallengeAsync("Twitter", new AuthenticationProperties() {
RedirectUri = "/" });
return;
});
});

view raw

Startup.cs

hosted with ❤ by GitHub

Now you redirected to a Twitter page and depending if you are already logged into Twitter or not the page either asks you to logon or only to authorize your new app to connect to Twitter.

Tip: If you want to dress the current user with more claims than Twitter sent, you can always add them. Like your internal user id of that Twitter-identified user.


public async Task<IActionResult> Index()
{
var principal = User.Identity as ClaimsIdentity;
var idClaim = principal.Claims.Where(i => i.Type == "https://marcusclasson.com/claims/id")
.SingleOrDefault();
if(idClaim == null)
{
principal.AddClaim(new Claim("https://marcusclasson.com/claims/id", "MyCustomId"));
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(User);
}
return View();
}

Code on Github

Done.

Advertisement

How to store and use passwords in .NET

Edit: I uploaded a sample project to GitHub here. End Edit.

This is really one of my pet peeves. The last five years of stolen user accounts really got the community on its feet and the internet is oozing of advice of how to do this. Most of them well intended but badly implemented. Many of the bad ones use good practices but put them all in a blender. Not all algorithms are meant to be mixed. My version I intend to keep clean and use mainstream encryption.

First what parts are we talking about?

  1. Storing passwords
  2. Using passwords

What good is a stored password if you cannot use it afterwards. This, however, doesn’t mean that you ever should be able to recreate (or decrypt) the original password. Always compare scrambled password to scrambled password. And yes, the downside is that the user never can ask to get his password sent thru email in case they forgot. There’s more to this than just number crunching the hash. How to set the cookie properly is one place where good intentions crumble…

Anyway, this is my take…

I won’t go into to much detail of all the inner workings of hashing algorithms and such. But two things is important, salting and stretching. Both of which is taken care of when using the standard algorithms i .NET.

Here is a the code that generates the salt and the salted password

public static HashedPassword Generate(string password)
{
byte[] _salt = new byte[8];
using (RNGCryptoServiceProvider csp = new RNGCryptoServiceProvider())
{
csp.GetBytes(_salt);
}
byte[] _password = System.Text.Encoding.UTF8.GetBytes(password);
Rfc2898DeriveBytes k1 = new Rfc2898DeriveBytes(_password, _salt, 10000);
var _saltedPasswordHash = k1.GetBytes(24);
return new HashedPassword()
{
Password = Convert.ToBase64String(_saltedPasswordHash),
Salt = Convert.ToBase64String(_salt)
};
}
public struct HashedPassword
{
public string Password { get; set; }
public string Salt { get; set; }
}
view raw gistfile1.cs hosted with ❤ by GitHub

The key areas in this code is the use of RNGCryptoServiceProvider for generating the hash. Don’t create you own randomizer! And don’t ever use the same salt for all users and then hide it somewhere.

The main difference between a normal random number generator (RNG) and Cryptographically Secure Pseudo Random Number Generator (CSPRNG) – that’s a mouthful – lies in the predictability. Normal random numbers looks random but they really aren’t.

Ok, so now we have a salt. Next we are going to use Microsofts PBKDF2-implementation Rfc2898DeriveBytes to generate the key. The key in this case, is a hash that could be used as an parameter to other cryptographic stuff like the TripleDES encryption algorithm for encrypting a file.

It is worth noting that the key is not an encrypted version of the password.

A PBKDF (Password Based Key Derivation Function) is in it self a CSPRNG using the password and salt to create its Initialization Vector. After that you can use it to generate as many bytes as you like.

Sequential calls to GetBytes will not return the same bytes but the next bytes in the sequence.

Rfc2898DeriveBytes k1 = new Rfc2898DeriveBytes(_password, _salt, 10000);
var _saltedPasswordHash = k1.GetBytes(12);
Debug.WriteLine(Convert.ToBase64String(_saltedPasswordHash));
_saltedPasswordHash = k1.GetBytes(12);
Debug.WriteLine(Convert.ToBase64String(_saltedPasswordHash));
Rfc2898DeriveBytes k2 = new Rfc2898DeriveBytes(_password, _salt, 10000);
_saltedPasswordHash = k2.GetBytes(24);
Debug.WriteLine(Convert.ToBase64String(_saltedPasswordHash));
view raw gistfile1.cs hosted with ❤ by GitHub

This will generate this output:

b9oLeVK9RKNatt7X
G1MQqtPYCZnlabPR

b9oLeVK9RKNatt7XG1MQqtPYCZnlabPR

It is important to remember to also store the salt alongside with the password in your database. Why, you say, are you storing the salt? If the database get snatched the hacker also has the salt.

Yes correct, but first of all you need it when validating at login time and furthermore the salting makes it impossible to use rainbow tables.

The hacker has to calculate every possible password, salt it, hash it and then do the compare. Note that we also did stretching, we ran the hashing 10000 times.
When our user tries to log in again, we take his newly entered password and hash it using the same salt which we retrieved from the database.

public static bool Validate(string passwordHash, string saltHash, string enteredPassword)
{
byte[] _password = System.Text.Encoding.UTF8.GetBytes(enteredPassword);
Rfc2898DeriveBytes keyEntered = new Rfc2898DeriveBytes(_password, Convert.FromBase64String(saltHash), 10000);
return Convert.ToBase64String(keyEntered.GetBytes(24)) == passwordHash;
}
view raw Validate hosted with ❤ by GitHub

Using this is pretty straightforward

var keyAndSalt = Hash.Generator.Generate("P@ssword2013");
bool isEqual1 = Hash.Generator.Validate(keyAndSalt.Password, keyAndSalt.Salt, "Password2013");
//isEqual1 == false
bool isEqual2 = Hash.Generator.Validate(keyAndSalt.Password, keyAndSalt.Salt, "P@ssword2013");
//isEqual2 == true
view raw gistfile1.cs hosted with ❤ by GitHub

Now, as a final note about using the passwords:

Ok, so the user is now logged in to your system. The password is not stored anywhere and the hash is safe with you.

Somehow you have to remember during the length of the session that he or she is auhenticated. Normally you do this using browser cookies. Pretty easy to do, but if you just add it as a normal cookie it is susceptible to eavesdropping and hijacking of the session.

Three things to remember:

  • Always use https from the login screen and on. When the user clicks “login” you switch to https and stay there.
  • Set the authentication cookie to be Secure and HttpOnly to mitigate most of the threats like XSS.
    Secure means that it will only be sent when doing https calls. HttpOnly means the the cookie will only be used by the browser. Javascript cannot see it.
  • Do not use mixed content, i.e. you serve the html securely via https but some script och images get fetched through normal http. You will leak cookies! However, the steps above will normally stop this.