shuhelohelo’s blog

Xamarin.Forms多めです.

ASP.NET CoreでOAuthを試す(3)

前回は外部認証のページを表示,利用するところまでやりました.

shuhelohelo.hatenablog.com

しかし認証の結果を用いて自分のアプリケーション側でどうするかという処理(コールバック)の部分を書いていないので,認証後にエラー画面が表示されていました.

今回は外部認証後のコールバックの部分についてです.

コールバックメソッドの名前は今回はExernalLoginCallbackです.

これは外部認証を利用する際に予め指定していたものです.

        [AllowAnonymous]
        [HttpPost]
        [Route("[action]")]
        public IActionResult ExternalLogin(LoginViewModel viewModel, string provider, string returnUrl)
        {
            //Google側の認証が終わったらどのURLに遷移させるかを指定している.コールバックの指定
            var redirectUrl = Url.Action("ExternalLoginCallbac", "Account",
                new { ReturnUrl = returnUrl });

            var properties = signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);

            return new ChallengeResult(provider, properties);//Googleの認証ページが開く
        }

さて,コールバック全体は以下のとおりです.

       /// <summary>
        /// Googleの認証が終わったらこちら.
        /// </summary>
        /// <param name="returnUrl"></param>
        /// <param name="remoteError"></param>
        /// <returns></returns>
        [AllowAnonymous]
        [Route("[action]")]
        public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
        {
            //returnUrlがnullだったらルートUrlを返す
            returnUrl = returnUrl ?? Url.Content("~/");

            LoginViewModel loginViewModel = new LoginViewModel
            {
                ReturnUrl = returnUrl,
                ExternalLogins = (await signInManager.GetExternalAuthenticationSchemesAsync()).ToList()
            };

            //外部認証プロバイダからエラーが帰ってきている場合は,
            //ログインページに戻す
            if (remoteError != null)
            {
                ModelState.AddModelError(string.Empty, $"Error from external provider:{remoteError}");

                return View("Login", loginViewModel);
            }

            //signInManagerがOAuthの認証結果を持っているので,それを取得する
            var info = await signInManager.GetExternalLoginInfoAsync();
            //空だったらログインページ行きだね
            if (info == null)
            {
                ModelState.AddModelError(string.Empty, "Error loading external login information.");

                return View("Login", loginViewModel);
            }

            //これはこのアプリケーション側のAspNetUserLoginsテーブルにログインユーザーとして登録するためか?
            var signInResult = await signInManager
                .ExternalLoginSignInAsync(info.LoginProvider,
                    info.ProviderKey, isPersistent: false, bypassTwoFactor: true);

            //わかった.
            // AspNetUserLoginsテーブルは外部認証によるログイン情報が格納されるんだ.
            //初回は記録がないからfalse

            if (signInResult.Succeeded)
            {
                //以前にその外部認証でログインしたことがある.
                //問題なし
                return LocalRedirect(returnUrl);
            }
            else
            {
                //signInResultがfalseってどういうとき?
                //初めて外部認証でログインしたユーザーの場合だ.こっちは.

                //メールアドレスを取得する.
                var email = info.Principal.FindFirstValue(ClaimTypes.Email);

                // このアプリにアカウントを持っているかどうかをEmailで検索する.
                //内部のユーザー情報を検索する
                if (email != null)
                {
                    var user = await userManager.FindByEmailAsync(email);

                    //user==nullとは,そんなユーザーはいない...の場合
                    //つまり,このアプリを初めて使うユーザーで,
                    //外部認証を使った人.

                    if (user == null)
                    {
                        user = new IdentityUser
                        {
                            UserName = info.Principal.FindFirstValue(ClaimTypes.Email),
                            Email = info.Principal.FindFirstValue(ClaimTypes.Email)
                        };

                        //このアプリ用のユーザー作成
                        await userManager.CreateAsync(user);
                    }

                    //AspNetUserLoginsテーブルに追加.
                    await userManager.AddLoginAsync(user, info);
                    //このアプリにおけるサインイン状態にする
                    await signInManager.SignInAsync(user, isPersistent: false);

                    return LocalRedirect(returnUrl);
                }
            }

            return View("Login", loginViewModel);
        }

流れとしては以下のとおりです.

  1. Google側の認証でエラーだったり,結果が空だったりしたらログイン画面へ戻す.
  2. 外部認証ユーザーとして登録されているかをチェックする.(AspNetUserLoginsテーブル,ExternalLoginSignInAsyncメソッドを使って).登録されていたらログイン扱いされる.アプリ利用可.終了.
  3. 登録されていない場合は,アプリのユーザーとして登録されているかチェック
  4. 登録されていない場合は新規作成
  5. AspNetUserLoginsに登録
  6. アプリでサインイン状態にする

と,このようになります.

ここで重要なことは外部の認証を利用していますが,これによって外部サービスにログインしているわけではありません.

そして,外部サービスにアプリ側のユーザー情報が渡されるわけでもありません.

外部認証は外部サービスが認証(IDとパスワードの一致)をチェックしてその結果を返してくれるだけです.

ライブラリも,外部認証を呼び出す手続きを簡略かし,認証結果を受け取るインターフェースを提供しているだけで,それ以上のことはしていません.

なので,その結果を受け取って,アプリ側でログイン処理を記述する必要があります.

今回のソースコード

github.com