Use a Identity 2.0 Database to Authenticate a ASP.NET Core 1.0 application Use a Identity 2.0 Database to Authenticate a ASP.NET Core 1.0 application asp.net asp.net

Use a Identity 2.0 Database to Authenticate a ASP.NET Core 1.0 application


This SQL migration script got me over the above hurdle:

Alter Table ASPNETROLESADD ConcurrencyStamp varchar(255) null,                NormalizedName varchar(255) null Drop Table AspNetUserTokens CREATE TABLE [AspNetUserTokens] (    [UserId]        NVARCHAR (450) NOT NULL,    [LoginProvider] NVARCHAR (450) NOT NULL,    [Name]          NVARCHAR (450) NOT NULL,    [Value]         NVARCHAR (MAX) NULL,    CONSTRAINT [PK_AspNetUserTokens] PRIMARY KEY CLUSTERED ([UserId] ASC, [LoginProvider] ASC, [Name] ASC))Alter Table AspNetUsers Add ConcurrencyStamp varchar(255) null, LockoutEnd DateTime null, NormalizedEmail varchar(255) null, NormalizedUserName varchar(255) nullDrop Table [AspNetRoleClaims]CREATE TABLE [AspNetRoleClaims] (    [Id]         INT            IDENTITY (1, 1) NOT NULL,    [ClaimType]  NVARCHAR (MAX) NULL,    [ClaimValue] NVARCHAR (MAX) NULL,    [RoleId]     NVARCHAR (128) NOT NULL,    CONSTRAINT [PK_AspNetRoleClaims] PRIMARY KEY CLUSTERED ([Id] ASC),    CONSTRAINT [FK_AspNetRoleClaims_AspNetRoles_RoleId] FOREIGN KEY ([RoleId])  REFERENCES [dbo].[AspNetRoles] ([Id]) ON DELETE CASCADE)GOCREATE NONCLUSTERED INDEX [IX_AspNetRoleClaims_RoleId]    ON [AspNetRoleClaims]([RoleId] ASC)Alter Table AspNetUserLogins   Add  ProviderDisplayName varchar(255) null

There's not been a ton of guidance from Microsoft over how to migrate the database but this fixed the above issue for me.


There's a package available to do this. It literally is for this exact purpose. It is part of Microsoft's codebase, and appears to have been updated recently for Core 2.1.

A compatibility layer for sharing identity databases between Microsoft.AspNet.Identity.EntityFramework and Microsoft.AspNetCore.Identity.EntityFrameworkCore.

https://www.nuget.org/packages/Microsoft.AspNet.Identity.AspNetCoreCompat/

https://github.com/aspnet/Identity/tree/master/src/AspNetCoreCompat

It handles the 'differences' between the two schemas with methods like this:

    /// <summary>    ///     Normalized email    /// </summary>    public string NormalizedEmail {        get        {            return Email.ToUpperInvariant();        }        set { }    }    /// <summary>    ///     Concurrency stamp    /// </summary>    public virtual string ConcurrencyStamp { get; set; } = Guid.NewGuid().ToString();

I cannot find any real documentation, but I've done the following and it seems to be working ok:

  • You install the compatibility package in your OLD website (.NET 4.6)
  • You must change ALL references to IdentityRole, IdentityUser, IdentityDbContext etc. to be the classes from the compatibility package.

    using Compat = Microsoft.AspNet.Identity.CoreCompat;// update to use the compatibility classpublic class ApplicationDbContext : Compat.IdentityDbContext<ApplicationUser>// change all instances, such as thisCompat.IdentityUser user = await _repo.FindUser(context.UserName, context.Password);  
  • You have to upgrade your database to the new format (basically adding a few columns and changing some data types). This is the trickiest stage! You're going to want to do this on a staging environment for sure. I'm using Azure so I just cloned the DB.

  • On GitHub I found some migrations scripts by @samnpathdr called Migration.zip. There are several scripts to be run one by one. I'd recommend running each command one at a time to make sure it all runs.
  • Currently there's one table in his script that is custom to his implementation (AspNetUserRolePermissions) so delete references to that.
  • If you have any other tables referencing the AspNetUsers table, you must drop constraints, update the datatypes and add the constraints back. For example I have a Notes table which is linked to a AspNetUser so I needed to run ALTER TABLE UserProfileNote ALTER COLUMN AspNetUsersId nvarchar(450) NOT NULL; (after removing constraints). Script the constraints first!
  • Be careful if you have any 'auto migrations' enabled for Core because personally I wouldn't trust them after this kind of change. You should reset to a baseline, or not do EF migrations at all.

https://github.com/aspnet/Docs/issues/6425


I manually wrote a migration from the old to the new Identity and the application works with both old and new users. Here's the migration if you want to save yourselves some manual work:

public partial class Identity : Migration{    protected override void Up(MigrationBuilder migrationBuilder)    {        migrationBuilder.AddColumn<string>(            name: "NormalizedName",            table: "AspNetRoles",            type: "nvarchar(256)",            maxLength: 256,            nullable: true);        migrationBuilder.AddColumn<string>(            name: "ConcurrencyStamp",            table: "AspNetRoles",            type: "nvarchar(max)",            nullable: true);        migrationBuilder.RenameColumn(            name: "LockoutEndDateUtc",            table: "AspNetUsers",            newName: "LockoutEnd");        migrationBuilder.AddColumn<string>(            name: "ConcurrencyStamp",            table: "AspNetUsers",            type: "nvarchar(max)",            nullable: true);        migrationBuilder.AddColumn<string>(            name: "NormalizedEmail",            table: "AspNetUsers",            type: "nvarchar(256)",            maxLength: 256,            nullable: true);        migrationBuilder.AddColumn<string>(            name: "NormalizedUsername",            table: "AspNetUsers",            type: "nvarchar(256)",            maxLength: 256,            nullable: true);        migrationBuilder.AddColumn<string>(            name: "ProviderDisplayName",            table: "AspNetUserLogins",            type: "nvarchar(max)",            nullable: true);        migrationBuilder.CreateTable(            name: "AspNetRoleClaims",            columns: table => new            {                Id = table.Column<int>(type: "int", nullable: false)                    .Annotation("SqlServer:Identity", "1, 1"),                RoleId = table.Column<string>(type: "nvarchar(128)", nullable: false),                ClaimType = table.Column<string>(type: "nvarchar(max)", nullable: true),                ClaimValue = table.Column<string>(type: "nvarchar(max)", nullable: true)            },            constraints: table =>            {                table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);                table.ForeignKey(                    name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",                    column: x => x.RoleId,                    principalTable: "AspNetRoles",                    principalColumn: "Id",                    onDelete: ReferentialAction.Cascade);            });        migrationBuilder.CreateTable(            name: "AspNetUserTokens",            columns: table => new            {                UserId = table.Column<string>(type: "nvarchar(128)", nullable: false),                LoginProvider = table.Column<string>(type: "nvarchar(450)", nullable: false),                Name = table.Column<string>(type: "nvarchar(450)", nullable: false),                Value = table.Column<string>(type: "nvarchar(max)", nullable: true)            },            constraints: table =>            {                table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });                table.ForeignKey(                    name: "FK_AspNetUserTokens_AspNetUsers_UserId",                    column: x => x.UserId,                    principalTable: "AspNetUsers",                    principalColumn: "Id",                    onDelete: ReferentialAction.Cascade);            });    }    protected override void Down(MigrationBuilder migrationBuilder)    {        migrationBuilder.DropColumn(            name: "ConcurrencyStamp",            table: "AspNetRoles");        migrationBuilder.DropColumn(            name: "NormalizedName",            table: "AspNetRoles");        migrationBuilder.RenameColumn(            name: "LockoutEnd",            table: "AspNetUsers",            newName: "LockoutEndDateUtc");        migrationBuilder.DropColumn(            name: "ConcurrencyStamp",            table: "AspNetUsers");        migrationBuilder.DropColumn(            name: "NormalizedEmail",            table: "AspNetUsers");        migrationBuilder.DropColumn(            name: "NormalizedUsername",            table: "AspNetUsers");        migrationBuilder.DropColumn(            name: "ProviderDisplayName",            table: "AspNetUserLogins");        migrationBuilder.DropTable("AspNetRoleClaims");        migrationBuilder.DropTable("AspNetUserTokens");    }}