From 070bf04a20f472d7de58a20d0c431a6bbf62c176 Mon Sep 17 00:00:00 2001 From: Alper Ebicoglu Date: Wed, 3 Jan 2018 11:07:57 +0300 Subject: [PATCH 1/4] Added facebook login... --- src/AbpDesk/AbpDesk.Web.Mvc/Startup.cs | 9 ++ src/AbpDesk/Web_PlugIns/AbpDesk.MongoBlog.dll | Bin 15360 -> 15360 bytes .../Pages/Account/ExternalLogin.cshtml | 29 ++++ .../Pages/Account/ExternalLogin.cshtml.cs | 133 ++++++++++++++++++ .../Pages/Account/Login.cshtml | 74 +++++++--- .../Pages/Account/Login.cshtml.cs | 8 +- .../Identity/EfCoreIdentityUserRepository.cs | 5 +- 7 files changed, 236 insertions(+), 22 deletions(-) create mode 100644 src/Volo.Abp.Account.Web/Pages/Account/ExternalLogin.cshtml create mode 100644 src/Volo.Abp.Account.Web/Pages/Account/ExternalLogin.cshtml.cs diff --git a/src/AbpDesk/AbpDesk.Web.Mvc/Startup.cs b/src/AbpDesk/AbpDesk.Web.Mvc/Startup.cs index 781db47760..7e0d9ad95a 100644 --- a/src/AbpDesk/AbpDesk.Web.Mvc/Startup.cs +++ b/src/AbpDesk/AbpDesk.Web.Mvc/Startup.cs @@ -37,6 +37,15 @@ namespace AbpDesk.Web.Mvc //); }); + + services.AddAuthentication().AddFacebook(facebookOptions => + { + facebookOptions.AppId = "911417875702990"; + facebookOptions.AppSecret = "adea0bff222ae340d8fb0ce3e6275d6b"; + }); + + + //TODO: This is needed because ASP.NET Core does not use IServiceProviderFactory! return services.BuildServiceProviderFromFactory(); } diff --git a/src/AbpDesk/Web_PlugIns/AbpDesk.MongoBlog.dll b/src/AbpDesk/Web_PlugIns/AbpDesk.MongoBlog.dll index e8b4663419530366f7417b0973d815e710d5722c..9dae5160a2294a65da6e60c34129019fefb30883 100644 GIT binary patch delta 44 zcmV+{0Mq|~cz}42hy=Eviu|#RvJwFSvp*A&6A(C8HL7VHt4N*vm3hQ^G1Rl*CLk5L CN)u!N delta 46 zcmV+}0MY+|cz}42hy;%7FOIQ{vJ#Wp5(=|O6NnQK(@`iD+3*%gr$Vbf|0emEv*0Em E73Pl=g#Z8m diff --git a/src/Volo.Abp.Account.Web/Pages/Account/ExternalLogin.cshtml b/src/Volo.Abp.Account.Web/Pages/Account/ExternalLogin.cshtml new file mode 100644 index 0000000000..5817404f38 --- /dev/null +++ b/src/Volo.Abp.Account.Web/Pages/Account/ExternalLogin.cshtml @@ -0,0 +1,29 @@ +@page +@model Volo.Abp.Account.Web.Pages.Account.ExternalLoginModel +@{ + ViewData["Title"] = "Register"; +} + +

@ViewData["Title"]

+

Associate your @Model.LoginProvider account.

+
+ +

+ You've successfully authenticated with @Model.LoginProvider. + Please enter an email address for this site below and click the Register button to finish + logging in. +

+ +
+
+
+
+
+ + + +
+ +
+
+
diff --git a/src/Volo.Abp.Account.Web/Pages/Account/ExternalLogin.cshtml.cs b/src/Volo.Abp.Account.Web/Pages/Account/ExternalLogin.cshtml.cs new file mode 100644 index 0000000000..7e08a7a805 --- /dev/null +++ b/src/Volo.Abp.Account.Web/Pages/Account/ExternalLogin.cshtml.cs @@ -0,0 +1,133 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Identity; +using Volo.Abp.Uow; + +namespace Volo.Abp.Account.Web.Pages.Account +{ + public class ExternalLoginModel : AccountModelBase + { + private readonly SignInManager _signInManager; + private readonly UserManager _userManager; + + public ExternalLoginModel( + SignInManager signInManager, + UserManager userManager) + { + _signInManager = signInManager; + _userManager = userManager; + } + + [BindProperty] + public InputModel Input { get; set; } + + public string LoginProvider { get; set; } + + public string ReturnUrl { get; set; } + + [TempData] + public string ErrorMessage { get; set; } + + public class InputModel + { + [Required] + [EmailAddress] + public string Email { get; set; } + } + + public IActionResult OnGetAsync() + { + return RedirectToPage("./Login"); + } + + [UnitOfWork] + public virtual IActionResult OnPost(string provider, string returnUrl = null) + { + // Request a redirect to the external login provider. + var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl }); + var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); + return new ChallengeResult(provider, properties); + } + + [UnitOfWork] + public virtual async Task OnGetCallbackAsync(string returnUrl = null, string remoteError = null, string returnUrlHash = "") + { + if (remoteError != null) + { + ErrorMessage = $"Error from external provider: {remoteError}"; + return RedirectToPage("./Login"); + } + var info = await _signInManager.GetExternalLoginInfoAsync(); + if (info == null) + { + return RedirectToPage("./Login"); + } + + // Sign in the user with this external login provider if the user already has a login. + var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true); + if (result.Succeeded) + { + return RedirectSafely(returnUrl, returnUrlHash); + } + + if (result.IsLockedOut) + { + return RedirectToPage("./Lockout"); + } + + // If the user does not have an account, then ask the user to create an account. + ReturnUrl = returnUrl; + LoginProvider = info.LoginProvider; + if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email)) + { + Input = new InputModel + { + Email = info.Principal.FindFirstValue(ClaimTypes.Email) + }; + } + return Page(); + } + + [UnitOfWork] + public virtual async Task OnPostConfirmationAsync(string returnUrl = null, string returnUrlHash = null) + { + if (ModelState.IsValid) + { + // Get the information about the user from the external login provider + var info = await _signInManager.GetExternalLoginInfoAsync(); + if (info == null) + { + throw new ApplicationException("Error loading external login information during confirmation."); + } + + var user = new IdentityUser(GuidGenerator.Create(), Input.Email); + + var result = await _userManager.CreateAsync(user); + + //CheckIdentityErrors( await _userManager.CreateAsync(user)); + + if (result.Succeeded) + { + result = await _userManager.AddLoginAsync(user, info); + if (result.Succeeded) + { + await _signInManager.SignInAsync(user, isPersistent: false); + + return RedirectSafely(returnUrl, returnUrlHash); + } + } + foreach (var error in result.Errors) + { + ModelState.AddModelError(string.Empty, error.Description); + } + } + + ReturnUrl = returnUrl; + return Page(); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml b/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml index 24e84bfdab..62b8c70dfd 100644 --- a/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml +++ b/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml @@ -1,27 +1,61 @@ @page @model Volo.Abp.Account.Web.Pages.Account.LoginModel
-
-
-
- - +
+
+ +
+ + +
+
+ + +
+
+ +
+ + + + -
- - -
-
- -
- - - -
+ +
+
+

Use another service to log in.

+
+ @{ + if ((Model.ExternalLogins?.Count ?? 0) == 0) + { +
+

+ There are no external authentication services configured. See this article + for details on setting up this ASP.NET application to support logging in via external services. +

+
+ } + else + { +
+
+

+ @foreach (var provider in Model.ExternalLogins) + { + + } +

+
+
+ } + } +
+
+
\ No newline at end of file diff --git a/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs b/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs index 7a8fc7b14d..39a3e40bb7 100644 --- a/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs +++ b/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Volo.Abp.Identity; @@ -15,6 +18,8 @@ namespace Volo.Abp.Account.Web.Pages.Account public string ReturnUrlHash { get; set; } //TODO: Try to automatically bind from querystring! + public IList ExternalLogins { get; set; } + private readonly SignInManager _signInManager; public LoginModel(SignInManager signInManager) @@ -22,10 +27,11 @@ namespace Volo.Abp.Account.Web.Pages.Account _signInManager = signInManager; } - public void OnGet(string returnUrl = "", string returnUrlHash = "") + public async Task OnGetAsync(string returnUrl = "", string returnUrlHash = "") { ReturnUrl = returnUrl; ReturnUrlHash = returnUrl; + ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); } //TODO: Bind input to a property instead of getting as parameter..? diff --git a/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EfCoreIdentityUserRepository.cs b/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EfCoreIdentityUserRepository.cs index 70b301e125..8deac6bef1 100644 --- a/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EfCoreIdentityUserRepository.cs +++ b/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EfCoreIdentityUserRepository.cs @@ -37,7 +37,10 @@ namespace Volo.Abp.Identity public async Task FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken) { //TODO: This should be changed since loginProvider, providerKey are not PKs. - var userLogin = await DbContext.UserLogins.FindAsync(new object[] { loginProvider, providerKey }, cancellationToken); + var userLogin = await DbContext.UserLogins + .Where(login => login.LoginProvider == loginProvider && login.ProviderKey == providerKey) + .FirstOrDefaultAsync(cancellationToken); + if (userLogin == null) { return null; From bd082f2366ac4ec9c435726e82bf3340612abb20 Mon Sep 17 00:00:00 2001 From: Alper Ebicoglu Date: Wed, 3 Jan 2018 11:32:57 +0300 Subject: [PATCH 2/4] Facebook credentials added to appsettings. Adding Facebook authentication moved to abpDeskWebMvcModule class. --- src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs | 14 ++++++++++++++ src/AbpDesk/AbpDesk.Web.Mvc/Startup.cs | 11 ----------- src/AbpDesk/AbpDesk.Web.Mvc/appsettings.json | 7 +++++++ 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs b/src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs index e6f0c74ec2..eea4fead9b 100644 --- a/src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs +++ b/src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs @@ -66,6 +66,20 @@ namespace AbpDesk.Web.Mvc //services.Configure(configuration); //Needed when we use Volo.Abp.Identity.HttpApi.Client + //Adding Facebook authentication + if (bool.Parse(configuration["Authentication:Facebook:IsEnabled"])) + { + services.AddAuthentication().AddFacebook(options => + { + options.AppId = configuration["Authentication:Facebook:AppId"]; + options.AppSecret = configuration["Authentication:Facebook:AppSecret"]; + + options.Scope.Add("email"); + options.Scope.Add("public_profile"); + }); + } + + services.AddAssemblyOf(); services.Configure(options => diff --git a/src/AbpDesk/AbpDesk.Web.Mvc/Startup.cs b/src/AbpDesk/AbpDesk.Web.Mvc/Startup.cs index 7e0d9ad95a..a166afafb9 100644 --- a/src/AbpDesk/AbpDesk.Web.Mvc/Startup.cs +++ b/src/AbpDesk/AbpDesk.Web.Mvc/Startup.cs @@ -1,12 +1,10 @@ using System; -using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Serilog; using Volo.Abp; -using Volo.Abp.Modularity.PlugIns; namespace AbpDesk.Web.Mvc { @@ -37,15 +35,6 @@ namespace AbpDesk.Web.Mvc //); }); - - services.AddAuthentication().AddFacebook(facebookOptions => - { - facebookOptions.AppId = "911417875702990"; - facebookOptions.AppSecret = "adea0bff222ae340d8fb0ce3e6275d6b"; - }); - - - //TODO: This is needed because ASP.NET Core does not use IServiceProviderFactory! return services.BuildServiceProviderFromFactory(); } diff --git a/src/AbpDesk/AbpDesk.Web.Mvc/appsettings.json b/src/AbpDesk/AbpDesk.Web.Mvc/appsettings.json index 927ad8e6c7..fe44ab1467 100644 --- a/src/AbpDesk/AbpDesk.Web.Mvc/appsettings.json +++ b/src/AbpDesk/AbpDesk.Web.Mvc/appsettings.json @@ -7,5 +7,12 @@ "AbpIdentity": { "BaseUrl": "http://localhost:63290/" } + }, + "Authentication": { + "Facebook": { + "IsEnabled": "true", + "AppId": "911417875702990", + "AppSecret": "adea0bff222ae340d8fb0ce3e6275d6b" + } } } From ea1a08bca8c6b2ee79b1c3c5264dfe556868d9b1 Mon Sep 17 00:00:00 2001 From: Alper Ebicoglu Date: Wed, 3 Jan 2018 13:04:47 +0300 Subject: [PATCH 3/4] Removed register page during Facebook authentication --- .../Pages/Account/ExternalLogin.cshtml.cs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Volo.Abp.Account.Web/Pages/Account/ExternalLogin.cshtml.cs b/src/Volo.Abp.Account.Web/Pages/Account/ExternalLogin.cshtml.cs index 7e08a7a805..ccbe25a167 100644 --- a/src/Volo.Abp.Account.Web/Pages/Account/ExternalLogin.cshtml.cs +++ b/src/Volo.Abp.Account.Web/Pages/Account/ExternalLogin.cshtml.cs @@ -82,18 +82,18 @@ namespace Volo.Abp.Account.Web.Pages.Account // If the user does not have an account, then ask the user to create an account. ReturnUrl = returnUrl; LoginProvider = info.LoginProvider; - if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email)) + var success = await CreateUserAsync(returnUrl, returnUrlHash); + + if (success) { - Input = new InputModel - { - Email = info.Principal.FindFirstValue(ClaimTypes.Email) - }; + return RedirectSafely(returnUrl, returnUrlHash); } + return Page(); } [UnitOfWork] - public virtual async Task OnPostConfirmationAsync(string returnUrl = null, string returnUrlHash = null) + public virtual async Task CreateUserAsync(string returnUrl = null, string returnUrlHash = null) { if (ModelState.IsValid) { @@ -104,7 +104,7 @@ namespace Volo.Abp.Account.Web.Pages.Account throw new ApplicationException("Error loading external login information during confirmation."); } - var user = new IdentityUser(GuidGenerator.Create(), Input.Email); + var user = new IdentityUser(GuidGenerator.Create(), info.Principal.FindFirstValue(ClaimTypes.Email)); var result = await _userManager.CreateAsync(user); @@ -117,17 +117,18 @@ namespace Volo.Abp.Account.Web.Pages.Account { await _signInManager.SignInAsync(user, isPersistent: false); - return RedirectSafely(returnUrl, returnUrlHash); + return true; } } + foreach (var error in result.Errors) { ModelState.AddModelError(string.Empty, error.Description); } } - ReturnUrl = returnUrl; - return Page(); + return false; + } } } \ No newline at end of file From 66077639a6dc36ee80ba85fb6020ace2057b75cc Mon Sep 17 00:00:00 2001 From: Alper Ebicoglu Date: Wed, 3 Jan 2018 13:07:43 +0300 Subject: [PATCH 4/4] Minor refactoring... --- .../Pages/Account/ExternalLogin.cshtml.cs | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/src/Volo.Abp.Account.Web/Pages/Account/ExternalLogin.cshtml.cs b/src/Volo.Abp.Account.Web/Pages/Account/ExternalLogin.cshtml.cs index ccbe25a167..6ea57b97a7 100644 --- a/src/Volo.Abp.Account.Web/Pages/Account/ExternalLogin.cshtml.cs +++ b/src/Volo.Abp.Account.Web/Pages/Account/ExternalLogin.cshtml.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Volo.Abp.Identity; +using Volo.Abp.Ui; using Volo.Abp.Uow; namespace Volo.Abp.Account.Web.Pages.Account @@ -61,6 +62,7 @@ namespace Volo.Abp.Account.Web.Pages.Account ErrorMessage = $"Error from external provider: {remoteError}"; return RedirectToPage("./Login"); } + var info = await _signInManager.GetExternalLoginInfoAsync(); if (info == null) { @@ -76,12 +78,13 @@ namespace Volo.Abp.Account.Web.Pages.Account if (result.IsLockedOut) { - return RedirectToPage("./Lockout"); + throw new UserFriendlyException("Cannot proceed because user is locked out!"); } - // If the user does not have an account, then ask the user to create an account. ReturnUrl = returnUrl; LoginProvider = info.LoginProvider; + + //User does not have an account, create an account. var success = await CreateUserAsync(returnUrl, returnUrlHash); if (success) @@ -95,40 +98,41 @@ namespace Volo.Abp.Account.Web.Pages.Account [UnitOfWork] public virtual async Task CreateUserAsync(string returnUrl = null, string returnUrlHash = null) { - if (ModelState.IsValid) + if (!ModelState.IsValid) { - // Get the information about the user from the external login provider - var info = await _signInManager.GetExternalLoginInfoAsync(); - if (info == null) - { - throw new ApplicationException("Error loading external login information during confirmation."); - } + return false; + } + + // Get the information about the user from the external login provider + var info = await _signInManager.GetExternalLoginInfoAsync(); + if (info == null) + { + throw new ApplicationException("Error loading external login information during confirmation."); + } - var user = new IdentityUser(GuidGenerator.Create(), info.Principal.FindFirstValue(ClaimTypes.Email)); + var user = new IdentityUser(GuidGenerator.Create(), info.Principal.FindFirstValue(ClaimTypes.Email)); - var result = await _userManager.CreateAsync(user); + var result = await _userManager.CreateAsync(user); - //CheckIdentityErrors( await _userManager.CreateAsync(user)); + //todo: needs to check identity errors? + //CheckIdentityErrors( await _userManager.CreateAsync(user)); + if (result.Succeeded) + { + result = await _userManager.AddLoginAsync(user, info); if (result.Succeeded) { - result = await _userManager.AddLoginAsync(user, info); - if (result.Succeeded) - { - await _signInManager.SignInAsync(user, isPersistent: false); - - return true; - } + await _signInManager.SignInAsync(user, false); + return true; } + } - foreach (var error in result.Errors) - { - ModelState.AddModelError(string.Empty, error.Description); - } + foreach (var error in result.Errors) + { + ModelState.AddModelError(string.Empty, error.Description); } return false; - } } } \ No newline at end of file