こんにちは。
テックリードのTerukiです。
前回もASP.NET Core Identityの記事でしたが今回もです。
ASP.NET CoreではGoogleアカウントなどのサードパーティのIdPでのログイン認証は公式のパッケージで実現できます。
これは非常に便利ですが、ASP.NET Core Identityのユーザーと併用する時の使い方が調べてもほとんど出てこなかったので備忘的に書こうと思います。
前提
外部ログインをASP.NET Coreで実装する時に、ASP.NET Core Identityを使うのか使わないのかで話がだいぶ変わってきます。
使わない場合はかなりシンプルでかつネットの情報も多いのですが、使う時にはひと手間必要です。
この記事では後者について触れていこうと思います。
外部ログインのプロバイダーはいろいろありますが、この記事ではGoogleログインをベースに記載します。
基本的には他の外部ログインのプロバイダーでも流用できると思います。
方針
ASP.NET Core Identityでは、ログインユーザーを作成した後はIDが発番されてユーザーテーブルに1行追加されます。
一方、Googleなどの外部ログインが完了すると、ログインしたGoogleアカウント固有のIDを受け取ることができます。
ASP.NET Core IdentityとGoogleログインを併用する場合、上記の2つのIDを紐づける仕組みがなければそれぞれ別々のIDとして認識されてしまうのでこれをこの記事で解決できるようにします。
実装
まずはGoogleログインできるようにします。
services.AddAuthentication()
.AddGoogle(options => {
options.ClientId = Configuration["Authentication:Google:ClientId"]!; // よしなに設定
options.ClientSecret = Configuration["Authentication:Google:ClientSecret"]!; // よしなに設定
options.SignInScheme = IdentityConstants.ExternalScheme;
});
クライアントIDやシークレットは下記を参考に作成します。
SignInSchemeをExternalSchemeにすることが重要です。
Googleログインを開始するには下記のようなアクションをコントローラに作っておきます。
[HttpGet]
public IActionResult Google() {
var properties = SignInManager.ConfigureExternalAuthenticationProperties(GoogleDefaults.AuthenticationScheme, "/api/account/callback");
return new ChallengeResult(GoogleDefaults.AuthenticationScheme, properties);
}
このアクションにアクセスするとGoogleアカウントを選択する画面に飛びます。
ここまではASP.NET Core Identityを使わないパターンとほぼ同じですが、このままだとASP.NET Core IdentityとGoogleアカウントが紐づいていないのでうまくいきません。
その処理をCallback側でやります。
[Authorize(AuthenticationSchemes = "Identity.External")] // IdentityConstants.ExternalSchemeはconstではなくstatic readonlyなのでここでは使えない😭
[HttpGet]
public async Task<IActionResult> Callback() {
// ログイン情報を取得する
var info = await SignInManager.GetExternalLoginInfoAsync().ConfigureAwait(false);
if (info is null) {
return BadRequest();
}
// 外部IDでのログインを試みる
var result = await SignInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, true).ConfigureAwait(false);
if (!result.Succeeded) {
// 外部IDでログインできなかった場合、メールアドレスで実際のユーザーを探して紐づける
// 見つからなかった場合はBadRequest
var user = await UserManager.FindByNameAsync(info.Principal.FindFirstValue(ClaimTypes.Email)!).ConfigureAwait(false);
if (user is null) {
return BadRequest();
}
await UserManager.AddLoginAsync(user, info).ConfigureAwait(false);
await SignInManager.SignInAsync(user, true).ConfigureAwait(false);
}
return Redirect("/");
}
SignInManager.GetExternalLoginInfoAsync
で外部ログインで取得したID等を取得できます。
このメソッドはSignInSchemeをExternalSchemeにした時のみ使えます。
その後にSignInManager.ExternalLoginSignInAsync
を使うことでASP.NET Core Identityのユーザーでログインできます。
が、それは後続のAddLoginAsync
を実行していた場合の話なので初回の場合は失敗します。
この例では失敗した時にユーザー名でASP.NET Core Identity側のユーザーを特定して紐づける処理を自動で実行していますが、ここで行うかはアプリの設計によりそうです。
一度AddLoginAsync
をすれば後はExternalLoginSignInAsync
でログインできるので良い感じです。
このあたりを触っていてSignInManager辺りのドキュメントがなかなか足りていない感じがあります。
今後もこのくらいの粒度で記事を書けたらなと思います。
Oh my teethについて
Oh my teethでは未来の歯科体験を創るために日々活動しています。
Techチームではより良いユーザー体験を提供するべく、Webフロントエンドからバックエンド、スマホアプリに機械学習モデルなど、さまざまなプロダクトを開発しています。
一緒に未来の歯科体験を創りませんか?興味がある方は是非こちらを確認してください。
カジュアル面談も可能なので気軽に応募してみてください!
