Skip to content

Commit

Permalink
Merge pull request #4 from alexs0ff/4-add-windows-login
Browse files Browse the repository at this point in the history
Added windows login
  • Loading branch information
alexs0ff authored Jul 25, 2019
2 parents 6795d09 + 2820f59 commit 0d27365
Show file tree
Hide file tree
Showing 24 changed files with 661 additions and 4 deletions.
22 changes: 22 additions & 0 deletions src/Areas/Identity/IdentityHostingStartup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using IdentityServer4WebApp.Data;
using IdentityServer4WebApp.Models;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

[assembly: HostingStartup(typeof(IdentityServer4WebApp.Areas.Identity.IdentityHostingStartup))]
namespace IdentityServer4WebApp.Areas.Identity
{
public class IdentityHostingStartup : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.ConfigureServices((context, services) => {
});
}
}
}
33 changes: 33 additions & 0 deletions src/Areas/Identity/Pages/Account/ExternalLogin.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@page
@model ExternalLoginModel
@{
ViewData["Title"] = "Register";
}

<h1>@ViewData["Title"]</h1>
<h4>Associate your @Model.LoginProvider account.</h4>
<hr />

<p class="text-info">
You've successfully authenticated with <strong>@Model.LoginProvider</strong>.
Please enter an email address for this site below and click the Register button to finish
logging in.
</p>

<div class="row">
<div class="col-md-4">
<form asp-page-handler="Confirmation" asp-route-returnUrl="@Model.ReturnUrl" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
</div>
</div>

@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
208 changes: 208 additions & 0 deletions src/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks;
using IdentityModel;
using Microsoft.AspNetCore.Authorization;
using IdentityServer4WebApp.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Server.IISIntegration;
using Microsoft.Extensions.Logging;

namespace IdentityServer4WebApp.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class ExternalLoginModel : PageModel
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
private readonly ILogger<ExternalLoginModel> _logger;

public ExternalLoginModel(
SignInManager<ApplicationUser> signInManager,
UserManager<ApplicationUser> userManager,
ILogger<ExternalLoginModel> logger)
{
_signInManager = signInManager;
_userManager = userManager;
_logger = logger;
}

[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");
}

public async Task<IActionResult> OnPost(string provider, string returnUrl = null)
{
if (IISDefaults.AuthenticationScheme == provider)
{
return await ProcessWindowsLoginAsync(returnUrl);
}

var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return new ChallengeResult(provider, properties);
}

public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (remoteError != null)
{
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToPage("./Login", new {ReturnUrl = returnUrl });
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}

// 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)
{
var user = await _userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey);

await UpdateClaims(info, user, "hasUsersGroup");

_logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider);
return LocalRedirect(returnUrl);
}
if (result.IsLockedOut)
{
return RedirectToPage("./Lockout");
}
else
{
// 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();
}
}

public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
// Get the information about the user from the external login provider
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information during confirmation.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}

if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email };
var result = await _userManager.CreateAsync(user);
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
await UpdateClaims(info, user, "hasUsersGroup");
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}

LoginProvider = info.LoginProvider;
ReturnUrl = returnUrl;
return Page();
}

private async Task<IActionResult> ProcessWindowsLoginAsync(string returnUrl)
{
var result = await HttpContext.AuthenticateAsync(IISDefaults.AuthenticationScheme);
if (result?.Principal is WindowsPrincipal wp)
{
var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl });

var props = _signInManager.ConfigureExternalAuthenticationProperties(IISDefaults.AuthenticationScheme, redirectUrl);
props.Items["scheme"] = IISDefaults.AuthenticationScheme;

var id = new ClaimsIdentity(IISDefaults.AuthenticationScheme);
id.AddClaim(new Claim(JwtClaimTypes.Subject, wp.Identity.Name));
id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name));
id.AddClaim(new Claim(ClaimTypes.NameIdentifier, wp.Identity.Name));

var wi = wp.Identity as WindowsIdentity;
var groups = wi.Groups.Translate(typeof(NTAccount));
var hasUsersGroup = groups.Any(i => i.Value.Contains(@"BUILTIN\Users", StringComparison.OrdinalIgnoreCase));

id.AddClaim(new Claim("hasUsersGroup", hasUsersGroup.ToString()));

await HttpContext.SignInAsync(IdentityConstants.ExternalScheme, new ClaimsPrincipal(id), props);

return Redirect(props.RedirectUri);
}

return Challenge(IISDefaults.AuthenticationScheme);
}

private async Task UpdateClaims(ExternalLoginInfo info, ApplicationUser user, params string[] claimTypes)
{
if (claimTypes == null)
{
return;
}

var claimTypesHash = new HashSet<string>(claimTypes);

var claims = (await _userManager.GetClaimsAsync(user)).Where(c => claimTypesHash.Contains(c.Type)).ToList();

await _userManager.RemoveClaimsAsync(user, claims);

foreach (var claimType in claimTypes)
{
if (info.Principal.HasClaim(c => c.Type == claimType))
{
claims = info.Principal.FindAll(claimType).ToList();
await _userManager.AddClaimsAsync(user, claims);
}
}
}


}
}
82 changes: 82 additions & 0 deletions src/Areas/Identity/Pages/Account/Login.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
@page
@model LoginModel

@{
ViewData["Title"] = "Log in";
}

<h1>@ViewData["Title"]</h1>
<div class="row">
<div class="col-md-4">
<section>
<form id="account" method="post">
<h4>Use a local account to log in.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label asp-for="Input.RememberMe">
<input asp-for="Input.RememberMe" />
@Html.DisplayNameFor(m => m.Input.RememberMe)
</label>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Log in</button>
</div>
<div class="form-group">
<p>
<a id="forgot-password" asp-page="./ForgotPassword">Forgot your password?</a>
</p>
<p>
<a asp-page="./Register" asp-route-returnUrl="@Model.ReturnUrl">Register as a new user</a>
</p>
</div>
</form>
</section>
</div>
<div class="col-md-6 col-md-offset-2">
<section>
<h4>Use another service to log in.</h4>
<hr />
@{
if ((Model.ExternalLogins?.Count ?? 0) == 0)
{
<div>
<p>
There are no external authentication services configured. See <a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a>
for details on setting up this ASP.NET application to support logging in via external services.
</p>
</div>
}
else
{
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
<div>
<p>
@foreach (var provider in Model.ExternalLogins)
{
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @(provider.DisplayName ??provider.Name) account">@(provider.DisplayName ?? provider.Name)</button>
}
</p>
</div>
</form>
}
}
</section>
</div>
</div>

@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
Loading

0 comments on commit 0d27365

Please sign in to comment.