#4927: Implement social/external logins for the account module.

pull/4935/head
Halil İbrahim Kalkan 5 years ago
parent d73d9fa14c
commit 5f78f89a53

@ -43,6 +43,7 @@
"Description:Abp.Account.EnableLocalLogin": "Indicates if the server will allow users to authenticate with a local account.",
"LoggedOutTitle": "Signed Out",
"LoggedOutText": "You have been signed out and you will be redirected soon.",
"ReturnToText": "Click here to redirect to {0}"
"ReturnToText": "Click here to redirect to {0}",
"OrLoginWith": "Or login with;"
}
}

@ -6,34 +6,34 @@
@model Volo.Abp.Account.Web.Pages.Account.LoginModel
@inject IHtmlLocalizer<AccountResource> L
@inject Volo.Abp.Settings.ISettingProvider SettingProvider
@if (Model.EnableLocalLogin)
{
<div class="card mt-3 shadow-sm rounded">
<div class="card-body p-5">
<h4>@L["Login"]</h4>
@if (await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled))
{
<strong>
@L["AreYouANewUser"]
<a href="@Url.Page("./Register", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})" class="text-decoration-none">@L["Register"]</a>
</strong>
}
<div class="card mt-3 shadow-sm rounded">
<div class="card-body p-5">
<h4>@L["Login"]</h4>
@if (await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled))
{
<strong>
@L["AreYouANewUser"]
<a href="@Url.Page("./Register", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})" class="text-decoration-none">@L["Register"]</a>
</strong>
}
@if (Model.EnableLocalLogin)
{
<form method="post" class="mt-4">
<input asp-for="ReturnUrl" />
<input asp-for="ReturnUrlHash" />
<input asp-for="ReturnUrl"/>
<input asp-for="ReturnUrlHash"/>
<div class="form-group">
<label asp-for="LoginInput.UserNameOrEmailAddress"></label>
<input asp-for="LoginInput.UserNameOrEmailAddress" class="form-control" />
<input asp-for="LoginInput.UserNameOrEmailAddress" class="form-control"/>
<span asp-validation-for="LoginInput.UserNameOrEmailAddress" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LoginInput.Password"></label>
<input asp-for="LoginInput.Password" class="form-control" />
<input asp-for="LoginInput.Password" class="form-control"/>
<span asp-validation-for="LoginInput.Password" class="text-danger"></span>
</div>
<div class="form-check">
<label asp-for="LoginInput.RememberMe" class="form-check-label">
<input asp-for="LoginInput.RememberMe" class="form-check-input" />
<input asp-for="LoginInput.RememberMe" class="form-check-input"/>
@Html.DisplayNameFor(m => m.LoginInput.RememberMe)
</label>
</div>
@ -43,29 +43,30 @@
<abp-button type="submit" button-type="Secondary" formnovalidate="formnovalidate" name="Action" value="Cancel" class="btn-block btn-lg mt-3">@L["Cancel"]</abp-button>
}
</form>
</div>
</div>
}
}
@if (Model.VisibleExternalProviders.Any())
{
<div class="col-md-6">
<h4>@L["UseAnotherServiceToLogIn"]</h4>
<form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post">
<input asp-for="ReturnUrl" />
<input asp-for="ReturnUrlHash" />
@foreach (var provider in Model.VisibleExternalProviders)
{
<button type="submit" class="btn btn-primary" name="provider" value="@provider.AuthenticationScheme" title="@L["GivenTenantIsNotAvailable", provider.DisplayName]">@provider.DisplayName</button>
}
</form>
</div>
}
@if (Model.VisibleExternalProviders.Any())
{
<div class="mt-2">
<h5>@L["OrLoginWith"]</h5>
<form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post">
<input asp-for="ReturnUrl"/>
<input asp-for="ReturnUrlHash"/>
@foreach (var provider in Model.VisibleExternalProviders)
{
<button type="submit" class="btn btn-primary m-1" name="provider" value="@provider.AuthenticationScheme" title="@L["GivenTenantIsNotAvailable", provider.DisplayName]">@provider.DisplayName</button>
}
</form>
</div>
}
@if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any())
{
<div class="alert alert-warning">
<strong>@L["InvalidLoginRequest"]</strong>
@L["ThereAreNoLoginSchemesConfiguredForThisClient"]
</div>
}
@if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any())
{
<div class="alert alert-warning">
<strong>@L["InvalidLoginRequest"]</strong>
@L["ThereAreNoLoginSchemesConfiguredForThisClient"]
</div>
}
</div>

@ -214,13 +214,23 @@ namespace Volo.Abp.Account.Web.Pages.Account
//TODO: Handle other cases for result!
// Get the information about the user from the external login provider
var info = await SignInManager.GetExternalLoginInfoAsync();
if (info == null)
var externalLoginInfo = await SignInManager.GetExternalLoginInfoAsync();
if (externalLoginInfo == null)
{
throw new ApplicationException("Error loading external login information during confirmation.");
}
var user = await CreateExternalUserAsync(info);
if (!IsEmailRetrievedFromExternalLogin(externalLoginInfo))
{
return RedirectToPage("./Register", new
{
IsExternalLogin = true,
ExternalLoginAuthSchema = externalLoginInfo.LoginProvider,
ReturnUrl = returnUrl
});
}
var user = await CreateExternalUserAsync(externalLoginInfo);
await SignInManager.SignInAsync(user, false);
@ -234,6 +244,11 @@ namespace Volo.Abp.Account.Web.Pages.Account
return RedirectSafely(returnUrl, returnUrlHash);
}
private static bool IsEmailRetrievedFromExternalLogin(ExternalLoginInfo externalLoginInfo)
{
return externalLoginInfo.Principal.FindFirstValue(AbpClaimTypes.Email) != null;
}
protected virtual async Task<IdentityUser> CreateExternalUserAsync(ExternalLoginInfo info)
{
var emailAddress = info.Principal.FindFirstValue(AbpClaimTypes.Email);

@ -4,18 +4,27 @@
@model Volo.Abp.Account.Web.Pages.Account.RegisterModel
@inject IHtmlLocalizer<AccountResource> L
<div class="card mt-3 shadow-sm rounded">
<div class="card-body p-5">
<h4>@L["Register"]</h4>
<strong>
@L["AlreadyRegistered"]
<a href="@Url.Page("./Login", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})" class="text-decoration-none">@L["Login"]</a>
</strong>
<form method="post" class="mt-4">
<abp-input asp-for="Input.UserName" auto-focus="true" />
<abp-input asp-for="Input.EmailAddress" />
<abp-input asp-for="Input.Password" />
<abp-button button-type="Primary" type="submit" class="btn-lg btn-block mt-4">@L["Register"]</abp-button>
</form>
</div>
<div class="card mt-3 shadow-sm rounded">
<div class="card-body p-5">
<h4>@L["Register"]</h4>
<strong>
@L["AlreadyRegistered"]
<a href="@Url.Page("./Login", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})" class="text-decoration-none">@L["Login"]</a>
</strong>
<form method="post" class="mt-4">
@if (!Model.IsExternalLogin)
{
<abp-input asp-for="Input.UserName" auto-focus="true"/>
}
<abp-input asp-for="Input.EmailAddress"/>
@if (!Model.IsExternalLogin)
{
<abp-input asp-for="Input.Password"/>
}
<abp-button button-type="Primary" type="submit" class="btn-lg btn-block mt-4">@L["Register"]</abp-button>
</form>
</div>
</div>

@ -1,14 +1,16 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Volo.Abp.Account.Settings;
using Volo.Abp.Auditing;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Identity;
using Volo.Abp.Settings;
using Volo.Abp.Uow;
using Volo.Abp.Validation;
using IdentityUser = Volo.Abp.Identity.IdentityUser;
@ -27,6 +29,12 @@ namespace Volo.Abp.Account.Web.Pages.Account
[BindProperty]
public PostInput Input { get; set; }
[BindProperty(SupportsGet = true)]
public bool IsExternalLogin { get; set; }
[BindProperty(SupportsGet = true)]
public string ExternalLoginAuthSchema { get; set; }
public RegisterModel(IAccountAppService accountAppService)
{
AccountAppService = accountAppService;
@ -35,34 +43,141 @@ namespace Volo.Abp.Account.Web.Pages.Account
public virtual async Task<IActionResult> OnGetAsync()
{
await CheckSelfRegistrationAsync();
await TrySetEmailAsync();
return Page();
}
public virtual async Task<IActionResult> OnPostAsync()
private async Task TrySetEmailAsync()
{
ValidateModel();
if (IsExternalLogin)
{
var externalLoginInfo = await SignInManager.GetExternalLoginInfoAsync();
if (externalLoginInfo == null)
{
return;
}
if (!externalLoginInfo.Principal.Identities.Any())
{
return;
}
var identity = externalLoginInfo.Principal.Identities.First();
var emailClaim = identity.FindFirst(ClaimTypes.Email);
if (emailClaim == null)
{
return;
}
Input = new PostInput {EmailAddress = emailClaim.Value};
}
}
public virtual async Task<IActionResult> OnPostAsync()
{
await CheckSelfRegistrationAsync();
var registerDto = new RegisterDto
var registerDto = new RegisterDto()
{
AppName = "MVC",
EmailAddress = Input.EmailAddress,
Password = Input.Password,
UserName = Input.UserName
AppName = "MVC"
};
var userDto = await AccountAppService.RegisterAsync(registerDto);
var user = await UserManager.GetByIdAsync(userDto.Id);
if (IsExternalLogin)
{
var externalLoginInfo = await SignInManager.GetExternalLoginInfoAsync();
if (externalLoginInfo == null)
{
Logger.LogWarning("External login info is not available");
return RedirectToPage("./Login");
}
registerDto.EmailAddress = Input.EmailAddress;
registerDto.UserName = Input.EmailAddress;
registerDto.Password = GeneratePassword();
}
else
{
ValidateModel();
await UserManager.SetEmailAsync(user, Input.EmailAddress);
registerDto.EmailAddress = Input.EmailAddress;
registerDto.Password = Input.Password;
registerDto.UserName = Input.UserName;
}
var userDto = await AccountAppService.RegisterAsync(registerDto);
var user = await UserManager.GetByIdAsync(userDto.Id);
await SignInManager.SignInAsync(user, isPersistent: false);
if (IsExternalLogin)
{
await AddToUserLogins(user);
}
return Redirect(ReturnUrl ?? "~/"); //TODO: How to ensure safety? IdentityServer requires it however it should be checked somehow!
}
protected virtual async Task AddToUserLogins(IdentityUser user)
{
var externalLoginInfo = await SignInManager.GetExternalLoginInfoAsync();
var userLoginAlreadyExists = user.Logins.Any(x =>
x.TenantId == user.TenantId &&
x.LoginProvider == externalLoginInfo.LoginProvider &&
x.ProviderKey == externalLoginInfo.ProviderKey);
if (!userLoginAlreadyExists)
{
user.AddLogin(new UserLoginInfo(
externalLoginInfo.LoginProvider,
externalLoginInfo.ProviderKey,
externalLoginInfo.ProviderDisplayName
)
);
}
}
protected virtual string GeneratePassword()
{
var random = new Random();
var options = UserManager.Options.Password;
int length = random.Next(options.RequiredLength, IdentityUserConsts.MaxPasswordLength - 1);
bool nonAlphanumeric = options.RequireNonAlphanumeric;
bool digit = options.RequireDigit;
bool lowercase = options.RequireLowercase;
bool uppercase = options.RequireUppercase;
StringBuilder password = new StringBuilder();
while (password.Length < length)
{
char c = (char)random.Next(32, 126);
password.Append(c);
if (char.IsDigit(c))
digit = false;
else if (char.IsLower(c))
lowercase = false;
else if (char.IsUpper(c))
uppercase = false;
else if (!char.IsLetterOrDigit(c))
nonAlphanumeric = false;
}
if (nonAlphanumeric)
password.Append((char)random.Next(33, 48));
if (digit)
password.Append((char)random.Next(48, 58));
if (lowercase)
password.Append((char)random.Next(97, 123));
if (uppercase)
password.Append((char)random.Next(65, 91));
return password.ToString();
}
protected virtual async Task CheckSelfRegistrationAsync()
{
if (!await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled) ||

Loading…
Cancel
Save