AspNetCore.Identity.AmazonDynamoDB 3.0.0

dotnet add package AspNetCore.Identity.AmazonDynamoDB --version 3.0.0
                    
NuGet\Install-Package AspNetCore.Identity.AmazonDynamoDB -Version 3.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="AspNetCore.Identity.AmazonDynamoDB" Version="3.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AspNetCore.Identity.AmazonDynamoDB" Version="3.0.0" />
                    
Directory.Packages.props
<PackageReference Include="AspNetCore.Identity.AmazonDynamoDB" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add AspNetCore.Identity.AmazonDynamoDB --version 3.0.0
                    
#r "nuget: AspNetCore.Identity.AmazonDynamoDB, 3.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package AspNetCore.Identity.AmazonDynamoDB@3.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=AspNetCore.Identity.AmazonDynamoDB&version=3.0.0
                    
Install as a Cake Addin
#tool nuget:?package=AspNetCore.Identity.AmazonDynamoDB&version=3.0.0
                    
Install as a Cake Tool

AspNetCore.Identity.AmazonDynamoDB

Build Status codecov NuGet

An ASP.NET Core Identity provider for DynamoDB, targeting .NET 8, 9 and 10. On .NET 10 it also stores passkey (WebAuthn) credentials through IUserPasskeyStore<TUser>.

Getting Started

You can install the latest version via Nuget:

> dotnet add package AspNetCore.Identity.AmazonDynamoDB

Then you use the stores by calling AddDynamoDbStores on IdentityBuilder, with the user type and, when roles are used, the role type:

services
    .AddIdentityCore<DynamoDbUser>()
    .AddRoles<DynamoDbRole>()
    .AddDynamoDbStores<DynamoDbUser, DynamoDbRole>()
    .Configure(options =>
    {
        options.BillingMode = BillingMode.PROVISIONED; // Default is BillingMode.PAY_PER_REQUEST
        options.ProvisionedThroughput = new ProvisionedThroughput
        {
            ReadCapacityUnits = 5, // Default is 1
            WriteCapacityUnits = 5, // Default is 1
        };
        options.DefaultTableName = "my-custom-identity-table-name"; // Default is identity
        options.ResolveRoleNames = true; // Default is false, see Role Names below
    });

Finally, you need to ensure that tables and indexes have been added:

AspNetCoreIdentityDynamoDbSetup.EnsureInitialized(serviceProvider);

Or asynchronously:

await AspNetCoreIdentityDynamoDbSetup.EnsureInitializedAsync(serviceProvider);

The table is created if it doesn't exist. If it already exists, any global secondary index that is missing from it (such as CredentialId-index, used to look up users by passkey) is added and the call waits until the index is active. New indexes use provisioned throughput from the options when the existing table is provisioned, and on-demand capacity otherwise. It's safe to call from several instances at the same time.

The call needs the dynamodb:DescribeTable permission on the table, plus dynamodb:CreateTable to create it and dynamodb:UpdateTable to add a missing index. Without dynamodb:DescribeTable it falls back to dynamodb:ListTables and only checks that the table exists. If an index can't be added because dynamodb:UpdateTable is missing, a warning is logged instead of failing.

The IServiceProvider overloads log progress and warnings through the registered ILoggerFactory.

Role Names

Users store the normalized names of their roles, so by default UserManager.GetRolesAsync returns ADMIN for a role named Admin, and that is also the value of the role claims in the user's cookie. ClaimsPrincipal.IsInRole and [Authorize(Roles = "...")] compare role names case sensitively, so they need to use the normalized name.

Set ResolveRoleNames (or call ResolveRoleNames() on the builder) to return the names of the roles instead, as the Entity Framework Core stores do. Each role is then looked up by its normalized name when the user's roles are read, and roles that can't be found are returned with their normalized name.

Tests

To run the tests, you need to have DynamoDB running locally on localhost:8000. This can easily be done using Docker and the following command:

docker run -p 8000:8000 amazon/dynamodb-local

Table Layout

Everything lives in one table with a PartitionKey and a SortKey and no secondary indexes. A user's items share its partition (USER#{id}): the user itself, its claims, logins, roles, tokens and passkeys. Values that are looked up directly have a lookup item of their own, which is one strongly consistent read: USERNAME#{normalized}, LOGIN#{provider}#{key}, PASSKEY#{credentialId} and ROLENAME#{normalized} point at the id they belong to, and the partitions EMAIL#{normalized}, CLAIM#{type}#{value} and USERROLE#{name} list the users that hold them. A user and its username lookup are written in one transaction, as are a role and its name lookup and each login or passkey with its lookup, so a username, role name, login or passkey that another user holds fails the write with Identity's DuplicateUserName, DuplicateRoleName, LoginAlreadyAssociated or a DuplicatePasskey error. Emails are not unique, as Identity leaves that to RequireUniqueEmail; FindByEmailAsync returns one of the users with the email.

Upgrading from 2.x

Version 2 found users and roles through seven global secondary indexes. Version 3 reads and writes the same items in the same format, so no data changes, but it needs the lookup items described above, which an existing table does not have. Upgrade like this:

  1. With version 3 installed, run AspNetCoreIdentityDynamoDbSetup.MigrateAsync(serviceProvider) (or DynamoDbTableSetup.MigrateAsync(options, client)) against the table. It scans the table once and writes the lookup items for every user, role, claim, login, role membership and passkey. It is safe to run while version 2 instances are serving traffic, and safe to run again. The result says how many items it wrote lookups for.
  2. Deploy version 3. EnsureInitialized logs a warning as long as the legacy indexes exist, and nothing else; it never scans or changes an existing table.
  3. Run MigrateAsync once more, to cover anything a version 2 instance wrote during the rollout.
  4. Run AspNetCoreIdentityDynamoDbSetup.RemoveLegacyIndexesAsync(serviceProvider) to drop the seven indexes, one at a time as DynamoDB requires. Until then the indexes are kept up to date by DynamoDB and cost storage and write capacity, but nothing reads them. This needs dynamodb:UpdateTable; without it a warning is logged and the indexes stay.

What changes for an application:

  • Lookups by name, email, login or passkey are two strongly consistent reads instead of one eventually consistent index query, so a user is found right after it was created.
  • Creating or updating a user is a transaction of the user item and its lookups, which DynamoDB bills at twice the write units of a plain write. Duplicate usernames, role names, logins and passkeys fail with the Identity errors above instead of relying on the managers' checks.
  • Register the stores with AddDynamoDbStores<TUser>() or AddDynamoDbStores<TUser, TRole>(); the non-generic overload still works but needs dynamic code, so it is not for trimmed or Native AOT applications.
  • Custom properties on a DynamoDbUser or DynamoDbRole subclass are stored only when the subclass overrides WriteAttributes and ReadAttributes, see Adding Attributes.
  • DynamoDbTableSetup.EnsureAliasCreated and DynamoDbOptionsMonitor are gone: the stores read the table name from the options on every call and no longer use the SDK's object persistence model.

Native AOT

The package is trim and Native AOT compatible: the stores read and write items through the low-level DynamoDB client, with no reflection. Register the stores with the generic AddDynamoDbStores<TUser>() or AddDynamoDbStores<TUser, TRole>(); the non-generic AddDynamoDbStores() builds the store types from the builder's user and role types at runtime and is marked as requiring dynamic code.

Adding Attributes

To add custom attributes to the user or role model, create a class that extends DynamoDbUser or DynamoDbRole, add the properties, and override WriteAttributes and ReadAttributes to store them. The AttributeValues extension methods convert strings, booleans, numbers, dates, lists and maps the way the store does.

public class CustomUser : DynamoDbUser
{
    public string? ProfilePictureUrl { get; set; }

    protected override void WriteAttributes(Dictionary<string, AttributeValue> item)
    {
        base.WriteAttributes(item);
        item.Set("ProfilePictureUrl", ProfilePictureUrl);
    }

    protected override void ReadAttributes(Dictionary<string, AttributeValue> item)
    {
        base.ReadAttributes(item);
        ProfilePictureUrl = item.GetString("ProfilePictureUrl");
    }
}

Then use your classes when adding the DynamoDB stores:

services
    .AddIdentityCore<CustomUser>()
    .AddRoles<CustomRole>()
    .AddDynamoDbStores<CustomUser, CustomRole>();
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.0.0 127 10/28/2026
2.2.0 297 9/30/2026
2.1.0 107 9/30/2026
2.0.1 733 10/28/2025
2.0.0 697 2/2/2025
1.2.4 1,110 10/4/2024
1.2.3 256 10/4/2024
1.2.1 330 9/26/2024
1.2.0 969 7/7/2024
1.1.3 1,096 3/23/2024
1.1.2 356 1/22/2024
1.1.1 280 1/21/2024
1.1.0 361 11/27/2023
1.0.2 604 10/29/2023
1.0.1 1,292 1/28/2023
1.0.0 556 1/6/2023
0.2.0 633 10/15/2022
0.1.3 597 10/5/2022
0.1.2 537 10/5/2022
Loading failed