System.ObjectDisposedException only occuers some times others it work fine - c#

In my aproch to keep track of users actions in a web app, I am stuck in this error that occuers only when a wrong password is enter and I canĀ“t discover how to solve it.
The code breaks after the statement _context.UsersActivity.Add(this); and only when user enter a wrong password and this append only after I move the method "SaveUserActivity" from the controller to the class, before i wrote it in the controller and work fine.
Any help will be deligth.
Code from my Controller:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
await SaveUserActivity(model);
return RedirectToLocal("~/Users/Index");
}
if (result.RequiresTwoFactor)
{
return RedirectToAction(nameof(LoginWith2fa), new { returnUrl, model.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToAction(nameof(Lockout));
}
else
{
if (await IfUserExist(model))
PasswordFailure(model);
ModelState.AddModelError(string.Empty, "Log In Failure.");
return View(model);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
private async Task SaveUserActivity(LoginViewModel model, [CallerMemberName] string callerName = "")
{
string Action = null;
var RegActivity = new UserActivity();
switch (callerName)
{
case "Login":
Action = "Log In";
break;
case "IfUserExist":
Action = "Log In attempt from no exist user.";
break;
case "PasswordFailure":
Action = "Log In attempt with wrong password.";
break;
}
await RegActivity.SaveUserActivity(_context, _userManager, model.UserName, Action);
}
private async Task<bool> IfUserExist(LoginViewModel model)
{
var LoginAttemp = await _userManager.FindByNameAsync(model.UserName);
if (LoginAttemp == null)
{
ViewData["Page"] = new PageSettings("AccountControllerLoginPostInvalidUser");
await SaveUserActivity(model);
return false;
}
return true;
}
private async void PasswordFailure(LoginViewModel model)
{
ViewData["Page"] = new PageSettings("AccountControllerLoginPostInvalidPass");
await SaveUserActivity(model);
}
The code from my class that saves the users activity is this one:
public async Task SaveUserActivity(SiteDbContext _context, UserManager<SiteUser> _userManager, string _UserName, string _Action)
{
var user = await _userManager.FindByNameAsync(_UserName);
var status = (user == null || _Action.Contains("password"));
var roles = (status) ? null : await _userManager.GetRolesAsync(user);
UserName = _UserName;
Roles = (status) ? null : roles;
TimeStamp = DateTime.Now;
Action = _Action;
Role = (status) ? null : TransformListOfRolesToString();
_context.UsersActivity.Add(this);
_context.SaveChanges();
}

Related

ASP.NET Core External Identity Provider Login problem

I've tried to implement Google Login in my API, but the table "AspNetUserLogins" in SQL won't get populated.
public async Task<IActionResult> ExternalLoginCallback(string? returnUrl = null)
{
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null) {
return RedirectToAction(nameof(Login));
}
var signInResult = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
if (signInResult.Succeeded)
{
return RedirectToLocal(returnUrl);
}
if (signInResult.IsLockedOut)
{
return RedirectToAction(nameof(ForgotPassword));
}
else
{
ViewData["ReturnUrl"] = returnUrl;
ViewData["Provider"] = info.LoginProvider;
var email = info.Principal.FindFirstValue(ClaimTypes.Email);
return View("ExternalLogin", new ExternalLoginModel { Email = email });
}
}
On the "var signInResult", I get "failed" and I can't access the If statement. Can you please help me?
I've tried many different solutions, but none of them worked.
After testing, I found that Google and other three-party logins cannot be used like Asp.Net Core Identity, and can be used directly after modification.
When using Google provide.
we need add below code in controller
[HttpPost("google-login")]
public IActionResult GoogleLogin()
{
var properties = new AuthenticationProperties { RedirectUri = "/api/authentication/google-login-callback" };
return Challenge(properties, GoogleDefaults.AuthenticationScheme);
}
[HttpGet("google-login-callback")]
public async Task<IActionResult> GoogleLoginCallback()
{
var result = await HttpContext.AuthenticateAsync(GoogleDefaults.AuthenticationScheme);
if (!result.Succeeded)
{
return BadRequest("Failed to authenticate with Google.");
}
var user = new
{
Id = result.Principal.FindFirst(ClaimTypes.NameIdentifier).Value,
Email = result.Principal.FindFirst(ClaimTypes.Email).Value,
Name = result.Principal.FindFirst(ClaimTypes.Name).Value
};
// TODO: Create or update user account in your database.
return Ok(new { User = user });
}
2. And I replace the form tag in Login.cshtml like below.
3. Test Result
In Asp.Net Core Identity, we can change the code to api endpoint directly like below.
public async Task<IActionResult> LoginApi(string? returnUrl, InputModel? Input)
{
returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
return Redirect(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToPage("./Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Ok("Invalid login attempt.");
}
}
// If we got this far, something failed, redisplay form
return Ok("Invalid model.");
}

How to pass Data from action to action using mvc and not in router

i have an action inside of users and i want that action to return the user to another action in another controller but not in the router parameter, Here is a sample
public IActionResult LoginCheck(UserForm user)
{
AuthUser auth = new AuthUser(_context);
var result = auth.IsLoggedIn(user.Email, user.Password);
if(result.isfound==false)
{
return NotFound();
}
result.User.IsAuth = true;
return RedirectToAction("Home","Index",result.User);
}
public async Task<IActionResult> Index(User user)
{
if(user.IsAuth == false)
{
return Unauthorized();
}
just part of the code
Home index did not use the incoming user as it was sent as router parameters i think
Welcome to stackoverflow!
You can use TempData to achieve that.
public IActionResult LoginCheck(UserForm user)
{
AuthUser auth = new AuthUser(_context);
var result = auth.IsLoggedIn(user.Email, user.Password);
if(result.isfound==false)
{
return NotFound();
}
result.User.IsAuth = true;
TempData["user"] = result.User;
return RedirectToAction("Home","Index");
}
Then you can get it in the other action
public async Task<IActionResult> Index()
{
if(TempData["user"] == null)
{
return Unauthorized();
}else{
var someUser= (User)TempData["user"];
}
}
But I do not recommend using TempData for sensitive data.
You can use second action as method:
public async Task<IActionResult> LoginCheck(UserForm user)
{
AuthUser auth = new AuthUser(_context);
var result = auth.IsLoggedIn(user.Email, user.Password);
if(result.isfound==false)
{
return NotFound();
}
result.User.IsAuth = true;
return await Index(result.User);
}
second action
[NonAction] // Indicates that a controller method is not an action method.
public async Task<IActionResult> Index(User user)
{
if(user == null)
{
return Unauthorized();
}
else{
var someUser= user;
}
}
Use redirection make browser to handle this transfer and it is slower.

Entity Framework MFA with angular project

I encounter the issue with MFA when I copy the MFA feature from my ASP.NET Core MVC project to a Angular project.
Here is how I generate the QR code for MFA:
[HttpGet]
public async Task<IActionResult> GetEnableTwoFactorCode()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var model = new EnableTwoFactorDto();
await LoadSharedKeyAndQrCodeUriAsync(user, model);
return Ok(model);
}
This is when the user log in
[HttpPost("login")]
public async Task<ActionResult<UserDto>> Login(LoginDto loginDto)
{
if (loginDto.Email != null)
{
var user = await _userManager.Users.SingleOrDefaultAsync(x => x.UserName.ToLower() == loginDto.Email.ToLower());
if (user == null)
return Unauthorized("Invalid username");
var result = await _signInManager.CheckPasswordSignInAsync(user, loginDto.Password, false);
if (result.Succeeded)
{
return new UserDto
{
Email = user.Email,
FirstName = user.FirstName,
Token = await _tokenService.CreateToken(user),
};
}
if (result.RequiresTwoFactor)
{
return new UserDto
{
Email = user.Email,
FirstName = user.FirstName,
RequiresTwoFactor = true
};
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return Unauthorized("Locked out");
}
else
{
return Unauthorized();
}
}
else
{
return BadRequest("Empty email");
}
}
This is how I validate the MFA code when the user log in; the TwoFactorAuthenticatorSignInAsync will always return false even though I have enabled the MFA and my email address is confirmed.
[HttpPost]
public async Task<ActionResult<UserDto>> Authorize(LoginWithTwoFactorDto loginWithTwoFactorDto)
{
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var authenticatorCode = loginWithTwoFactorDto.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty);
var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, loginWithTwoFactorDto.RememberMe, loginWithTwoFactorDto.RememberMachine);
if (result.Succeeded)
{
_logger.LogInformation("User with ID {UserId} logged in with 2fa.", user.Id);
return new UserDto
{
Email = user.Email,
FirstName = user.FirstName,
Token = await _tokenService.CreateToken(user),
};
}
else if (result.IsLockedOut)
{
_logger.LogWarning("User with ID {UserId} account locked out.", user.Id);
return Unauthorized("Locked out");
}
else
{
_logger.LogWarning("Invalid authenticator code entered for user with ID {UserId}.", user.Id);
ModelState.AddModelError(string.Empty, "Invalid authenticator code.");
return Unauthorized("Invalid authenticator code.");
}
}
I have the same code in the MVC project and it works perfectly but not when I use Angular as front end and use the API call for logging in and activating the MFA.
I don't know if I did a bad practice here or the system build in MFA is only work in a MVC project ?

How To Pass ErrorMessage From API to MVC In Asp.Net Core

I've created an API. When you face an error, It shows you the type of error with it's message. But When I try to use that API in my MVC project, It just shows the type of error. I want to see the message in Modelstate.AddModelError
Here is API controller for Login
[HttpPost("login")]
public async Task<IActionResult> LoginUser([FromBody] UserDtoLogin user)
{
var userToRetrieve = await _applicationDbContext.Users.FirstOrDefaultAsync(u => u.UserName == user.UserName);
if (userToRetrieve == null)
{
ModelState.AddModelError("username", "Such a user doesn't exists! Enter the correct username please");
return NotFound(ModelState);
}
if (!_userRepository.VerifyPasswordHash(user.Password, userToRetrieve.PasswordHash, userToRetrieve.PasswordSalt))
{
ModelState.AddModelError("password", "Wrong Password!");
return BadRequest(ModelState);
}
await _userRepository.Login(userToRetrieve);
return Ok(user);
}
Here is MVC Controller for Login
[HttpPost]
public async Task<IActionResult> Login(User user)
{
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:42045/api/user/login");
if (user != null)
{
request.Content = new StringContent(JsonConvert.SerializeObject(user),
System.Text.Encoding.UTF8, "application/json");
}
var client = _clientFactory.CreateClient();
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
ViewBag.StatusCode = System.Net.HttpStatusCode.OK;
var apiString = await response.Content.ReadAsStringAsync();
user = JsonConvert.DeserializeObject<User>(apiString);
}
else
{
ViewBag.StatusCode = response.StatusCode;
}
return View(user);
}
I write a simple demo to show how to pass ErrorMessage From API to MVC In Asp.Net Core, you can reference to it.
API
[Route("api/[controller]")]
[ApiController]
public class LoginController : ControllerBase
{
//For testing convenience, I use hard code here
List<UserDtoLogin> context = new List<UserDtoLogin>
{
new UserDtoLogin
{
UserName = "Mike"
},
new UserDtoLogin
{
UserName = "Jack"
},
new UserDtoLogin
{
UserName = "Lily"
}
};
[HttpPost("login")]
public IActionResult LoginUser([FromBody] UserDtoLogin user)
{
var userToRetrieve = context.FirstOrDefault(u => u.UserName == user.UserName);
if (userToRetrieve == null)
{
return BadRequest("Such a user doesn't exists! Enter the correct username please");
}
//your logic.....
return Ok();
}
}
MVC/Controller
[HttpPost]
public async Task<IActionResult> Privacy(UserDtoLogin todoItem)
{
var todoItemJson = new StringContent(JsonSerializer.Serialize(todoItem),Encoding.UTF8,Application.Json);
using var httpResponseMessage = await _httpClient.PostAsync("your api url", todoItemJson);
var errormessage = httpResponseMessage.Content.ReadAsStringAsync().Result;
return View(todoItem);
}
Then you can see it can receive the errormessage successfully.

Multi database login

I know this is repeated question and even here but I have tried all solutions without no sense. I have the main DB where the user check if found and return by his own or related database that should change the connection string to be current used one. My problem is that despite it's got the database correct but a dbcontext works on the main database it diverts again I don't why.
My applicationdbcontext is
public ApplicationDbContext(string connectionString)
: base(string.IsNullOrEmpty(connectionString) ? "DefaultConnection" : connectionString, throwIfV1Schema: false)
{
this.Database.CommandTimeout = 600;
}
public static ApplicationDbContext Create(string dbCatlogConn)
{
return new ApplicationDbContext(ConString.dbCatlogConn);
}
and this is my public class
public class ConString
{
public static string dbCatlogConn { get; set; }
}
This is my login in accountcontroller class
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl, string language = Config.DefaultLanguage)
{
try
{
System.Web.Helpers.AntiForgery.Validate();
}
catch
{
return RedirectToRoute("Login", new { language, returnUrl });
}
dbName = model.username;
Session["dbName"] = dbName;
var dbname = db.SchoolLists
.Where(t => (t.dbname == dbName))
.Select(t => new { ConnString = t.ConnectionString }).ToList();
// new conection
dbConnectionString = "";
Session["ConnectionString"] = dbConnectionString;
db = new ApplicationDbContext();
UserManager.PasswordHasher = new CustomPasswordHasher();
bool CheckConnResult = db.Database.Exists();
// code here
var user = db.Users.Where(e => e.UserName.ToLower() == model.UserName.ToLower()).FirstOrDefault();
var result = new SignInStatus();
if (user == null)
result = SignInStatus.Failure;
else
{
string dbPassword = dal.DecryptPassword(user.AnotherUsername, user.AnotherSalt, user.PasswordHash);
var status = UserManager.PasswordHasher.VerifyHashedPassword(dbPassword, model.Password);
if (status == PasswordVerificationResult.Success)
// error here
result = await SignInManager.PasswordSignInAsync(model.UserName, user.PasswordHash, model.RememberMe, shouldLockout: false);
// result = SignInStatus.Success;
else
result = SignInStatus.Failure;
}
switch (result)
{
case SignInStatus.Success:
if (user != null)
{
if (user.Disabled == true)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
ModelState.AddModelError("", language == "Invalid login attempt.");
// rest the connection to default
// = ConString.Mainbd;
return View(model);
//return View("Lockout");
}
else
{
}
}
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", language == "Invalid login attempt.");
return View(model);
}
}
I try also to change applicationdbcontext create in a startup file
app.CreatePerOwinContext(() => ApplicationDbContext.Create(ConString.dbCatlogConn));
but the ApplicationSignInManager always uses the main connection - it doesn't update for the new one after login despite I got the connection string correctly
Could you please try by creating a configure like below? and then change the connection string dynamically.
public static class ApplicationDbContextConfigurer
{
public static void Configure(
DbContextOptionsBuilder<ApplicationDbContext> builder, string connectionString)
{
builder.UseSqlServer(connectionString);
}
public static void Configure(
DbContextOptionsBuilder<ApplicationDbContext> builder, DbConnection connection)
{
builder.UseSqlServer(connection);
}
}
Change connection string
DbContextOptionsBuilder<ApplicationDbContext> builder =
new DbContextOptionsBuilder<VineforceDbContext>();
ApplicationDbContextConfigurer.Configure(builder, connectionString);

Categories