public async Task <bool> CompleteLogin(string userId, AuthenticatorAssertionRawResponse response) { await using var dbContext = _contextFactory.CreateContext(); var user = await dbContext.Users.Include(applicationUser => applicationUser.Fido2Credentials) .FirstOrDefaultAsync(applicationUser => applicationUser.Id == userId); if (user == null || !LoginStore.TryGetValue(userId, out var options)) { return(false); } var credential = user.Fido2Credentials .Where(fido2Credential => fido2Credential.Type is Fido2Credential.CredentialType.FIDO2) .Select(fido2Credential => (fido2Credential, fido2Credential.GetBlob())) .FirstOrDefault(fido2Credential => fido2Credential.Item2.Descriptor.Id.SequenceEqual(response.Id)); if (credential.Item2 is null) { return(false); } // 5. Make the assertion var res = await _fido2.MakeAssertionAsync(response, options, credential.Item2.PublicKey, credential.Item2.SignatureCounter, x => Task.FromResult(true)); // 6. Store the updated counter credential.Item2.SignatureCounter = res.Counter; credential.fido2Credential.SetBlob(credential.Item2); await dbContext.SaveChangesAsync(); LoginStore.Remove(userId, out _); // 7. return OK to client return(true); }
public async Task <JsonResult> SignIn([FromBody] AuthenticatorAssertionRawResponse clientResponse) { try { var jsonOptions = HttpContext.Session.GetString("fido2.assertionOptions"); var options = AssertionOptions.FromJson(jsonOptions); var user = await GetUserByCredentials(clientResponse.Id); var credential = JsonConvert.DeserializeObject <StoredCredential>(user.Profile["PasswordlessPublicKey"].ToString()); var result = await fido2.MakeAssertionAsync(clientResponse, options, credential.PublicKey, credential.SignatureCounter, args => Task.FromResult(credential.UserHandle.SequenceEqual(args.UserHandle))); await UpdateCounter(user, credential, result.Counter); var claims = new List <Claim> { new Claim(ClaimTypes.Name, user.Profile.Email) }; var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity)); return(Json(result)); } catch (Exception e) { return(Json(new AssertionVerificationResult { Status = "error", ErrorMessage = e.Message })); } }
public async Task <JsonResult> MakeAssertion([FromBody] AuthenticatorAssertionRawResponse clientResponse) { try { // 1. Get the assertion options we sent the client var jsonOptions = HttpContext.Session.GetString("fido2.assertionOptions"); var options = AssertionOptions.FromJson(jsonOptions); // 2. Get registered credential from database var signInUser = await _signInManager.GetTwoFactorAuthenticationUserAsync(); var applicationUser = await _userManager.FindByNameAsync(signInUser.UserName); var creds = _fido2CredentialService.GetCredentialByUser(applicationUser); if (creds == null) { throw new Exception("Unknown credentials"); } // 3. Get credential counter from database var storedCounter = creds.SignatureCounter; // 4. Create callback to check if userhandle owns the credentialId IsUserHandleOwnerOfCredentialIdAsync callback = async(args) => { var storedCreds = await _fido2CredentialService.GetCredentialsByUserHandleAsync(args.UserHandle); return(storedCreds.Exists(c => new PublicKeyCredentialDescriptor(c.Descriptor).Id.SequenceEqual(args.CredentialId))); }; // 5. Make the assertion var res = await _fido2.MakeAssertionAsync(clientResponse, options, creds.PublicKey, storedCounter, callback); // 6. Store the updated counter var code = await _fido2CredentialService.UpdateCounter(res.CredentialId, res.Counter); // TODO Fix counter // TODO Allow more than one key to work // var result = await _signInManager.TwoFactorSignInAsync("Fido2", string.Empty, false, false); await _signInManager.SignInAsync(applicationUser, false); // 7. return OK to client return(Json(res)); } catch (Exception e) { return(Json(new AssertionVerificationResult { Status = "error", ErrorMessage = FormatException(e) })); } }
public async Task <CacheItem> ExecuteAsync(string fido2Payload, CacheItem relatedItem) { var request = OwnIdSerializer.Deserialize <Fido2LoginRequest>(fido2Payload); var storedFido2Info = await _userHandlerAdapter.FindFido2InfoAsync(request.CredentialId); if (storedFido2Info == null) { throw new OwnIdException(ErrorType.UserNotFound); } var options = new AssertionOptions { Challenge = Encoding.ASCII.GetBytes(relatedItem.Context), RpId = _configuration.Fido2.RelyingPartyId }; var fidoResponse = new AuthenticatorAssertionRawResponse { Extensions = new AuthenticationExtensionsClientOutputs(), Id = Base64Url.Decode(request.CredentialId), RawId = Base64Url.Decode(request.CredentialId), Response = new AuthenticatorAssertionRawResponse.AssertionResponse { AuthenticatorData = Base64Url.Decode(request.AuthenticatorData), ClientDataJson = Base64Url.Decode(request.ClientDataJson), Signature = Base64Url.Decode(request.Signature), UserHandle = Base64Url.Decode(storedFido2Info.UserId) }, Type = PublicKeyCredentialType.PublicKey }; var result = await _fido2.MakeAssertionAsync( fidoResponse, options, Base64Url.Decode(storedFido2Info.PublicKey), storedFido2Info.SignatureCounter, args => { var storedCredentialId = Base64Url.Decode(storedFido2Info.CredentialId); var storedCredDescriptor = new PublicKeyCredentialDescriptor(storedCredentialId); var credIdValidationResult = storedCredDescriptor.Id.SequenceEqual(args.CredentialId); return(Task.FromResult(credIdValidationResult)); }); return(await _cacheItemRepository.UpdateAsync(relatedItem.Context, item => { item.PublicKey = storedFido2Info.PublicKey; item.Fido2SignatureCounter = result.Counter; item.Fido2CredentialId = request.CredentialId; item.FinishFlow(storedFido2Info.UserId, storedFido2Info.PublicKey); })); }
public async Task <bool> ValidateAsync(string purpose, string token, UserManager <User> manager, User user) { var userService = _serviceProvider.GetRequiredService <IUserService>(); if (!(await userService.CanAccessPremium(user)) || string.IsNullOrWhiteSpace(token)) { return(false); } var provider = user.GetTwoFactorProvider(TwoFactorProviderType.WebAuthn); var keys = LoadKeys(provider); if (!provider.MetaData.ContainsKey("login")) { return(false); } var clientResponse = JsonSerializer.Deserialize <AuthenticatorAssertionRawResponse>(token, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); var jsonOptions = provider.MetaData["login"].ToString(); var options = AssertionOptions.FromJson(jsonOptions); var webAuthCred = keys.Find(k => k.Item2.Descriptor.Id.SequenceEqual(clientResponse.Id)); if (webAuthCred == null) { return(false); } IsUserHandleOwnerOfCredentialIdAsync callback = (args) => Task.FromResult(true); var res = await _fido2.MakeAssertionAsync(clientResponse, options, webAuthCred.Item2.PublicKey, webAuthCred.Item2.SignatureCounter, callback); provider.MetaData.Remove("login"); // Update SignatureCounter webAuthCred.Item2.SignatureCounter = res.Counter; var providers = user.GetTwoFactorProviders(); providers[TwoFactorProviderType.WebAuthn].MetaData[webAuthCred.Item1] = webAuthCred.Item2; user.SetTwoFactorProviders(providers); await userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.WebAuthn, logEvent : false); return(res.Status == "ok"); }
public async Task <JsonResult> MakeAssertion([FromBody] AuthenticatorAssertionRawResponse clientResponse) { try { // 1. Get the assertion options we sent the client var jsonOptions = HttpContext.Session.GetString("fido2.assertionOptions"); var options = AssertionOptions.FromJson(jsonOptions); // 2. Get registered credential from database var creds = PasswordlessStore.GetCredentialById(clientResponse.Id); if (creds == null) { throw new Exception("Unknown credentials"); } // 3. Get credential counter from database var storedCounter = creds.SignatureCounter; // 4. Create callback to check if userhandle owns the credentialId IsUserHandleOwnerOfCredentialIdAsync callback = async(args) => { var storedCreds = await PasswordlessStore.GetCredentialsByUserHandleAsync(args.UserHandle); return(storedCreds.Exists(c => c.Descriptor.Id.SequenceEqual(args.CredentialId))); }; // 5. Make the assertion var res = await _fido2.MakeAssertionAsync(clientResponse, options, creds.PublicKey, storedCounter, callback); // 6. Store the updated counter PasswordlessStore.UpdateCounter(res.CredentialId, res.Counter); if (res.Status == "ok") { var username = System.Text.Encoding.UTF8.GetString(creds.UserId); await SignInOidc(username); } // 7. return OK to client return(Json(res)); } catch (Exception e) { return(Json(new AssertionVerificationResult { Status = "error", ErrorMessage = FormatException(e) })); } }
MakeAssertionAsync(AnonymousAuthenticatorAssertionRawResponse clientResponse, string deviceName) { try { // 1. Get the assertion options we sent the client if (string.IsNullOrWhiteSpace(clientResponse.RequestId)) { _logger.LogError("Attempted to make assertion without request id"); return(Result.Failure <(User, string, string), (bool, string)>((false, "No user id or request id provided"))); } var cacheKey = $"AssertionOptions:{clientResponse.RequestId}"; if (!_cache.TryGetValue(cacheKey, out AssertionOptions options)) { _logger.LogError($"Failed to find assertion options for key '{cacheKey}'"); return(Result.Failure <(User, string, string), (bool, string)>((true, "No assertion found"))); } _cache.Remove(cacheKey); // 2. Get registered credential from database var authorization = await _db.DeviceAuthorizations .Include(x => x.User) //todo In .net 5, ef-core 5 will support using SequenceEqual, for now we'll use normal equality and just remember that it will be properly translated into sql for real databases, see https://github.com/dotnet/efcore/issues/10582 //.FirstOrDefaultAsync(x => x.CredentialId.SequenceEqual(clientResponse.Id)); .FirstOrDefaultAsync(x => x.CredentialId == clientResponse.Id); if (authorization is null) { _logger.LogError("Failed to find credentials"); return(Result.Failure <(User, string, string), (bool, string)>((true, "No credentials found"))); } // 3. Make the assertion var avr = await _fido2.MakeAssertionAsync(clientResponse, options, authorization.PublicKey, authorization.SignatureCounter, x => Task.FromResult(true)); // 4. Store the updated counter authorization.SignatureCounter = avr.Counter; var login = _userService.GenerateAndSaveLogin(authorization.User, deviceName, authorization.Id); return(Result.Success <(User user, string accessToken, string refreshToken), (bool unauthorized, string message)>(login)); } catch (Exception e) { _logger.LogError(e, "Failed to make assertion for user"); return(Result.Failure <(User, string, string), (bool, string)>((false, "Error making assertion"))); } }
public async Task <IActionResult> MakeAssertion(AuthenticatorAssertionRawResponse clientResponse) { try { // 1. Get the assertion options we sent the client var jsonOptions = HttpContext.Session.GetString("fido2.assertionOptions"); var options = AssertionOptions.FromJson(jsonOptions); // 2. Get registered credential from database var credentialIdString = Base64Url.Encode(clientResponse.Id); var cred = await context.StoredCredentials.Where(x => x.DescriptorJson.Contains(credentialIdString)).FirstOrDefaultAsync(); if (cred == null) { throw new Exception("Unknown credentials"); } // 3. Get credential counter from database var storedCounter = cred.SignatureCounter; // 4. Create callback to check if userhandle owns the credentialId IsUserHandleOwnerOfCredentialIdAsync callback = async(args) => { var storedCreds = await context.StoredCredentials.Where(x => x.UserHandle == args.UserHandle).ToListAsync(); return(storedCreds.Exists(c => c.Descriptor.Id.SequenceEqual(args.CredentialId))); }; // 5. Make the assertion var res = await _fido2.MakeAssertionAsync(clientResponse, options, cred.PublicKey, storedCounter, callback); // 6. Store the updated counter cred.SignatureCounter = res.Counter; await context.SaveChangesAsync(); // 7. return OK to client return(Ok(res)); } catch (Exception e) { return(BadRequest(new AssertionVerificationResult { Status = "error", ErrorMessage = FormatException(e) })); } }
/// <summary> /// 断言:验证断言响应 /// <para>当客户端返回响应时,我们对其进行验证并接受登录。</para> /// </summary> public async Task <AssertionVerificationResult> MakeAssertionAsync(User user, AuthenticatorAssertionRawResponse clientRespons) { if (user == null) { return(new AssertionVerificationResult() { Status = "bad" }); } // 1. Get the assertion options we sent the client var jsonOptions = distributedCache.GetString(user.UserId.ToString() + "assertionOptions"); if (string.IsNullOrEmpty(jsonOptions)) { return(new AssertionVerificationResult() { Status = "fail", ErrorMessage = "time out" }); } var options = AssertionOptions.FromJson(jsonOptions); // 2. Get registered credential from database var storedCredential = this.GetFIDO2ItemByUserId(user.UserId); // 3. Get credential counter from database var creds = user.FIDO2Items.Where(b => b.CredentialId.SequenceEqual(clientRespons.Id)).FirstOrDefault(); var storedCounter = creds.SignatureCounter; // 4. Create callback to check if userhandle owns the credentialId IsUserHandleOwnerOfCredentialIdAsync callback = async(args) => { return(storedCredential.Exists(c => c.CredentialId.SequenceEqual(args.CredentialId))); }; // 5. Make the assertion var res = await _fido2.MakeAssertionAsync(clientRespons, options, creds.PublicKey, storedCounter, callback); // 6. Store the updated counter creds.SignatureCounter = res.Counter; dataContext.SaveChanges(); return(res); }