ASP.NET Core Identity 8 を使ってトークン認証をする
昨今では、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"な認証基盤が欲しい」などと常々言われてました。
そして、ついにASP.NET Core 8 ではASP.NET Core Identity の改善が行われるようです。
詳しくは上記のページに書いてある通りですが、基本的にはDuende Identity Serverから脱却し、スタンドアロンでSPAなども含めたある程度の認証基盤が使えるようになりそうです。
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サービスならこれだけでよいかも?
ただ実際にはカスタマイズしたいケースが多いので、そのまま使いはしないでしょうけどね。
ASP.NET Core 公式のIndentity Api サンプルを確認する
実際の設定箇所
// Add services to the container.
builder.Services.AddAuthorization();
builder.Services.AddIdentityApiEndpoints<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
// Configure the HTTP request pipeline.
app.MapGroup("/identity").MapIdentityApi<IdentityUser>();
David Fowler のIndentity Api サンプルを確認する
実際の設定箇所
// 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>();
AddAuthorization()
と AddAuthentication()
の違い
ASP.NET Core 公式 と David Fowler の Indentity Api サンプルの違い
全然違うじゃねぇか
dotnet/aspnetcore | davidfowl/IdentityEndpointsSample | |
---|---|---|
AddAuthorization |
〇 | - |
AddIdentityApiEndpoints<TUser> |
〇 | - |
AddAuthentication |
- | 〇 |
AddBearerToken(IdentityConstants.BearerScheme) |
- | 〇 |
AddAuthorizationBuilder |
- | 〇 |
AddIdentityCore<TUser> |
- | 〇 |
AddApiEndpoints<TUser> |
- | 〇 |
同じことやってるのに何でこんなに違うんだと半ギレしながら、中をちょっと見てみたら
-
AddAuthorizationBuilder
はAddAuthorization
を呼び出している -
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 がリリースされるときにはしっかり固まるでしょう。
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で死ぬ)
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>();