Skip to content

Commit

Permalink
Merge pull request #97 from damienbod/dev-improve-fido2
Browse files Browse the repository at this point in the history
FIDO2 Anti-forgery tokens, .NET Core 3.1.8
  • Loading branch information
damienbod committed Sep 12, 2020
2 parents e3a29cb + 531b35c commit 02eb4ec
Show file tree
Hide file tree
Showing 28 changed files with 106 additions and 74 deletions.
5 changes: 5 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

[Readme](https://github.com/damienbod/IdentityServer4AspNetCoreIdentityTemplate/blob/master/README.md)

2020-09-12 5.0.4
- FIDO2 Anti-forgery tokens
- updated nuget packages 3.1.7
- update npm packages

2020-08-28 5.0.3
- updated nuget packages 3.1.7

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ dotnet new -i IdentityServer4AspNetCoreIdentityTemplate
Locally built nupkg:

```
dotnet new -i IdentityServer4AspNetCoreIdentityTemplate.5.0.3.nupkg
dotnet new -i IdentityServer4AspNetCoreIdentityTemplate.5.0.4.nupkg
```

Local folder:
Expand Down
4 changes: 2 additions & 2 deletions content/IdentityServer4AspNetCoreIdentityTemplate.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<id>IdentityServer4AspNetCoreIdentityTemplate</id>
<version>5.0.3</version>
<version>5.0.4</version>
<title>IdentityServer4.Identity.Template</title>
<license type="file">LICENSE</license>
<description>
Expand All @@ -17,7 +17,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<copyright>2020 damienbod</copyright>
<summary>This template provides a simle getting started for IdentityServer4 with Identity. Identity is Localized and the UI uses Bootstrap 4, Remove AllowAnonymous from the logout</summary>
<releaseNotes>updated nuget packages 3.1.7</releaseNotes>
<releaseNotes>FIDO2 Anti-forgery tokens, updated nuget packages 3.1.7, update npm packages</releaseNotes>
<repository type="git" url="https://github.com/damienbod/IdentityServer4AspNetCoreIdentityTemplate" />
<packageTypes>
<packageType name="Template" />
Expand Down
17 changes: 9 additions & 8 deletions content/StsServerIdentity/Controllers/AccountController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public async Task<IActionResult> Login(LoginInputModel model)
var requires2Fa = context?.AcrValues.Count(t => t.Contains("mfa")) >= 1;

var user = await _userManager.FindByNameAsync(model.Email);
if(user != null && !user.TwoFactorEnabled && requires2Fa)
if (user != null && !user.TwoFactorEnabled && requires2Fa)
{
return RedirectToAction(nameof(ErrorEnable2FA));
}
Expand Down Expand Up @@ -161,7 +161,7 @@ public async Task<IActionResult> LoginFido2Mfa(string provider, bool rememberMe,

return View(new MfaModel { /*Provider = provider,*/ ReturnUrl = returnUrl, RememberMe = rememberMe });
}

[HttpGet]
[AllowAnonymous]
public IActionResult ErrorEnable2FA()
Expand Down Expand Up @@ -211,7 +211,7 @@ public async Task<IActionResult> Logout(LogoutViewModel model)
await _signInManager.SignOutAsync();
// await HttpContext.Authentication.SignOutAsync(idp, new AuthenticationProperties { RedirectUri = url });
}
catch(NotSupportedException)
catch (NotSupportedException)
{
}
}
Expand Down Expand Up @@ -260,7 +260,8 @@ public async Task<IActionResult> Register(RegisterViewModel model, string return
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser {
var user = new ApplicationUser
{
UserName = model.Email,
Email = model.Email,
IsAdmin = false
Expand Down Expand Up @@ -320,7 +321,7 @@ public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null,
}

var email = info.Principal.FindFirstValue(ClaimTypes.Email);

if (!string.IsNullOrEmpty(email))
{
var user = await _userManager.FindByNameAsync(email);
Expand Down Expand Up @@ -450,9 +451,9 @@ public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
await _emailSender.SendEmail(
model.Email,
model.Email,
"Reset Password",
$"Please reset your password by clicking here: {HtmlEncoder.Default.Encode(callbackUrl)}",
$"Please reset your password by clicking here: {HtmlEncoder.Default.Encode(callbackUrl)}",
"Hi Sir");

return View("ForgotPasswordConfirmation");
Expand Down Expand Up @@ -585,7 +586,7 @@ public async Task<IActionResult> VerifyCode(string provider, bool rememberMe, st
return View("Error");
}

if(string.IsNullOrEmpty(provider))
if (string.IsNullOrEmpty(provider))
{
provider = "Authenticator";
}
Expand Down
2 changes: 1 addition & 1 deletion content/StsServerIdentity/Controllers/ConsentController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ private async Task<ConsentViewModel> BuildViewModelAsync(string returnUrl, Conse
vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray();

var apiScopes = new List<ScopeViewModel>();
foreach(var parsedScope in request.ValidatedResources.ParsedScopes)
foreach (var parsedScope in request.ValidatedResources.ParsedScopes)
{
var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName);
if (apiScope != null)
Expand Down
2 changes: 1 addition & 1 deletion content/StsServerIdentity/Controllers/GrantsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ private async Task<GrantsViewModel> BuildViewModelAsync()
var grants = await _interaction.GetAllUserGrantsAsync();

var list = new List<GrantViewModel>();
foreach(var grant in grants)
foreach (var grant in grants)
{
var client = await _clients.FindClientByIdAsync(grant.ClientId);
if (client != null)
Expand Down
4 changes: 2 additions & 2 deletions content/StsServerIdentity/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ public static bool IsNativeClient(this AuthorizationRequest context)
public static IActionResult LoadingPage(this Controller controller, string viewName, string redirectUri)
{
controller.HttpContext.Response.StatusCode = 200;
controller.HttpContext.Response.Headers["Location"] = "";
controller.HttpContext.Response.Headers["Location"] = "";

return controller.View(viewName, new RedirectViewModel { RedirectUrl = redirectUri });
}
}
Expand Down
6 changes: 3 additions & 3 deletions content/StsServerIdentity/Fido2/Fido2Storage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace StsServerIdentity
{
public class Fido2Storage
{
private readonly ApplicationDbContext _applicationDbContext;
private readonly ApplicationDbContext _applicationDbContext;

public Fido2Storage(ApplicationDbContext applicationDbContext)
{
Expand All @@ -26,9 +26,9 @@ public async Task<List<FidoStoredCredential>> GetCredentialsByUsername(string us
public async Task RemoveCredentialsByUsername(string username)
{
var items = await _applicationDbContext.FidoStoredCredential.Where(c => c.Username == username).ToListAsync();
if(items != null)
if (items != null)
{
foreach(var fido2Key in items)
foreach (var fido2Key in items)
{
_applicationDbContext.FidoStoredCredential.Remove(fido2Key);
};
Expand Down
24 changes: 12 additions & 12 deletions content/StsServerIdentity/Fido2/FidoStoredCredential.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,25 @@

namespace StsServerIdentity
{
public class FidoStoredCredential
public class FidoStoredCredential
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Username { get; set; }
public byte[] UserId { get; set; }
public byte[] PublicKey { get; set; }
public byte[] UserHandle { get; set; }
public uint SignatureCounter { get; set; }
public string CredType { get; set; }
public DateTime RegDate { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Username { get; set; }
public byte[] UserId { get; set; }
public byte[] PublicKey { get; set; }
public byte[] UserHandle { get; set; }
public uint SignatureCounter { get; set; }
public string CredType { get; set; }
public DateTime RegDate { get; set; }
public Guid AaGuid { get; set; }

[NotMapped]
[NotMapped]
public PublicKeyCredentialDescriptor Descriptor
{
get { return string.IsNullOrWhiteSpace(DescriptorJson) ? null : JsonConvert.DeserializeObject<PublicKeyCredentialDescriptor>(DescriptorJson); }
set { DescriptorJson = JsonConvert.SerializeObject(value); }
}
public string DescriptorJson { get; set; }
public string DescriptorJson { get; set; }
}
}
6 changes: 4 additions & 2 deletions content/StsServerIdentity/Fido2/MfaFido2RegisterController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class MfaFido2RegisterController : Controller
private readonly IStringLocalizer _sharedLocalizer;

public MfaFido2RegisterController(
Fido2Storage fido2Storage,
Fido2Storage fido2Storage,
UserManager<ApplicationUser> userManager,
IOptions<Fido2Configuration> optionsFido2Configuration,
IStringLocalizerFactory factory)
Expand Down Expand Up @@ -57,6 +57,7 @@ private string FormatException(Exception e)
}

[HttpPost]
[ValidateAntiForgeryToken]
[Route("/mfamakeCredentialOptions")]
public async Task<JsonResult> MakeCredentialOptions([FromForm] string username, [FromForm] string displayName, [FromForm] string attType, [FromForm] string authType, [FromForm] bool requireResidentKey, [FromForm] string userVerification)
{
Expand All @@ -78,7 +79,7 @@ public async Task<JsonResult> MakeCredentialOptions([FromForm] string username,
// 2. Get user existing keys by username
var items = await _fido2Storage.GetCredentialsByUsername(identityUser.UserName);
var existingKeys = new List<PublicKeyCredentialDescriptor>();
foreach(var publicKeyCredentialDescriptor in items)
foreach (var publicKeyCredentialDescriptor in items)
{
existingKeys.Add(publicKeyCredentialDescriptor.Descriptor);
}
Expand Down Expand Up @@ -110,6 +111,7 @@ public async Task<JsonResult> MakeCredentialOptions([FromForm] string username,
}

[HttpPost]
[ValidateAntiForgeryToken]
[Route("/mfamakeCredential")]
public async Task<JsonResult> MakeCredential([FromBody] AuthenticatorAttestationRawResponse attestationResponse)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public class MfaFido2SignInFidoController : Controller
ServerDomain = _optionsFido2Configuration.Value.ServerDomain,
ServerName = _optionsFido2Configuration.Value.ServerName,
Origin = _optionsFido2Configuration.Value.Origin,
TimestampDriftTolerance = _optionsFido2Configuration.Value.TimestampDriftTolerance,
TimestampDriftTolerance = _optionsFido2Configuration.Value.TimestampDriftTolerance
});
}

Expand All @@ -61,6 +61,7 @@ private string FormatException(Exception e)
}

[HttpPost]
[ValidateAntiForgeryToken]
[Route("/mfaassertionOptions")]
public async Task<ActionResult> AssertionOptionsPost([FromForm] string username, [FromForm] string userVerification)
{
Expand All @@ -76,7 +77,7 @@ public async Task<ActionResult> AssertionOptionsPost([FromForm] string username,

if (!string.IsNullOrEmpty(identityUser.UserName))
{

var user = new Fido2User
{
DisplayName = identityUser.UserName,
Expand Down Expand Up @@ -115,6 +116,7 @@ public async Task<ActionResult> AssertionOptionsPost([FromForm] string username,
}

[HttpPost]
[ValidateAntiForgeryToken]
[Route("/mfamakeAssertion")]
public async Task<JsonResult> MakeAssertion([FromBody] AuthenticatorAssertionRawResponse clientResponse)
{
Expand Down Expand Up @@ -154,7 +156,7 @@ public async Task<JsonResult> MakeAssertion([FromBody] AuthenticatorAssertionRaw
{
throw new InvalidOperationException(_sharedLocalizer["FIDO2_UNABLE_TO_LOAD_2FA_AUTHENTICATED_USER"]);
}

var result = await _signInManager.TwoFactorSignInAsync("FIDO2", string.Empty, false, false);

// 7. return OK to client
Expand Down
2 changes: 1 addition & 1 deletion content/StsServerIdentity/LocalizationQueryProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpC
var requestCulture = query1.FirstOrDefault(t => t.Key == "ui_locales").Value;

var cultureFromReturnUrl = requestCulture.ToString();
if(string.IsNullOrEmpty(cultureFromReturnUrl))
if (string.IsNullOrEmpty(cultureFromReturnUrl))
{
return NullProviderCultureResult;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ namespace StsServerIdentity.Models.ManageViewModels
{
public class EnableAuthenticatorViewModel
{
[Required]
[StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Text)]
[Display(Name = "Verification Code")]
public string Code { get; set; }
[Required]
[StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Text)]
[Display(Name = "Verification Code")]
public string Code { get; set; }

[BindNever]
public string SharedKey { get; set; }
[BindNever]
public string SharedKey { get; set; }

[BindNever]
public string AuthenticatorUri { get; set; }
[BindNever]
public string AuthenticatorUri { get; set; }
}
}
2 changes: 1 addition & 1 deletion content/StsServerIdentity/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,6 @@ public static int Main(string[] args)
.Enrich.FromLogContext()
.WriteTo.Console(theme: AnsiConsoleTheme.Code)
);
});
});
}
}
2 changes: 1 addition & 1 deletion content/StsServerIdentity/Resources/LocService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public LocalizedString GetLocalizedHtmlString(string key)

public LocalizedString GetLocalizedHtmlStringAllowNull(string key)
{
if(!string.IsNullOrWhiteSpace(key))
if (!string.IsNullOrWhiteSpace(key))
{
return _localizer[key];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public static async Task<(X509Certificate2 ActiveCertificate, X509Certificate2 S
}

// search for local PFX with password, usually local dev
if(certs.ActiveCertificate == null)
if (certs.ActiveCertificate == null)
{
certs.ActiveCertificate = new X509Certificate2(
certificateConfiguration.DevelopmentCertificatePfx,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public KeyVaultCertificateService(string keyVaultEndpoint, string certificateNam
{
throw new ArgumentException("missing keyVaultEndpoint");
}

_keyVaultEndpoint = keyVaultEndpoint; // "https://damienbod.vault.azure.net"
_certificateName = certificateName; // certificateName
}
Expand All @@ -46,7 +46,7 @@ public async Task<(X509Certificate2 ActiveCertificate, X509Certificate2 Secondar
return certs;
}

private async Task<List<CertificateItem>> GetAllEnabledCertificateVersionsAsync( KeyVaultClient keyVaultClient)
private async Task<List<CertificateItem>> GetAllEnabledCertificateVersionsAsync(KeyVaultClient keyVaultClient)
{
// Get all the certificate versions (this will also get the currect active version
var certificateVersions = await keyVaultClient.GetCertificateVersionsAsync(_keyVaultEndpoint, _certificateName);
Expand Down
2 changes: 1 addition & 1 deletion content/StsServerIdentity/Services/EmailSender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public async Task SendEmail(string email, string subject, string message, string
//msg.AddContent(MimeType.Html, message);

msg.SetReplyTo(new EmailAddress(_optionsEmailSettings.Value.SenderEmailAddress, "damienbod"));

var response = await client.SendEmailAsync(msg);
}
}
Expand Down
Loading

0 comments on commit 02eb4ec

Please sign in to comment.