Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
wip 9e3f3b2 Apr 3, 2019
1 contributor

Users who have contributed to this file

248 lines (212 sloc) 10.2 KB
using AuthServer.Infrastructure.Data.Identity;
using Microsoft.AspNetCore.Mvc;
using AuthServer.Models;
using AuthServer.Infrastructure.Constants;
using System.Threading.Tasks;
using IdentityServer4.Services;
using System.Linq;
using System;
using IdentityServer4.Stores;
using IdentityServer4.Models;
using IdentityServer4.Events;
using AuthServer.Extensions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
namespace AuthServer.Controllers
{
public class AccountController : Controller
{
private readonly UserManager<AppUser> _userManager;
private readonly IIdentityServerInteractionService _interaction;
private readonly IAuthenticationSchemeProvider _schemeProvider;
private readonly IClientStore _clientStore;
private readonly IEventService _events;
public AccountController(UserManager<AppUser> userManager, IIdentityServerInteractionService interaction, IAuthenticationSchemeProvider schemeProvider, IClientStore clientStore, IEventService events)
{
_userManager = userManager;
_interaction = interaction;
_schemeProvider = schemeProvider;
_clientStore = clientStore;
_events = events;
}
/// <summary>
/// Entry point into the login workflow
/// </summary>
[HttpGet]
public async Task<IActionResult> Login(string returnUrl)
{
// build a model so we know what to show on the login page
var vm = await BuildLoginViewModelAsync(returnUrl);
if (vm.IsExternalLoginOnly)
{
// we only have one option for logging in and it's an external provider
return RedirectToAction("Challenge", "External", new { provider = vm.ExternalLoginScheme, returnUrl });
}
return View(vm);
}
/// <summary>
/// Handle postback from username/password login
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginInputModel model, string button)
{
// check if we are in the context of an authorization request
var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
// the user clicked the "cancel" button
if (button != "login")
{
if (context != null)
{
// if the user cancels, send a result back into IdentityServer as if they
// denied the consent (even if this client does not require consent).
// this will send back an access denied OIDC error response to the client.
await _interaction.GrantConsentAsync(context, ConsentResponse.Denied);
// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
if (await _clientStore.IsPkceClientAsync(context.ClientId))
{
// if the client is PKCE then we assume it's native, so this change in how to
// return the response is for better UX for the end user.
return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl });
}
return Redirect(model.ReturnUrl);
}
else
{
// since we don't have a valid context, then we just go back to the home page
return Redirect("~/");
}
}
if (ModelState.IsValid)
{
// validate username/password
var user = await _userManager.FindByNameAsync(model.Username);
if (user != null && await _userManager.CheckPasswordAsync(user, model.Password))
{
await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id, user.Name));
// only set explicit expiration here if user chooses "remember me".
// otherwise we rely upon expiration configured in cookie middleware.
AuthenticationProperties props = null;
if (AccountOptions.AllowRememberLogin && model.RememberLogin)
{
props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
};
};
// issue authentication cookie with subject ID and username
await HttpContext.SignInAsync(user.Id, user.UserName, props);
if (context != null)
{
if (await _clientStore.IsPkceClientAsync(context.ClientId))
{
// if the client is PKCE then we assume it's native, so this change in how to
// return the response is for better UX for the end user.
return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl });
}
// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
return Redirect(model.ReturnUrl);
}
// request for a local page
if (Url.IsLocalUrl(model.ReturnUrl))
{
return Redirect(model.ReturnUrl);
}
else if (string.IsNullOrEmpty(model.ReturnUrl))
{
return Redirect("~/");
}
else
{
// user might have clicked on a malicious link - should be logged
throw new Exception("invalid return URL");
}
}
await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials"));
ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage);
}
// something went wrong, show form with error
var vm = await BuildLoginViewModelAsync(model);
return View(vm);
}
[HttpPost]
[Route("api/[controller]")]
public async Task<IActionResult> Register([FromBody]RegisterRequestViewModel model)
{
//var aVal = 0; var blowUp = 1 / aVal;
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var user = new AppUser { UserName = model.Email, Name = model.Name, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (!result.Succeeded) return BadRequest(result.Errors);
await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("userName", user.UserName));
await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("name", user.Name));
await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("email", user.Email));
await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("role", Roles.Consumer));
return Ok(new RegisterResponseViewModel(user));
}
/*****************************************/
/* helper APIs for the AccountController */
/*****************************************/
private async Task<LoginViewModel> BuildLoginViewModelAsync(string returnUrl)
{
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
if (context?.IdP != null)
{
var local = context.IdP == IdentityServer4.IdentityServerConstants.LocalIdentityProvider;
// this is meant to short circuit the UI and only trigger the one external IdP
var vm = new LoginViewModel
{
EnableLocalLogin = local,
ReturnUrl = returnUrl,
Username = context?.LoginHint,
};
if (!local)
{
vm.ExternalProviders = new[] { new ExternalProvider { AuthenticationScheme = context.IdP } };
}
return vm;
}
var schemes = await _schemeProvider.GetAllSchemesAsync();
var providers = schemes.Where(x => x.DisplayName != null || (x.Name.Equals(AccountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase))
)
.Select(x => new ExternalProvider
{
DisplayName = x.DisplayName,
AuthenticationScheme = x.Name
}).ToList();
var allowLocal = true;
if (context?.ClientId != null)
{
var client = await _clientStore.FindEnabledClientByIdAsync(context.ClientId);
if (client != null)
{
allowLocal = client.EnableLocalLogin;
if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any())
{
providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList();
}
}
}
return new LoginViewModel
{
AllowRememberLogin = AccountOptions.AllowRememberLogin,
EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin,
ReturnUrl = returnUrl,
Username = context?.LoginHint,
ExternalProviders = providers.ToArray()
};
}
private async Task<LoginViewModel> BuildLoginViewModelAsync(LoginInputModel model)
{
var vm = await BuildLoginViewModelAsync(model.ReturnUrl);
vm.Username = model.Username;
vm.RememberLogin = model.RememberLogin;
return vm;
}
}
}
You can’t perform that action at this time.