Saturday, March 28, 2020

IdentityServer4 Custom Claims and Services

It is all started from my intention to add "iat" claim to access token. IssuedAt (iat) claim is optional so it takes a bit of searching to figure out how to do that. Add that to my unfamiliarity with IdentityServer4, it becomes quite a task. At this point, I am using IdentityServer4 version 3.0.2.0.

Problem

First, I found out that you might be able to add custom claim by extending IProfileService. It works well for some random claim, but not "iat". Strange, it must be filtered somewhere then.

Then browsing the source code in github, I found out that it was indeed filtered by FilterProtocolClaims method in DefaultClaimService:

https://github.com/IdentityServer/IdentityServer4/blob/master/src/IdentityServer4/src/Services/Default/DefaultClaimsService.cs

Ok, so I think I can extend DefaultClaimsService. I tried by adding a custom class in the StartUp using the following code:

services.AddTransient<IClaimsService,CustomClaimsService>();

Sadly, it didn't work. 

Solution

I then learn that you can add the service under builder.Services, so I tried the following:

services.AddIdentityServer()
      .... (removed for brevity)
      .Services.AddTransient<IClaimsService, CustomClaimsService>();

That works! My "iat" claim is included in the access token. In my case, I choose to overwrite GetStandardSubjectClaims method because that is where "auth_time" claim is set and "iat" claim has the same value as "auth_time" claim using code like the following:

var authTime = claims.FirstOrDefault(c => c.Type == JwtClaimTypes.AuthenticationTime);
if (authTime != null)
{
   outputClaims.Add(new Claim(JwtClaimTypes.IssuedAt, authTime.Value, ClaimValueTypes.Integer));
}

Wednesday, March 18, 2020

EF6 MySql Migrations Error "Specified key was too long; max key length is 767 bytes"

We were in the middle of moving to MySQL and would like to use EF Migrations against MySQL. But the error held us back a little.

Problem

When trying to apply EF Migrations, it failed with "Specified key was too long; max key length is 767 bytes" error.

Solution

The following article helped solve my problem.

https://docs.microsoft.com/en-us/aspnet/identity/overview/getting-started/aspnet-identity-using-mysql-storage-with-an-entityframework-mysql-provider#adding-custom-migrationhistory-context

Under "Adding custom MigrationHistory context" section, it talks about creating a custom HistoryContext class. In my case:

Public Class MySqlHistoryContext
        Inherits HistoryContext

    Sub New(existingConnection As DbConnection, defaultSchema As String)
        MyBase.New(existingConnection, defaultSchema)
    End Sub

    Protected Overrides Sub OnModelCreating(modelBuilder As DbModelBuilder)
        MyBase.OnModelCreating(modelBuilder)
        modelBuilder.Entity(Of HistoryRow)().Property(Function(h) h.MigrationId).HasMaxLength(100).IsRequired()
        modelBuilder.Entity(Of HistoryRow)().Property(Function(h) h.ContextKey).HasMaxLength(200).IsRequired()
    End Sub
End Class

Then use it on the generated Configuration file. My problem is fixed afterwards.

Friend NotInheritable Class Configuration
        Inherits DbMigrationsConfiguration(Of MyDbContext)

    Public Sub New()
        AutomaticMigrationsEnabled = False

        SetHistoryContextFactory(MySqlProviderInvariantName.ProviderName, Function(connection, schema) New MySqlHistoryContext(connection, schema))
    End Sub

    Protected Overrides Sub Seed(context As MyDbContext)
    End Sub

End Class

EF6 MySQL Remove Default dbo Schema

As we all know, Entity Framework has default schema of "dbo", but MySQL will ignore it when performing migrations. But it gets troublesome when we try to rollback, because the dbo doesn't get ignored in that case. 

Problem

Due to the fact that dbo doesn't get ignored, rolling back the migrations often caused error in my case.
And removing the schema manually every time a new migration file is generated is tiring. That prompted me to find a way to remove the schema when new migration file is generated. 

Solution

My first attempt is by looking inside the Configuration file. In my case, I have a custom HistoryContext because some MigrationHistory keys are just too long. And the custom HistoryContext is set in the Configuration file constructor:
SetHistoryContextFactory(MySqlProviderInvariantName.ProviderName, Function(connection, schema) New MySqlHistoryContext(connection, schema))

After some online search, it seems like for some databases, setting the schema to something else alters the generated file, so I give it a try. My code thus becomes:
SetHistoryContextFactory(MySqlProviderInvariantName.ProviderName, Function(connection, schema) New MySqlHistoryContext(connection, ""))

However, it doesn't work at all. Then I think there maybe a way to set it on the DbContext level and I found the following code which I apply to the OnModelCreating method:
modelBuilder.HasDefaultSchema(String.Empty)

And this time it works! No more dbo. But on the newly generated migration file, it has MoveTable() code for existing tables. In my case, I just comment them out since schema was ignored anyway.

Finally, out of curiosity, I just tried changing the schema to null instead of empty string. However, that one doesn't work in my case as it tries to set the schema back to dbo for my database.

So the final solution is to set the default schema to empty string.

Monday, March 16, 2020

EF6 Strange Pending Changes Error

One of the complicated errors on EF6 that I often troubleshoot is the following:

Unable to update database to match the current model because there are pending changes and automatic migration is disabled. Either write the pending model changes to a code-based migration or enable automatic migration. Set DbMigrationsConfiguration.AutomaticMigrationsEnabled to true to enable automatic migration.

Problem

Usually, I will just run Add-Migration on Package Manager Console to see what changes in the schema. But this time it is strange, the Add-Migration includes the code to create existing tables. I searched for the migration files between the time those tables were created and the latest migration, I can't find anywhere that those tables were dropped.

When trying to find some clue online, I bumped into the following amazing article: https://tech.trailmax.info/2014/03/inside_of_ef_migrations/.

So, I went into the database and decompress a few blob/binary files from Model column. The files contain schema snapshots in the form of XML. And in my case, somewhere along the migrations, the tables were suddenly missing from the snapshots. However, the actual tables were still in the database.

I also found out that my coworker at one time commented those tables out. But it is weird because if they were commented out and Add-Migration was triggered, the generated file would have DropTable code. When I asked my coworker, the migration file was not changed manually, so the DropTable code was not generated in the first place. But the latest snapshot doesn't have those tables in it.

So, no DropTable, no manual migration file modification and no tables in the snapshot. It took me a few hours to understand how to recreate the issue.

With everything as is, I run Add-Migration once. EF then generates a new migration file and shows the following message in the console:

The Designer Code for this migration file includes a snapshot of your current Code First model. This snapshot is used to calculate the changes to your model when you scaffold the next migration. If you make additional changes to your model that you want to include in this migration, then you can re-scaffold it by running 'Add-Migration TestMigration' again.

Then I commented out the table that I want to go missing and rerun the Add-Migration. Usually, when you need to rescaffold a migration, you use the -Force option, in this case I did not. I simply re-ran the same Add-Migration command. This time EF shows the following message in the console:

Only the Designer Code for migration 'TestMigration' was re-scaffolded. To re-scaffold the entire migration, use the -Force parameter.

Nothing changed on the generated file, but notice that the Designer Code which has the model snapshot has been changed. That means the table that I commented has been removed from the snapshot, but no DropTable code. And when I run Update-Database, EF inserted the model snapshot along with the latest migration file name into the database.

Since no table was dropped, when I uncommented the table and ran the application, EF throws the pending changes error. Eventually to summarize the steps:

  1. Run Add-Migration
  2. Comment a table
  3. Run the same Add-Migration (without -Force option)
  4. Update-Database
  5. Uncomment the table

Solution

To fix it, I simply reverted the migrations and re-did them properly. 

Also, not sure if the following code plays a part in the issue, but we have them in our code:

Database.SetInitializer<DbContext>(null);



Wednesday, March 11, 2020

A2 Hosting Let's Encrypt Can't Install Certificate on ASP.NET Core Application

I have an ASP.NET Core app hosted by A2 Hosting. A2 Hosting provides free SSL certificate through Let's Encrypt, so I decided to install it for my application.

Problem

Since the portal is by Plesk, I follow the guide in:


However, it didn't work as expected.

Solution

My application still has a web.config file. After trying different ways, I commented out aspNetCore module in the web.config and then the certificate was installed successfully. By commenting the module, ASP.NET Core processing by IIS is disabled, so the validation request by Let's Encrypt is treated like a regular request towards a static file.