ASP.NET Daj Się Poznać Programowanie

LOGOWANIE DO APLIKACJI ZA POMOCĄ KONTA GOOGLE+

Wielu czytelników pytało mnie kiedy będzie kontynuacja serii dotyczącej procesu autoryzacji użytkowników w ASP.NET. I oto nadszedł ten dzień. W dniu dzisiejszym zajmiemy się logowaniem przy użyciu konta Google+. Przed przystąpieniem do tego poradnika polecam przeczytać mój poprzedni artykuł z tej dziedziny, który znajdziesz tutaj. Gotowy? No to zaczynamy!

KONFIGUROWANIE PROTOKOŁU SSL W PROJEKCIE

  1. Kliknij w nazwę projektu w Solution Explorer.
  2. W zakładce Properties znajdź opcję SSL Enabled i ustaw ją na True.
  3. Skopiuj SSL URL do schowka.
  4. W Solution Explorer kliknij prawym przyciskiem myszy na nazwę projektu i wybierz Properties
  5. Wybierz zakładkę Web i w polu Project URL wklej wcześniej skopiowany adres.
  6. Dodaj atrybut [RequireHttps] w HomeControler przed akcją Index aby wymusić korzystanie z protokołu HTTPS. (W celu lepszej czytelności kodu polecam otwierać kod w nowym oknie).

[RequireHttps]

public class HomeController : Controller

{

   public ActionResult Index()

   {

      return View();

   }

}

Twój projekt od teraz korzysta z certyfikatu SSL. Oczywiście, do czasu gdy zakupisz odpowiedni certyfikat może pojawiać się ostrzeżenie, iż certyfikat pochodzi z nieznanego źródła. Nie należy się tym przejmować, ponieważ certyfikat spełnia swoją rolę i pozwoli nam na korzystanie z autoryzacji użytkownika za pomocą konta Google.

STWORZENIE APLIKACJI GOOGLE DLA OAUTH2

  1. Przejdź do strony https://console.developers.google.com
  2. Klikamy w przycisk „Dodaj projekt” i podajemy nazwę projektu.
  3. Przechodzimy do zakładki „Dane logowania” a następnie „Ekran Zgody OAuth” i uzupełniamy dane według poniższego wzoru.
  4. Następnie przechodzimy do zakładki „Dane logowania” i naciskamy przycisk „Utwórz dane logowania”. Z rozwijalnej listy wybieramy ID Klienta OAuth. W następnym oknie wybieramy „Aplikacja Internetowa” i uzupełniamy pola według wzoru.  Zostanie wygenerowany unikalny kod, który będziemy musieli umieścić w aplikacji.
  5. Pozostał ostatni krok a mianowicie włączenie Google+ API. Przechodzimy do zakładki „Biblioteka” i wyszukujemy „Google + API”. Po kliknięciu w link należy włączyć obsługę tego API poprzez naciśnięcie przycisku włącz u góry strony.  Następnie przechodzimy do
    „Panel API” i naciskamy „Włącz API” widoczne u góry strony.

DODAWANIE AUTORYZACJI DO PROJEKTU

  1. Za pomocą NuGet Package Menager należy zainstalować Microsoft.Owin.Security.Google
  2. W Solution Explorer przechodzimy do folderu App_Start i otwieramy plik Startup.cs. Kod tej klasy znajduje się na listingu poniżej
     public partial class Startup
     {
     public void ConfigureAuth(IAppBuilder app)
     {
     app.CreatePerOwinContext(eTrenerContext.Create);
     app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
     app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
     app.UseCookieAuthentication(new CookieAuthenticationOptions
     {
     AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
     LoginPath = new PathString("/Account/Login"),
     Provider = new CookieAuthenticationProvider
     {
     OnValidateIdentity =
     SecurityStampValidator
     .OnValidateIdentity<ApplicationUserManager, IdentityModels.ApplicationUser>(
     validateInterval: TimeSpan.FromMinutes(30),
     regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
     }
     });
     app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
    
     app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
    
     app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
    
     app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
     {
     ClientId = "tutaj wpisz uzyskane ID",
     ClientSecret = "tutaj wpisz hasło"
     });
     }
     }

  1. Konieczne jest utworzenie 3 widoków. Widoku częściowego dostępnych metod logowania, potwierdzenia loginu po udanym zalogowaniu oraz informacji o nieudanym logowaniu. Przykładowe kody tych widoków prezentują się następująco:

WIDOK CZĘŚCIOWY:

Model:

namespace eTrener.Models
{
   public class ExternalLoginListViewModel
   {
      public string ReturnUrl { get; set; }
   }

}

Widok:

@model eTrener.Models.ExternalLoginListViewModel
@using Microsoft.Owin.Security

@{
    var loginProviders = Context.GetOwinContext().Authentication.GetExternalAuthenticationTypes();
   
        using (Html.BeginForm("ExternalLogin", "Account", new { ReturnUrl = Model.ReturnUrl }))
        {
            @Html.AntiForgeryToken()
            <div id="socialLoginList">
                <p>
                    @foreach (AuthenticationDescription p in loginProviders)
                    {
                        <button type="submit" class="btn btn-default" id="@p.AuthenticationType" name="provider" value="@p.AuthenticationType">
                                <i class="fa fa-google-plus"></i> Sign in using Google+
                        </button>
                    }
                </p>
            </div>
        }
    }
    }

POTWIERDZENIE REJESTRACJI

Model:

using System.ComponentModel.DataAnnotations;
namespace eTrener.Models
{
   public class ExternalLoginConfirmationViewModel
   {
      [Required]
      [Display(Name = "Email")]
      public string Email { get; set; }
   }
}

Widok:

@using System.Web.Optimization
@model eTrener.Models.ExternalLoginConfirmationViewModel
@{
    ViewBag.Title = "Register";
}
<h2>@ViewBag.Title.</h2>
<h3>Associate your @ViewBag.LoginProvider account.</h3>

@using (Html.BeginForm("ExternalLoginConfirmation", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
    @Html.AntiForgeryToken()

    <h4>Association Form</h4>
    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
    <p class="text-info">
        You've successfully authenticated with <strong>@ViewBag.LoginProvider</strong>.
        Please enter a user name for this site below and click the Register button to finish
        logging in.
    </p>
    <div class="form-group">
        @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
            @Html.ValidationMessageFor(m => m.Email, "", new { @class = "text-danger" })
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" class="btn btn-default" value="Register" />
        </div>
    </div>
}

BŁĄD LOGOWANIA

Widok:

@{
    ViewBag.Title = "Login Failure";
}

<hgroup>
    <h2>@ViewBag.Title.</h2>
    <h3 class="text-danger">Unsuccessful login with service.</h3>
</hgroup>

 

  1. Do wykonania pozostaje najgorsza czynność czyli zaprogramowanie kontrolera. Na poniższych listingach podam tylko fragmenty kodu, które należy dodać do programu. Resztę kodu znajdziecie w moim poprzednim wpisie lub na kocie GitHub.
public ActionResult ExternalLoginConfirmation()
       {
           return View();
       }

       [HttpPost]
       [AllowAnonymous]
       [ValidateAntiForgeryToken]
       public async Task&amp;amp;lt;ActionResult&amp;amp;gt; ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model,
           string returnUrl)
       {
           if (User.Identity.IsAuthenticated)
           {
               return RedirectToAction("Index", "Home");
           }

           if (ModelState.IsValid)
           {
               // Get the information about the user from the external login provider
               var info = await AuthenticationManager.GetExternalLoginInfoAsync();
               if (info == null)
               {
                   return View("ExternalLoginFailure");
               }
               var user = new IdentityModels.ApplicationUser
               {
                   UserName = model.Email,
                   Email = model.Email,
                   UserData = new UserData()
               };
               var result = await UserManager.CreateAsync(user);
               if (result.Succeeded)
               {
                   result = await UserManager.AddLoginAsync(user.Id, info.Login);
                   if (result.Succeeded)
                   {
                       await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
                       return RedirectToLocal(returnUrl);
                   }
               }
               AddErrors(result);
           }

           ViewBag.ReturnUrl = returnUrl;
           return View(model);
       }

       [HttpPost]
       [AllowAnonymous]
       [ValidateAntiForgeryToken]
       public ActionResult ExternalLogin(string provider, string returnUrl)
       {
           // Request a redirect to the external login provider
           return new ChallengeResult(provider,
               Url.Action("ExternalLoginCallback", "Account", new {ReturnUrl = returnUrl}));
       }

       internal class ChallengeResult : HttpUnauthorizedResult
       {
           public ChallengeResult(string provider, string redirectUri)
               : this(provider, redirectUri, null)
           {
           }

           public ChallengeResult(string provider, string redirectUri, string userId)
           {
               LoginProvider = provider;
               RedirectUri = redirectUri;
               UserId = userId;
           }

           public string LoginProvider { get; set; }
           public string RedirectUri { get; set; }
           public string UserId { get; set; }
           private const string XsrfKey = "XsrfId";

           public override void ExecuteResult(ControllerContext context)
           {
               var properties = new AuthenticationProperties {RedirectUri = RedirectUri};
               if (UserId != null)
               {
                   properties.Dictionary[XsrfKey] = UserId;
               }
               context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
           }
       }

       [AllowAnonymous]
       public ActionResult ExternalLoginFailure()
       {
           return View();
       }

       [AllowAnonymous]
       public async Task&amp;amp;lt;ActionResult&amp;amp;gt; ExternalLoginCallback(string returnUrl)
       {
           var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
           if (loginInfo == null)
           {
               return RedirectToAction("Login");
           }

           // Sign in the user with this external login provider if the user already has a login
           var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
           switch (result)
           {
               case SignInStatus.Success:
                   return RedirectToLocal(returnUrl);
               default:
                   // If the user does not have an account, then prompt the user to create an account
                   ViewBag.ReturnUrl = returnUrl;
                   ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
                   return View("ExternalLoginConfirmation",
                       new ExternalLoginConfirmationViewModel {Email = loginInfo.Email});
           }
       }

PODSUMOWANIE

Gratuluję! Udało Ci się dojść do końca tego niełatwego procesu i jeżeli wszystko poszło zgodnie z planem Twoja aplikacja właśnie uzyskała nową funkcjonalność. Kod który zamieściłem w tym artykule pochodzi z mojej aplikacji więc jeżeli pojawią się jakieś błędy upewnij się czy dostosowałeś parametry do swoich potrzeb. Jeżeli ten artykuł okazał się dla Ciebie pomocny będzie mi bardzo miło jeżeli zostawisz komentarz oraz udostępnisz ten post dalej, aby jak najwięcej ludzi mogło z niego skorzystać J. Jeżeli masz jakieś uwagi co do artykułu, to również zachęcam do pozostawienia komentarza i zapewniam, że wszystkie uwagi biorę sobie do serca i staram się, aby z biegiem czasu moje wpisy były coraz lepsze.
Do następnego!

2 Comments

Dodaj komentarz