I have this following mvc application
The problem is when Im trying to assign profile values:
// Attempt to register the user
MembershipCreateStatus createStatus = MembershipService.CreateUser(model.Email, model.Password);
if (createStatus == MembershipCreateStatus.Success)
{
//Adding role
MembershipService.AddDefaultRole(model.Email);
FormsService.SignIn(model.Email, false /* createPersistentCookie */);
//Add other initial profile data
HttpContext.Profile["FirstName"] = model.FirstName; //PROBLEM
HttpContext.Profile["LastName"] = model.LastName; //PROBLEM
return RedirectToAction("List", new { area = "", controller = "Requests" });
}
else
{
ModelState.AddModelError("", AccountValidation.ErrorCodeToString(createStatus));
}
Inside FormsService.SignIn(model.Email, false):
public void SignIn(string email, bool createPersistentCookie)
{
if (String.IsNullOrEmpty(email)) throw new ArgumentException("Value cannot be null or empty.", "email");
FormsAuthentication.SetAuthCookie(email, createPersistentCookie);
}
How come after calling FormsAuthentication.SetAuthCookie, User isn't yet authenticated?
I'm getting an error b.c. im trying to assign some profile value to anonymous user .
Any idea?
When you set a cookie, it's added to the Response, but the IsAuthenticated bool is set from the Request. After setting the authentication and setting up your session variables, you should redirect to another page, like the home page or the original request.
Related
I'm confused by the example in the documentation here that describes how to add claims using IUserClaimsPrincipalFactory.
The sample code shows how to extend the ApplicationUser class:
public class ApplicationUser : IdentityUser
{
public bool IsAdmin { get; set; }
}
...and then implement a UserClaimsPrincipalFactory that tests that property to determine which claims to add:
if (user.IsAdmin)
{
claims.Add(new Claim(JwtClaimTypes.Role, "admin"));
}
else
{
claims.Add(new Claim(JwtClaimTypes.Role, "user"));
}
It's not stated, but I think the implication is that something else (not shown) will set the IsAdmin property for a user in the database. I think they could have made that clear. (Also, it's disappointing that the example uses roles when there's so much confusion around roles versus claims, but I digress...)
Anyway, we have added some "role" claims to the user based on the value of that new IsAdmin property. So far, so good. What I don't understand is the next bit:
The additional claim can then be used in the app. In a Razor Page, the IAuthorizationService instance can be used to access the claim value.
Sounds like the Razor page is going to access our claim then - but here's the code:
#if ((await AuthorizationService.AuthorizeAsync(User, "IsAdmin")).Succeeded)
{
...
}
Is that really accessing the claim? It looks to me like it's accessing the IsAdmin property of the user instead. I don't see how the claim we added is referenced at all - unless there's something else that's not being explained.
That overload of AuthorizeAsync describes the last parameter as 'policyName'. Are we meant to assume that there's a policy called "IsAdmin" that checks for our new role claim?
What a terrible piece of documentation this is - and I'm ignoring the fact that it's also in the wrong place.
It's not stated, but I think the implication is that something else (not shown) will set the IsAdmin property for a user in the database.
You can set the IsAdmin where you want,For example you can set it when register.Here is a demo:
Input Model in register:
public class InputModel
{
...
public bool IsAdmin { get; set; }
}
Post handler:
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email ,IsAdmin=Input.IsAdmin};
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);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = user.Id, code = code, returnUrl = returnUrl },
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>.");
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl });
}
else
{
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();
}
Is that really accessing the claim? It looks to me like it's accessing the IsAdmin property of the user instead. I don't see how the claim we added is referenced at all - unless there's something else that's not being explained.
That overload of AuthorizeAsync describes the last parameter as 'policyName'. Are we meant to assume that there's a policy called "IsAdmin" that checks for our new role claim?
IsAdmin is a ploicy name in the code,you need to add a policy which name is IsAdmin,and check new role claim in it.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddAuthorization(options =>
{
options.AddPolicy("IsAdmin", policy => policy.RequireClaim("role", "admin"));
});
}
result:
Im using aspnetboilerplate (MVC) and wanted to implement a forgot password feature to allow the user to reset their own passwords using a link on the login screen.
I imagine this to work by generating a password reset code which is then emailed to the user.The user follows the link and is taken to a screen allowing them to reset the password.
Im stuck at the initial stage. i started with a copy of the login action after noticing that when attempting to log in the user object was returned. From here i attempt to set a password reset code.
[HttpPost]
[UnitOfWork]
public virtual async Task<JsonResult> ForgotPassword(ForgotPasswordViewModel forgotPasswordModel, string returnUrl = "", string returnUrlHash = "")
{
returnUrl = NormalizeReturnUrl(returnUrl);
if (!string.IsNullOrWhiteSpace(returnUrlHash))
{
returnUrl = returnUrl + returnUrlHash;
}
var loginResult = await _logInManager.LoginAsync(forgotPasswordModel.UsernameOrEmailAddress, "ForgotPassword", GetTenancyNameOrNull());
loginResult.User.SetNewPasswordResetCode();
switch (loginResult.Result)
{
case AbpLoginResultType.Success:
return Json(loginResult);
default:
throw _abpLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt(loginResult.Result, forgotPasswordModel.UsernameOrEmailAddress, GetTenancyNameOrNull());
}
}
Checking the AbpUser table after the
loginResult.User.SetNewPasswordResetCode();
i cannot see any password reset code for the user, they are all null.
Could someone point me in the right direction.
Thanks in advance
Thanks to answer below for being correct, just for completion below is exactly what worked. Obviously ignore the json return at the end
public virtual async Task<JsonResult> ForgotPassword(ForgotPasswordViewModel forgotPasswordModel, string returnUrl = "", string returnUrlHash = "")
{
//var user = await GetUserByChecking(emailAddress);
var user = await _userManager.FindByEmailAsync(forgotPasswordModel.UsernameOrEmailAddress);
if (user == null)
{
throw new UserFriendlyException("User not found!");
}
user.SetNewPasswordResetCode();
//Send an email to user with the below password reset code
/* Uri.EscapeDataString(user.PasswordResetCode) */
return Json("");
}
public class AccountAppService: IAccountAppService
{
public UserManager UserManager {get; set; }
public async Task SendPasswordResetCode(string emailAddress)
{
var user = await UserManager.FindByEmailAsync(emailAddress);
if (user == null)
{
throw new UserFriendlyException("User not found!");
}
user.SetNewPasswordResetCode();
//Send an email to user with the below password reset code
/* Uri.EscapeDataString(user.PasswordResetCode) */
}
}
I'm working on writing fully customized ASP.NET Identity for my WebAPi.
I have rewritten my own derived OAuthAuthorizationServerProvider in this way:
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// Check User availability ...
//ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
// if i couldn't found user in my DataBase ...
//if (user == null)
//{
//context.SetError("invalid_grant", "The user name or password is incorrect.");
// return;
//}
context.Validated();
}
}
GrantResourceOwnerCredentials just returns an invalid_grant error for each calls. i want to handle it but, i don't know how.
ValidateClientAuthentication is where you would do your authentication checks and this is where you throw errors if anything doesn't match.
move your code there and do the checks before you call context.Validated(). You only call the Validate method once you make sure everything is validated correctly.
here is an example of such an implementation I did a while back:
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientId;
string clientSecret;
//first try to get the client details from the Authorization Basic header
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
{
//no details in the Authorization Header so try to find matching post values
context.TryGetFormCredentials(out clientId, out clientSecret);
}
if (string.IsNullOrWhiteSpace(clientId) || string.IsNullOrWhiteSpace(clientSecret))
{
context.SetError("client_not_authorized", "invalid client details");
return Task.FromResult<object>(null);
}
var dataLayer = new RepoManager(new DataLayerDapper()).DataLayer;
var audienceDto = dataLayer.GetAudience(clientId);
if (audienceDto == null || !clientSecret.Equals(audienceDto.Secret))
{
context.SetError("unauthorized_client", "unauthorized client");
return Task.FromResult<object>(null);
}
context.Validated();
return Task.FromResult<object>(null);
}
Notice how the checks happen in order and certain errors are raised with some appropriate errors.
This code takes a client id and client secret from an authorization header but you can easily drop all that and replace it with your own checks and database calls.
The important part is that this is where you deal with stuff like this and this is where you set the errors so your clients know what's going on.
GrantResourceOwnerCredentials this is where you get once the call is properly authenticated, at which point you can start creating tokens, adding claims and creating the authentication ticket. This method does not get hit if the previous one fails to authenticate the request.
Here is a working example:
public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var identity = new ClaimsIdentity("JWT");
identity.AddClaim(new Claim("clientID", context.ClientId));
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"audience", context.ClientId
}
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
return Task.FromResult<object>(null);
}
Now, if you get an invalid grant error that usually happens because you either didn't set up the grant_type in your initial call or you set up the wrong value.
in my case I had to setup this:
"grant_type", "password"
I am implementing 2 factor authentication in WebApi, asp.net identity and OWIN. Every time I log in, I get SignInStatus = Success never reaches to SignInStatus = RequiresVerification though user TwoFactorAuthentication is enabled.
Below are some code snippets,
Startup.cs:
private void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
app.UseOAuthBearerTokens(OAuthOptions);
}
Action method for enabling two factor authentication,
[HttpPost]
public async Task<IHttpActionResult> EnableTwoFactorAuthentication()
{
var user = await this.AppUserManager.FindByIdAsync(User.Identity.GetUserId());
if (user != null)
{
IdentityResult result = await this.AppUserManager.SetTwoFactorEnabledAsync(User.Identity.GetUserId(), true);
await this.AppSignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
}
return Ok();
}
Please suggest a solution.
If you get stuck here, one way to solve the problem is to copy the methods from SignInManager directly into your code and call those instead so you can step through the methods and see why you are getting the wrong status. For me the problem ended up being that I instantiated my UserManager with:
new MyUserManager()
instead of the right way:
HttpContext.GetOwinContext().Get<MyUserManager>()
I was using this as my template for setting it up:
https://github.com/adamtuliper/ASP.NET-Identity-Samples/tree/master/BasicTemplate%20-%20Two%20Factor/BasicTemplate
SignInManager return RequiresVerification if :
dbo.ASpnetUsers has for user set to true TwoFactorEnabled and EmailConfirmed and user email should be confirmed, email not be empty or null.
var result = SignInManager.PasswordSignIn(usernameIdentity, model.Password, model.RememberMe, shouldLockout: true);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", returnUrl);
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid username or password.");
return View(model);
}
Working on an MVC4 Application where users first fill in a form as the starting point. With this form there is a field to provide user email and a unique 9 digit number and other details. What I want to achieve is after submitting this form I want the user to be silently registered using the unique 9 digit number as username, and an auto-generated password hashed and saved as password in the membership(extended simplemembership) database. An email with password will be sent to the user afterwards. I will be grateful for any hints or help in this regards.
After the form is filled, I make the following redirection
return RedirectToAction("AutoRegister", new RouteValueDictionary(new { controller = "Account", action = "AutoRegister", Uname= ViewBag.Uname, Uemail = ViewBag.Uemail }));
and I have the following code in Account Controller
[AllowAnonymous]
public ActionResult AutoRegister()
{
return View();
}
[AllowAnonymous]
[HttpPost]
public ActionResult AutoRegister(RegisterModel model,string Uname, string Uemail)
{
//if (ModelState.IsValid)
if(Uemail!=null && Uname!=null)
{
string Upass = Membership.GeneratePassword(12, 1);
model.Email = Uemail;
model.UserName = Uname;
model.Password = Upass;
// Attempt to register the user
MembershipCreateStatus createStatus;
Membership.CreateUser(model.UserName, model.Password, model.Email, passwordQuestion: null, passwordAnswer: null, isApproved: false, providerUserKey: null, status: out createStatus);
if (createStatus == MembershipCreateStatus.Success)
{
FormsAuthentication.SetAuthCookie(model.UserName, createPersistentCookie: false);
return RedirectToAction("Welcome", "OnlineApplication");
}
else
{
ModelState.AddModelError("", ErrorCodeToString(createStatus));
}
}
I have also set the RouteConfig to accept the url. When I fill the form it redirects allright to a page with the url parameters populated but nothing happens. The user is not created. As for emailing of password I can fix that with no problems.
Could anyone assist on how I can commit the new user details in database