.NET Core unit test how to Mock HttpContext RemoteIpAddress? - c#

I have a .NET Core 3.1 project using Identity. For the Login page handler I have added a line of code that after a user logs in, it updates a users location based on their IP address:
_locationRepository.UpdateUserLocationAsync(HttpContext.Connection.RemoteIpAddress);
Full Code
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
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)
{
_locationRepo.UpdateUserLocation(HttpContext.Connection.RemoteIpAddress);
_logger.LogInformation("User logged in.");
return LocalRedirect(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 Page();
}
}
// If we got this far, something failed, redisplay form
return Page();
}
My problem is when writing the unit test, I don't know how to properly mock the HttpContext. I keep getting a null reference exception regardless of what I have tried.
var httpContext = new Mock<HttpContext>();
httpContext.Setup(x => x.Connection.RemoteIpAddress).Returns(new IPAddress(16885952));
How do I mock the RemoteIpAddress?

Xunit + FakeItEasy
var mockHttpContextAccessor = new Fake<IHttpContextAccessor>();
var httpContext = new DefaultHttpContext()
{
Connection =
{
RemoteIpAddress = new IPAddress(16885952)
}
};
mockHttpContextAccessor.CallsTo(x => x.HttpContext)
.Returns(httpContext);
var extractedIpAddress = IpExtractor.GetIpAddress(httpContext);
//assert 192.168.1.1

Here is my solution
public static void MockIpAddress(this ControllerBase controller)
{
controller.ControllerContext = new ControllerContext()
{
HttpContext = new DefaultHttpContext()
{
Connection =
{
RemoteIpAddress = new System.Net.IPAddress(16885952)
}
}
};
}

The setup you have won't match any call. You need to mock the Connection as well:
var connectionMock = new Mock<Microsoft.AspNetCore.Http.ConnectionInfo>(MockBehavior.Strict);
connectionMock.SetupGet(c => c.RemoteIpAddress).Returns(new IPAddress(16885952));
var httpContext = new Mock<HttpContext>(MockBehavior.Strict);
httpContext.SetupGet(x => x.Connection).Returns(connectionMock.Object);
Set your MockBehavior to Strict to throw exceptions when calls are made to properties or methods that weren't set up properly.

Related

Header Request too long in Error 400 on Chrome, but works fine on firefox

AccountController
This is my SignMeIn Claim function
private void SignMeIn(User user, bool isPersistent)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.Email, user.Email),
new Claim(Global.CustomClaimTypes.UserId, user.UserId),
new Claim(Global.CustomClaimTypes.UserType, user.UserType),
new Claim(Global.CustomClaimTypes.UserModules, JsonConvert.SerializeObject(user.Rights)),
// ISSUE 001 - ONLY ON CHROME. WORKS FINE ON FIREFOX! - 05/06/2022
// Too heavy claim for store. Causing ERROR 400, data requested from the server is too long.
// Comment the code below to remove claim request from session!
new Claim(Global.CustomClaimTypes.UserStores, JsonConvert.SerializeObject(user.Stores)) // Original Code
};
var id = new ClaimsIdentity(claims, Global.AuthenticationTypes.ApplicationCookie);
var ctx = Request.GetOwinContext();
var authenticationManager = ctx.Authentication;
authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, id);
}
I'm using claims to save all user data into its current session, and can easily access Global variables and functionalities.
It work's fine calling out in Login Function
public ActionResult Login(LoginViewModel model, string returnUrl)
{
try
{
if (ModelState.IsValid)
{
var user = _userService.VerifyUser(model.InputUserID, ClsRijndael.EncryptRijndael(model.InputPassword));
if (user != null)
{
SignMeIn(user, model.RememberMe);
// Calling the fetch function - 05/06/2022
FetchStore(user);
//_sessionService.Set<string>(Global.SessionKeys.UserImage, user.ImageData);
if (user.UserType == "PCP")
{
return RedirectToAction("Main", "PCP");
}
else if (user.UserType == "SIC")
{
return RedirectToAction("Main", "SIC");
}
else if (user.UserType == "CCP")
{
return RedirectToAction("Main", "CCP");
}
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
}
return View(model);
}
catch (AuthenticationException)
{
ModelState.AddModelError("", "There is a problem with your account, please contact your administrator.");
return View(model);
}
catch (System.Exception)
{
ModelState.AddModelError("", "Invalid username or password.");
return View(model);
}
}
But if I run on Chrome, it gives the Error 400 on specific that has quite a load of data.
CHROME ERROR
BUT IT WORKS FINE ON FIREFOX
Solutions I've tried:
clearing all the cookies and it doesn't seem to work.
adding a new claim outside the controller, but I haven't seen the fitting article for it so far.
web.server maxRequestLength = 'maximum amount'

How to create confirm email callback from repository class in ASP.NET Core 3.1

I want to make an email confirmation callback link from asp.net core web API repository class,
This is easy when done in the controller of the API, but I am having trouble doing this from the repository class.
I keep getting this error:
Could not find an IRouter associated with the ActionContext. If your application is using endpoint routing then you can get a IUrlHelperFactory with dependency injection and use it to create a UrlHelper, or use Microsoft.AspNetCore.Routing.LinkGenerator.
this is what I have so far:
this is my repository code for registration:
public async Task<(bool IsSuccess, string ErrorMessage)> Register(RegisterDTO model)
{
if (model != null)
{
var user = _mapper.Map<ApplicationUser>(model);
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await _userManager.AddToRoleAsync(user, "Admin");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
string _scheme = _urlHelper.ActionContext.HttpContext.Request.Scheme;
//var callbackurl = _urlHelper.Link("ConfirmEmail", new { email =
user.Email, code = code });
var callbackurl =
_urlHelper.Action("ConfirmEmail",nameof(AccountController), new { email = user.Email, code = code }, _scheme);
var mailresult = _mailSender.SendEmail(model.Email, "Please confirm your
account by clicking here: link");
return (true, "Account created successfully");
}
return (false, "Eror occured while creating your account");
}
return (false, "Please provide the user data");
}
Account Controller
[HttpGet("{token}/{email}")]
public async Task<IActionResult> ConfirmEmail(string token, string email)
{
var result = await _repository.ConfirmEmail(token, email);
if (result.IsSuccess)
return Ok(result.ErrorMessage);
else
return BadRequest(result.ErrorMessage);
}
I managed to get it to work.
see the code below:
enter code here
public async Task<(bool IsSuccess, string ErrorMessage)>
Register(RegisterDTO model)
{
if (model != null)
{
var user = _mapper.Map<ApplicationUser>(model);
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await _userManager.AddToRoleAsync(user, "Admin");
var _urlHelper = _urlHelperFactory.GetUrlHelper(
_actionContextAccessor.ActionContext);
var code = await _userManager.
GenerateEmailConfirmationTokenAsync(user);
string _scheme = _urlHelper.ActionContext.
HttpContext.Request.Scheme;
var callbackurl = _urlHelper.Action(action:
nameof(AccountController.ConfirmEmail),
controller: "Account", values:
new { email = user.Email, code = code },
protocol: _scheme);
var mailresult = _mailSender.SendEmail(model.Email, "Please
confirm your account by clicking here: <a href=\"" +
callbackurl + "\">link</a>");
if (mailresult.IsSuccess)
{
return (true, "Account created successfully, Please visit
your email to confirm your email");
}
else
{
return (true, $"Account created successfully,
{mailresult.ErrorMessage}");
}
}
return (false, result.Errors.ToString());
}
return (false, "Please provide the user data");
}

Confirm Email in asp.net core web api

This API is intended for a mobile application. The goal is to let the user confirm the email upon registration. When the user registers, a confirmation link is generated and sent over the email. I've done it the same way in a MVC project, it worked fine, but in a Web API project looks like it ain't gonna cut.
Now when the user clicks that link, the respective action method should be hit and do the job.
The only problem is, the ConfirmEmail action method is just not getting triggered when clicking the confirmation link although it looked fine.
Here are the main configurations which might help
MVC service configuration
services.AddMvc(options =>
{
options.EnableEndpointRouting = true;
options.Filters.Add<ValidationFilter>();
})
.AddFluentValidation(mvcConfiguration => mvcConfiguration.RegisterValidatorsFromAssemblyContaining<Startup>())
.SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_3_0);
Identity Service
public async Task<AuthenticationResult> RegisterAsync(string email, string password)
{
var existingUser = await _userManager.FindByEmailAsync(email);
if(existingUser != null)
{
return new AuthenticationResult { Errors = new[] { "User with this email address exists" } };
}
// generate user
var newUser = new AppUser
{
Email = email,
UserName = email
};
// register user in system
var result = await _userManager.CreateAsync(newUser, password);
if (!result.Succeeded)
{
return new AuthenticationResult
{
Errors = result.Errors.Select(x => x.Description)
};
}
// when registering user, assign him user role, also need to be added in the JWT!!!
await _userManager.AddToRoleAsync(newUser, "User");
// force user to confirm email, generate token
var token = await _userManager.GenerateEmailConfirmationTokenAsync(newUser);
// generate url
var confirmationLink = _urlHelper.Action("ConfirmEmail", "IdentityController",
new { userId = newUser.Id, token = token }, _httpRequest.HttpContext.Request.Scheme);
// send it per email
var mailresult =
await _emailService.SendEmail(newUser.Email, "BingoApp Email Confirmation",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(confirmationLink)}'>clicking here</a>.");
if (mailresult)
return new AuthenticationResult { Success = true };
else
return new AuthenticationResult { Success = false, Errors = new List<string> { "Invalid Email Address"} };
}
Controller
[HttpPost(ApiRoutes.Identity.Register)]
public async Task<IActionResult> Register([FromBody] UserRegistrationRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest(new AuthFailedResponse
{
Errors = ModelState.Values.SelectMany(x => x.Errors.Select(xx => xx.ErrorMessage))
});
}
// register the incoming user data with identity service
var authResponse = await _identityService.RegisterAsync(request.Email, request.Password);
if (!authResponse.Success)
{
return BadRequest(new AuthFailedResponse
{
Errors = authResponse.Errors
});
}
// confirm registration
return Ok();
}
[HttpGet]
public async Task<IActionResult> ConfirmEmail(string userId, string token)
{
if (userId == null || token == null)
{
return null;
}
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return null;
}
var result = await _userManager.ConfirmEmailAsync(user, token);
if (result.Succeeded)
{
await _emailService.SendEmail(user.Email, "BingoApp - Successfully Registered", "Congratulations,\n You have successfully activated your account!\n " +
"Welcome to the dark side.");
}
return null;
}
Your _urlHelper.Action(..) looks a bit suspicious to me.
I'm not sure you should pass the full controller name, that is, including the actual word controller.
Try _urlHelper.Action("ConfirmEmail", "Identity", instead.
As a tip: I try to avoid magic strings like these by using nameof(IdentityController) because it will return the controller name without the controller postfix.

How to authorize the user after confirming the email?

I'm writing api for the application
The task is to redirect to the profile page on the client after confirming the email. While email is not confirmed, I can not give access token to api.
At the moment a crutch lives on the client: username and password
temporarily stored locally, so that after the customer can make a hidden sign in.(what is not safe)
How can this problem be solved?
I have one not very nice idea: after registration give a access token and after on for each request check - emailConfirmed(bool)
But - perform an additional request each time ...
Small code example from back:
ApplicationOAuthProvider:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
var a = await userManager.FindByIdAsync(context.ClientId);
if (user == null)
{
context.SetError("invalid_grant", "Username or Password is incorrect.");
return;
}
if (!user.EmailConfirmed)
{
context.SetError("invalid_grant", "Email not confirmed.");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
Send code on email:
[AllowAnonymous]
[HttpPost]
[Route("Users/{userId}/emailConfirmation")]
public async Task<IHttpActionResult> SendConfirmationCode(string userId)
{
if (userId == null)
{
return BadRequest("UserId were not transferred");
}
var user = await UserManager.FindByIdAsync(userId);
if (user == null)
{
return Content(HttpStatusCode.NotFound, "User not found");
}
var code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var url = $"{clientBaseUrl}confirm_email?userId={user.Id}&code={WebUtility.UrlEncode(code)}";
await MailHelper.SendConfirmationLinkOnEmail(url, user.Email);
return Content(HttpStatusCode.OK, new { Message = "Confirmation code was sent on email" });
}
Email confirmation by code:
[AllowAnonymous]
[HttpPost]
[Route("Users/{userId}/emailConfirmationCode")]
public async Task<IHttpActionResult> ConfirmEmail(string userId, [FromBody]EmailConfirmationDTO input)
{
if (userId == null || input == null)
{
return BadRequest("UserId or code were not transferred");
}
try
{
var isConfirmed = await UserManager.IsEmailConfirmedAsync(userId);
if (isConfirmed)
{
return Content(HttpStatusCode.Forbidden, new { Message = "This user already confirmed email" });
}
var result = await UserManager.ConfirmEmailAsync(userId, input.Code);
if (result.Succeeded)
{
var user = Db.Users.FirstOrDefaultAsync(u => u.Id == userId);
return Content(HttpStatusCode.OK, new { responseObj = user });
}
}
catch (Exception e)
{
// ignored
}
return Content(HttpStatusCode.InternalServerError, new { Message = "Error confirmation." });
}
ConfigureAuth:
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(5),
#if (!DEBUG)
AllowInsecureHttp = false
#else
AllowInsecureHttp = true
#endif
};

Asp net Core Identity token authentication expiration

I use asp net core Identity. This is what I am trying to do and I have no Idea how to do this so need some expert help on this. When a new user registers in my application , a password reset link is sent to the person's email, Now when the link token in the link has expired and the user clicks on the link I need to show a message as token expired. How do I find out If the token has expired or not when the user clicks on the link which is in the email. Can any one suggest ways to achieve this scenario?
The ASP.NET Core has a built-in Claims system to store information about the user .
To do that ,
add a helper method to store the expiration datetime in Register.cshtml.cs:
private async Task AddTokenExpirationInfo(IdentityUser user, int span=1*24*60)
{
var expiresAt = DateTime.Now.Add(TimeSpan.FromMinutes(span));
var tokenExpiredAtClaim = new Claim("ActivtationTokenExpiredAt", expiresAt.ToUniversalTime().Ticks.ToString());
await _userManager.AddClaimAsync(user, tokenExpiredAtClaim);
}
add a helper method to check whether the token has expired in the ConfirmEmail.cshtml.cs :
private async Task<bool> TokenExpiredValidate(IdentityUser user) {
var claims = (await _userManager.GetClaimsAsync(user))
.Where(c => c.Type == "ActivtationTokenExpiredAt");
var expiredAt = claims.FirstOrDefault()?.Value;
bool expired = true; // default value
if (expiredAt != null)
{
var expires = Convert.ToInt64(expiredAt);
var now = DateTime.Now.Ticks;
expired= now <= expires? false : true;
}
else {
expired = false;
}
// clear claims
await _userManager.RemoveClaimsAsync(user, claims);
return expired;
}
invoke AddTokenExpirationInfo when there's a registeration request :
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
var user = new IdentityUser { UserName = Input.Email, Email = Input.Email };
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { userId = user.Id, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
///////////////// invoke here ////////////////////
AddTokenExpirationInfo(user);
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
// If we got this far, something failed, redisplay form
return Page();
}
invoke TokenExpiredValidate in your ConfirmEmail.cshtml.cs:
public async Task<IActionResult> OnGetAsync(string userId, string code)
{
if (userId == null || code == null)
{
return RedirectToPage("/Index");
}
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return NotFound($"Unable to load user with ID '{userId}'.");
}
var result = await _userManager.ConfirmEmailAsync(user, code);
if (!result.Succeeded)
{
throw new InvalidOperationException($"Error confirming email for user with ID '{userId}':");
}
if (await TokenExpiredValidate(user))
throw new InvalidOperationException($"Token has alread expired '{userId}':");
return Page();
}
When a user registers , there' will be a record in the AspNetUserClaims table :
When a user confirmed successfully , the record will be removed . Just as a reminder , a more robust method is to use a background service to clear the expired record .

Categories

Resources