ASP.NET Core authentication – In the beginning

While building my ASP.NET Core identity, Dapper Custom storage provider I found there wasn’t a lot of discussion of the ASPNETUserClaims functionality for fine “grained permissions”.

ASP.NET Core identity initial data model

ASP.NET Core identity Roles can also have individual claims but with the authorisation model of the legacy application I work on this functionality hasn’t been useful. We use role based authentication with a few user claims to minimise the size of our Java Web Tokens(JWT)

Visual Studio 2022 ASP.NET Core Web Application template options

The first step was to create a “bare-bones” ASP.NET Core Razor pages Web Application with Individual Accounts Authentication project

Default ASP.NET Core identity Web application Homepage

I tried to minimise the modifications to the application. I added EnableRetryOnFailure, some changes to names spaces etc. I also added support for email address confirmation with SendGrid and “authentication” link to the navabar in _Layout.cshtml.

@page
@model RolesModel
@{
    <table class="table">
        <thead>
            <tr>
                <th>Role</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var role in Model.Roles)
            {
                <tr>
                    <td>
                        @Html.DisplayFor(modelItem => role.Value)
                    </td>
                </tr>
            }
        </tbody>
    </table>
    <br/>
        <table class="table">
        <thead>
            <tr>
                <th>Claim Subject</th>
                <th>Value</th>
            </tr>
        </thead>

        <tbody>
            @foreach (var claim in Model.Claims)
            {
                <tr>
                    <td>
                        @Html.DisplayFor(modelItem => claim.Type)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => claim.Value)
                    </td>
                </tr>
            }
        </tbody>
    </table>
}

The “Authentication” page displays the logged in User’s Role and Claims.

namespace devMobile.AspNetCore.Identity.WebApp.EFCore.Pages
{
    [Authorize()]
    public class RolesModel : PageModel
    {
        private readonly ILogger<RolesModel> _logger;

        public List<Claim> Roles { get; set; }
        public List<Claim> Claims { get; set; }

        public RolesModel(ILogger<RolesModel> logger)
        {
            _logger = logger;
        }

        public void OnGet()
        {
            Roles = User.Claims.Where(c => c.Type == ClaimTypes.Role).ToList();

            Claims = User.Claims.Where(c => c.Type != ClaimTypes.Role).ToList();
        }
    }
}

Each user can have role(s), with optional claims, and some optional individual claims.

ASP.NET Core identity application Authentication information page

The WebApp.EFCore project is intended to be the starting point for a series of posts about ASP.NET Core identity so I have not included Cross-Origin Resource Sharing (CORS), Cross Site Request Forgery (CSRF) etc. functionality.

.Net version of SQL Server PWDCompare

One of my customers Hedgebook has a Microsoft SQL Server database with passwords that have been secured using PWDENCRYPT and PWDCOMPARE As part of a migration plan (away from this approach) we need to be able to validate passwords against hashes that have been generated with many versions of Microsoft SQL Server.

After some searching I found a stackoverflow post which described how to validate hashes up to SQL Server 2012 and I have added code to support more modern versions of SQL Server.

I had a chat with my boss and he approved me posting a console application wrapper for an anonymised version of the code as an aide to other developers.

This sample code is not production ready it is just to illustrate how the password hashes for older and newer versions of SQL Server can be validated in C#

//---------------------------------------------------------------------------------
// Copyright ® Feb 2018, devMobile Software
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// based on https://stackoverflow.com/questions/43879003/how-can-i-manually-check-passwords-hashed-with-sql-server-pwdencrypt-in-c-sharp
//
// Have added implementation for more modern SQL Server boxes and built as a console application
//---------------------------------------------------------------------------------
namespace devMobile.SqlServer.PWDCompareDemo
{
   using System;
   using System.Data.SqlClient;
   using System.Linq;
   using System.Security.Cryptography;
   using System.Text;

   public class Program
   {
      private const int DatabasePasswordHashLength = 256;
      private const int HeaderLength = 2;
      private const int SaltLength = 4;
      private const int Sha1HashLength = 20;
      private const int Sha512HashLength = 64;

      public static void Main(string[] args)
      {
         if (args.Length != 3)
         {
            Console.WriteLine("Expecting ConnectionString UserID Password");
            Console.WriteLine("Press ");
            Console.ReadLine();
            return;
         }

         string connectionString = args[0];
         string userId = args[1];
         string password = args[2];

         using (SqlConnection conn = new SqlConnection(connectionString))
         {
            conn.Open();

            using (SqlCommand cmd = new SqlCommand("SELECT Password FROM Users WHERE UserID=@UserID", conn))
            {
               cmd.Parameters.AddWithValue("@UserID", userId);

               using (SqlDataReader reader = cmd.ExecuteReader())
               {
                  if (reader.Read())
                  {
                     byte[] databasePasswordHash = new byte[DatabasePasswordHashLength];
                     reader.GetBytes(0, 0, databasePasswordHash, 0, databasePasswordHash.Length);

                     int header = BitConverter.ToChar(databasePasswordHash, 0);
                     byte[] salt = new byte[SaltLength];
                     Buffer.BlockCopy(databasePasswordHash, HeaderLength, salt, 0, salt.Length);

                     switch (header)
                     {
                        case 1: //SHA1 encryption for old SQL Server
                           byte[] sha1Hash = new byte[Sha1HashLength];
                           Buffer.BlockCopy(databasePasswordHash, HeaderLength + SaltLength, sha1Hash, 0, sha1Hash.Length);

                           HashAlgorithm sha1Hasher = SHA1.Create();
                           byte[] sha1Result = sha1Hasher.ComputeHash(Encoding.Unicode.GetBytes(password + Encoding.Unicode.GetString(salt)));
                           if (sha1Hash.SequenceEqual(sha1Result))
                           {
                              Console.WriteLine("SHA1 Password is good");
                           }
                           else
                           {
                              Console.WriteLine("SHA1 Password is bad");
                           }

                           break;

                        case 2: //SHA2-512 encryption for modern SQL Server
                           byte[] sha512Hash = new byte[Sha512HashLength];
                           Buffer.BlockCopy(databasePasswordHash, HeaderLength + SaltLength, sha512Hash, 0, sha512Hash.Length);

                           HashAlgorithm sha512Hasher = SHA512.Create();
                           byte[] sha512Result = sha512Hasher.ComputeHash(Encoding.Unicode.GetBytes(password + Encoding.Unicode.GetString(salt)));
                           if (sha512Hash.SequenceEqual(sha512Result))
                           {
                              Console.WriteLine("SHA512 Password is good");
                           }
                           else
                           {
                              Console.WriteLine("SHA512 Password is bad");
                           }

                           break;

                        default:
                           Console.WriteLine("Unknown header value something bad has happened");
                           break;
                     }
                  }
               }
            }
         }

         Console.WriteLine("Press ");
         Console.ReadLine();
      }
   }
}

NetMF MP3 Player Part 1

For one of my class projects the students build an NetMF MP3 player using a Netduino, MP3 shield, MicroSD card and some code. The first step is to learn about files, and directories by enumerating the contents of the MicroSD card.

public static void Main()
 {
    string[] musicFiles = Directory.GetFiles(@"\SD");

    foreach (string musicFile in musicFiles)
    {
       Debug.Print(musicFile);
    }
}

The NetMF implementation of GetFiles doesn’t support wildcards (unlike the full .Net Framework) so the list of files has to be manually filtered.

public static void Main()
{
   string[] musicFiles = Directory.GetFiles(@"\SD");

   foreach (string musicFile in musicFiles)
   {
      if (filePath.IndexOf(".mp3") != -1)
      {
         Debug.Print(musicFile);
      }
   }
}

The code above displayed
\SD\01 Pride (In the Name of Love).mp3
\SD\02 New Year’s Day.mp3
\SD\03 With or Without You.mp3
\SD\04 I Still Haven’t Found What I’m Looking For.mp3
\SD\05 Sunday Bloody Sunday.mp3
\SD\06 Bad.mp3
\SD\07 Where the Streets Have No Name.mp3
\SD\08 I Will Follow.mp3
\SD\09 The Unforgettable Fire.mp3
\SD\10 Sweetest Thing [The Single Mix].mp3
\SD\11 Desire.mp3
\SD\12 When Love Comes to Town.mp3
\SD\13 Angel of Harlem.mp3
\SD\14 All I Want Is You.mp3

For this project Directory.GetFiles was used (rather than Directory.EnumerateFiles) because the list files on the MicroSD will be used in other parts of the application.

The parsing and processing of file paths is important and can if done wrong can introduce hard to find issues(e.g. directory traversal attacks)

Debug.Print(musicFile .Substring( 0, musicFile.LastIndexOf(".mp3")));

Updating the code to use the built in System.IO.Path functionality

public static void Main()
{
   string[] MusicFiles = Directory.GetFiles(@"\SD");

   foreach (string musicFile in MusicFiles)
   {
      if (Path.GetExtension(musicFile) == ".mp3" )
      {
         Debug.Print(Path.GetFileNameWithoutExtension(musicFile));
      }
   }
}

The next step is to load and play the MP3 files using a provided library.