ElCamino.AspNetCore.Identity.AzureTable - Technical Overview 3 docs Icon identityazuretable

This project provides a high performance cloud solution for ASP.NET Identity Core using Azure Table storage replacing the Entity Framework / MSSQL provider.

Technical Overview

ElCamino.AspNetCore.Identity.AzureTable >= 2.2

High Level Dependencies

High Level Dependencies

ElCamino.AspNetCore.Identity.AzureTable.dll acts as the data access layer under the Microsoft.AspNetCore.Identity.dll classes UserManager and RoleManager by implementing the necessary interfaces on the UserStore and RoleStore respectively. These classes consume the IdentityCloudContext and the other Identity Model classes described below to persist the user and role information according to the business logic dictated by the respective UserManager and RoleManager classes.

UserStore and RoleStore Classes

UserStore and RoleStore Classes

IdentityCloudContext and Connections

IdentityCloudContext that enables access to the Azure Table storage service and exposes the tables.

The IdentityCloudContext default constructor will look for the IdentityConfiguration object that contains the table storage connection and other settings. The example is shown storing and loading these settings in a .json configuration file. Add the default local connection string to the Azure Storage Emulator or to an Azure Table Storage account. TablePrefix can be left blank, any text here will prefix the Azure table storage names of AspNetUser, AspNetRoles, and AspNetIndex. StorageConnectionString is the Azure table storage connection string. LocationMode can be left empty (defaults to PrimaryOnly). Other storage location modes are listed here and must be used with a RA-GRS storage account if changed from the default. *TableName values can be null or left empty and default values are shown in the example below.

{
...
    "IdentityAzureTable": {
        "IdentityConfiguration": {
            "TablePrefix": "mvc6"
            "StorageConnectionString": "UseDevelopmentStorage=true;"
            "IndexTableName": "AspNetIndex"
            "RoleTableName": "AspNetRoles"
            "UserTableName": "AspNetUsers"
        }
    }...
}

Identity Model to Azure Table Relationships

Identity Model to Azure Table Relationships
AspNetRoles Entities
AspNetIndex Entities

AspNetUsers

Identity ModelPartitionKeyRowKeyCardinality
IdentityUserHashed(UserId)Hashed(UserId)1 UserId : 1 IdentityUser, UserId uniquely identifies IdentityUser.
IdentityUserClaimHashed(UserId)Hashed(ClaimType, ClaimValue)1 IdentityUser : 0,Many IdentityUserClaim(s)
IdentityUserLoginHashed(UserId)Hashed(LoginProvider, ProviderKey)1 IdentityUser : 0,Many IdentityUserLogin(s)
IdentityUserRoleHashed(UserId)Hashed(RoleName)1 IdentityUser : 0,Many IdentityUserRole(s)
IdentityUserTokenHashed(UserId)Hashed(LoginProvider, Name)1 IdentityUser : 0,Many IdentityUserToken(s)

AspNetIndex

Identity ModelTypePartitionKeyRowKeyIdCardinality
IdentityUserIndexUserName IndexHashed(UserName)Hashed(UserId)Hashed(UserId)1 UserName : 1 IdentityUser
IdentityUserIndexUser Email IndexHashed(User Email)Hashed(UserId)Hashed(UserId)1 Email : 1,Many IdentityUser(s) (unless the UserManager is set to enforce unique email addresses per user, then 1 Email : 1 IdentityUser)
IdentityUserIndexUser Login IndexHashed(LoginProvider, ProviderKey)Hashed(LoginProvider, ProviderKey)Hashed(UserId)1 IdentityUserLogin : 1 IdentityUser
IdentityUserIndexUser Claim IndexHashed(ClaimType, ClaimValue)Hashed(UserId)Hashed(UserId)1 IdentityUserClaim : 1,Many IdentityUser(s). Used to lookup users by claim.
IdentityUserIndexUser Role IndexHashed(RoleName)Hashed(UserId)Hashed(UserId)1 IdentityUserRole : 1,Many IdentityUser(s). Used to lookup users by role.

AspNetRoles

Identity ModelPartitionKeyRowKey
IdentityRoleHashed(First Char of RoleName)Hashed(RoleName)

The properties of the respective identity model class are stored as a field in the table unless marked with the ignore attribute. Inheriting and extending these classes one can add fields whenever your extended class is saved. Also, the Hashed() psydo-code is a SHA1 hash of the input found in the HashKeyHelper class. The Hashed() algorithm was introduced to overcome the size limitation of the rowkey for claim values and overall is a better algorithm for setting deterministic key sizes.

Performance

The AspNetUsers table is designed to keep all of the user's data in a single table and in a table partition for each user. This results in a single query using the user's id (partition key) to get all of the data in a single query when the user's id is known. The user's id is an 'Hashed' version of the UserId guid. When the user id is not known, the AspNetIndex table is queried first either by user's email UserStore.FindByEmailAsync(), user's username UserStore.FindByUserNameAsync() or external login information UserStore.FindAsync() to get the user's id to then query by user id for the complete user information. The AspNextIndex is always queried by partition and rowkey making that query extremely fast. See above for the details of the AspNetIndex partition and rowkey strategy. In addition, Roles and Claims mapping to users are also indexed for use by GetUsersInRoleAsync() and GetUsersForClaimAsync(), respectively.

AspNetRoles table also uses a composite of the RoleName for the partition and row keys making it possible query the table by its primary key optimizing performance.

Testing

A full suite of integration tests against this assembly ElCamino.AspNetCore.Identity.AzureTable.Tests.csproj

Release Management

  • Full suite of integration tests against this assembly at 100% pass rate against the Azure Local Emulator and against a live Azure Storage account. 100% pass rate is required before release.
  • Releases available on NuGet..
  • NuGet Badge