Open8

ASP.NET Core Identity 8 を使ってトークン認証をする

soagssoags

昨今では、SPA+Web APIという構成は珍しくもないです。

それにもかかわらず、ASP.NET Core Identity のドキュメントにはこのように書いてあります。

ASP.NET Core Identity では、ASP.NET Core Web アプリにユーザー インターフェイス (UI) ログイン機能が追加されます。 Web API と SPA をセキュリティで保護するには、次のいずれかを使用します。

  • Azure Active Directory
  • Azure Active Directory B2C (Azure AD B2C)
  • Duende Identity Server

Azureは言わずもがな、Duende Identity Serverも当初は無料でしたが今は有料です。
そういうわけで、ASP.NET Core ユーザーの多くは認証処理を再発明するか、適当に探して拾ってくるとかしてたわけです。
当然、この状況についての不満は絶えず、「.NETは認証がクソ」「Laravel等みたいな"Battery Included"な認証基盤が欲しい」などと常々言われてました。

https://github.com/dotnet/aspnetcore/issues/42158

そして、ついにASP.NET Core 8 ではASP.NET Core Identity の改善が行われるようです。

https://devblogs.microsoft.com/dotnet/improvements-auth-identity-aspnetcore-8/

https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-7/

詳しくは上記のページに書いてある通りですが、基本的にはDuende Identity Serverから脱却し、スタンドアロンでSPAなども含めたある程度の認証基盤が使えるようになりそうです。

soagssoags

Identity API endpoints

app.MapIdentityApi<ApplicationUser>();

上記のように記載するだけで

HTTPメソッド エンドポイント
POST /register
POST /login
POST /login?cookieMode=true
POST /refresh
GET /confirmEmail
POST /resendConfirmationEmail
POST /resetPassword
GET /account/2fa
POST /account/2fa
GET /account/info

のエンドポイントが実装されます。
一般的なWebサービスならこれだけでよいかも?

ただ実際にはカスタマイズしたいケースが多いので、そのまま使いはしないでしょうけどね。

soagssoags

David Fowler のIndentity Api サンプルを確認する

https://github.com/davidfowl/IdentityEndpointsSample

実際の設定箇所

// Add services to the container.

builder.Services.AddAuthentication().AddBearerToken(IdentityConstants.BearerScheme);
builder.Services.AddAuthorizationBuilder();

builder.Services.AddIdentityCore<MyUser>()
                .AddEntityFrameworkStores<AppDbContext>()
                .AddApiEndpoints();

// Configure the HTTP request pipeline.

app.MapIdentityApi<MyUser>();
soagssoags

ASP.NET Core 公式 と David Fowler の Indentity Api サンプルの違い

全然違うじゃねぇか

dotnet/aspnetcore davidfowl/IdentityEndpointsSample
AddAuthorization -
AddIdentityApiEndpoints<TUser> -
AddAuthentication -
AddBearerToken(IdentityConstants.BearerScheme) -
AddAuthorizationBuilder -
AddIdentityCore<TUser> -
AddApiEndpoints<TUser> -

同じことやってるのに何でこんなに違うんだと半ギレしながら、中をちょっと見てみたら

  • AddAuthorizationBuilderAddAuthorizationを呼び出している
  • AddIdentityApiEndpoints<TUser>はいろいろ呼び出してる

という感じでした。なので、比較表はこんな感じになります。

dotnet/aspnetcore davidfowl/IdentityEndpointsSample
AddAuthorization
AddIdentityApiEndpoints<TUser> -
AddAuthentication
AddBearerToken(IdentityConstants.BearerScheme)
AddIdentityCore<TUser>
AddApiEndpoints<TUser>

AddIdentityApiEndpoints<TUser>が何やってるか

じゃあ残りのこいつが何をやっているのかというとこんな感じ。
AuthenticationSchemeOptionsとCookie周りが違いになるかな。

 services
     .AddAuthentication(IdentityConstants.BearerAndApplicationScheme)
     .AddScheme<AuthenticationSchemeOptions, CompositeIdentityHandler>(IdentityConstants.BearerAndApplicationScheme, null, compositeOptions =>
     {
         compositeOptions.ForwardDefault = IdentityConstants.BearerScheme;
         compositeOptions.ForwardAuthenticate = IdentityConstants.BearerAndApplicationScheme;
     })
     .AddBearerToken(IdentityConstants.BearerScheme)
     .AddIdentityCookies();

 return services.AddIdentityCore<TUser>(configure)
     .AddApiEndpoints();

たぶん davidfowl/IdentityEndpointsSample の方が推奨されてる

確認してみたところ、ASP.NET Core 公式 の方はPreview 4あたりで実装されていて、davidfowl/IdentityEndpointsSample の方はPreview 7のアナウンスにチュートリアルとして載っています。

まぁ、まだPreviewなのでね。.NET 8 がリリースされるときにはしっかり固まるでしょう。

soagssoags

EntityFramework を使わずに Identity API endpoints を使う

Dapper を代わりに使用する場合とかですね。
基本的にはUserStore をカスタムするだけでOK。

これでいける

 // Add services to the container.
 builder.Services.AddAuthentication().AddBearerToken(IdentityConstants.BearerScheme);
 builder.Services.AddAuthorizationBuilder();

 builder.Services.AddIdentityCore<MyUser>()
+                .AddUserStore<MyUserStore>()
                 .AddApiEndpoints();

 // Configure the HTTP request pipeline.

 app.MapIdentityApi<MyUser>();

Identity API endpointsはRole 使えないっぽい (RoleManagerのDIで死ぬ)

soagssoags

Role を使用する

AddIdentityCore<TUser>AddIdentity<TUser, TRole>()にしてもダメっぽい

ClaimsPrincipal が IsAuthenticated = false になり、認証が通らなくなる。
認証スキーマが変わってしまうためか?

 // Add services to the container.
  
 builder.Services.AddAuthentication().AddBearerToken(IdentityConstants.BearerScheme);
 builder.Services.AddAuthorizationBuilder();

-builder.Services.AddIdentityCore<MyUser>()
+builder.Services.AddIdentity<MyUser, MyRole>()
                 .AddUserStore<MyUserStore>()
+                .AddRoleStore<MyRoleStore>()
                 .AddApiEndpoints();

 // Configure the HTTP request pipeline.

 app.MapIdentityApi<MyUser>();

AddRolesならいけそう

 // Add services to the container.
  
 builder.Services.AddAuthentication().AddBearerToken(IdentityConstants.BearerScheme);
 builder.Services.AddAuthorizationBuilder();

 builder.Services.AddIdentityCore<MyUser>()
                 .AddUserStore<MyUserStore>()
+                .AddRoles<MyRole>()
+                .AddRoleStore<MyRoleStore>()
                 .AddApiEndpoints();

 // Configure the HTTP request pipeline.

 app.MapIdentityApi<MyUser>();