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 aNotes
table which is linked to aAspNetUser
so I needed to runALTER 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.
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"); }}