The code to register a user works locally against the production DB, but for some reason, on the remote host, the user isn't added and the Register view is being returned (the delivered Register view, not the one I'm using). It doesn't appear that any errors are being issued, as I've tried to return Content any result errors from the UserManager.CreateAsync task. I'll post my code below, but I don't know if it will help. What could be keeping it from working on the remote host vs. working correctly when executed locally?
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
//await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
var identity = new IdentityManager();
if (!identity.RoleExists("profile"))
{
identity.CreateRole("profile");
}
bool userAdded = identity.AddUserToRole(user.Id, "profile");
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(user.Id,
"Confirm your account for Site",
"Please confirm your account by clicking this link: <a href=\""
+ callbackUrl + "\">link</a>");
return View("ConfirmationEmailSent");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View("Login", model);
}
Okay, it's due to the host's add trailing slash rule. It must be clashing with how I configured routing.
Related
I have a new asp.net MVC core 6 application .try to authenticate users ( not by using Identity scaffolding ) .. however the the SignInmanger is always return False
Login function
programe.cs
Full code snippet for login :
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginVM loginvm)
{ // this wil return view model
if (!ModelState.IsValid)
{
return View(loginvm);
}
var user = await _userManager.FindByEmailAsync(loginvm.Username);
if (user != null)
{
// if we have user let us check the password
var checkpsssword = await _userManager.CheckPasswordAsync(user, loginvm.Password);
if (checkpsssword)
{
var letUserLoginIn = await _signInManager.PasswordSignInAsync(user, loginvm.Password, false, false);
if (letUserLoginIn.Succeeded)
{
var tempo = User.Identity.IsAuthenticated;
var isok = _signInManager.IsSignedIn(User);
ViewBag.tempo=tempo;
ViewBag.isok = isok;
return RedirectToAction("index", "Movie");
}
ModelState.AddModelError("Error","can login innnnn");
TempData["Error"] = "Password is not correct! !";
return View(loginvm);
}
else
{
// password wrong
TempData["Error"] = "Password is not correct! !";
}
}
TempData["Error"] = "no user found ya mozznoz!";
return View(loginvm);//STRONGLY TYPED VIEW
}
One part #Kevin have mentioned above, and another part was the missing of authentication mechanism register.
It should be something like builder.Services.AddAuthentication(opts => opts.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
AddAuthentication part add all the necessary middlewares and config to setup authentication process. Here, we specify DefaultScheme as CookieAuthenticationDefaults.AuthenticationScheme
AddCookie tell asp.net Core that we want to store the login information in cookie, therefore, a response that tell client to save a cookie with pre-defined information was sent(and the name for that authentication mechanism of choice was default to CookieAuthenticationDefaults.AuthenticationScheme).
For every subsequent requests, the cookie was included then server know, we already logged in
I want to validate and use my database users in identity server 4.Here is my customized login code in Account Controller.
if (ModelState.IsValid)
{
// validate username/password against my user repository class, and get the user's info
var user = await _users.FindAsync(model.Username, model.Password);
if(user != null)
{
await _events.RaiseAsync(new UserLoginSuccessEvent(user.LoginId, user.SubjectId, user.FullName, clientId: context?.Client.ClientId));
AuthenticationProperties props = null;
if (AccountOptions.AllowRememberLogin && model.RememberLogin)
{
props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
};
};
var isuser = new IdentityServerUser(user.SubjectId)
{
DisplayName = user.FullName,
AdditionalClaims=user.Claims.ToList(),
AuthenticationTime=DateTime.UtcNow
};
await HttpContext.SignInAsync(isuser, props);
if (context != null)
{
if (context.IsNativeClient())
{
return this.LoadingPage("Redirect", model.ReturnUrl);
}
return Redirect(model.ReturnUrl);
}
// request for a local page
if (Url.IsLocalUrl(model.ReturnUrl))
{
return Redirect(model.ReturnUrl);
}
else if (string.IsNullOrEmpty(model.ReturnUrl))
{
return Redirect("~/");
}
else
{
// user might have clicked on a malicious link - should be logged
throw new Exception("invalid return URL");
}
}
await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials", clientId:context?.Client.ClientId));
ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage);
}
I have created the Profile service class and configured the startup class like this
services.AddIdentityServer()
.AddInMemoryClients(InMemoryConfig.GetClients())
.AddProfileService<ProfileService>()
.AddInMemoryApiScopes(InMemoryConfig.GetApiScopes())
.AddInMemoryIdentityResources(InMemoryConfig.GetIdentityResources())
.AddDeveloperSigningCredential();
services.AddTransient<IProfileService, ProfileService>();
services.AddTransient<IUserRepository, UserRepository>();
But the user session never starts, takes me back to the login page and Profile service never gets called.
Where am I going wrong?
Sorry, guys my bad. This problem arises when SSL is disabled for my identity server 4 application. I enabled SSL for my application and everything started working fine.
We have a requirement to authenticate users in IdentityServer4 against an external API. The scenario works like this:
User visits a Javascript client application and clicks the login button to redirect to IdentityServer login page (exact same client as provided in the docs here
User enters their username (email) and password
IdentityServer4 connects to an external API to verify credentials
User is redirected back to the JavaScript application
The above process works perfect when using the TestUsers provided in the QuickStarts. However, when an API is used, the login page resets and does not redirect the user back to the JavaScript client. The only change is the below code and a custom implementation of IProfileService.
Below is the custom code in the login action (showing only the relevant part):
var apiClient = _httpClientFactory.CreateClient("API");
var request = new HttpRequestMessage(HttpMethod.Post, "/api/auth");
var loginModel = new LoginModel
{
Email = model.Email,
Password = model.Password
};
var content = new StringContent(JsonConvert.SerializeObject(loginModel),
Encoding.UTF8, "application/json");
request.Content = content;
HttpResponseMessage result = await apiClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
var loginStatus = JsonConvert.DeserializeObject<ApiLoginStatus>(
await result.Content.ReadAsStringAsync());
if (loginStatus.LoginSuccess)
{
await _events.RaiseAsync(new UserLoginSuccessEvent(model.Email, model.Email, loginStatus.Name, clientId: context?.ClientId));
AuthenticationProperties props = null;
if (AccountOptions.AllowRememberLogin && model.RememberLogin)
{
props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
};
};
var user = new IdentityServerUser(loginStatus.SubjectId)
{
DisplayName = loginStatus.Name
};
await HttpContext.SignInAsync(user, props);
if (context != null)
{
if (await _clientStore.IsPkceClientAsync(context.ClientId))
{
return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl });
}
return Redirect(model.ReturnUrl);
}
The code actually hits the return View() path, but for some reason it resets and the login page is shown again.
Code in Startup.cs:
var builder = services.AddIdentityServer()
.AddInMemoryIdentityResources(Config.Ids)
.AddInMemoryApiResources(Config.Apis)
.AddInMemoryClients(Config.Clients)
.AddProfileService<ProfileService>()
.AddDeveloperSigningCredential();
Code in ProfileService.cs:
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var profile = await GetUserProfile(context.Subject.GetSubjectId());
var claims = new List<Claim>
{
new Claim(ClaimTypes.Email, profile.Email),
new Claim(ClaimTypes.Name, profile.Name)
};
context.IssuedClaims.AddRange(claims);
}
public async Task IsActiveAsync(IsActiveContext context)
{
var profile = await GetUserProfile(context.Subject.GetSubjectId());
context.IsActive = (profile != null);
}
There are multiple sources online that show how to user a custom store for authentication, but they all seem to use ResourceOwnerPasswordValidator. If someone could point out what is missing here, it would help greatly. Thanks.
So the issue turned out to be very simple. We had missed removing the builder.AddTestUsers(TestUsers.Users) line when setting up IdentityServer in Startup.cs.
Looking at the code here, it turned out that this line was overriding our profile service with the test users profile service. Removing that line solved the problem.
I want to implement email confirmation to my registration process,
I'm using Angular 7 as client, I tried to implement it myself through tutorials but most of them for MVC...
I want to know what do I need exactly and how its should work...
here is my code:
ASP core:
[HttpPost]
[Route("Register")]
public async Task<object> PostAppUser(AppUserModel model)
{
var result = await _userService.Register(model);
if (result != null)
return Ok(result);
else
return BadRequest(new { message = "Register failed! Please try again later" });
}
public async Task<object> Register(AppUserModel model)
{
if (model.SpecialCode == _appSettings.Special_Code)
model.Role = "admin";
else
model.Role = "customer";
var appUser = new AppUser()
{
UserName = model.UserName,
Email = model.Email,
FullName = model.FullName,
};
try
{
var result = await _userManager.CreateAsync(appUser, model.Password);
await _userManager.AddToRoleAsync(appUser, model.Role);
return result;
}
catch (Exception ex)
{
throw ex;
}
}
Startup:
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddScoped<IUserService, UserService>();
services.AddDbContext<AuthContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("IdentityConnection")));
services.AddDefaultIdentity<AppUser>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<AuthContext>();
Angular:
onSubmit(form: NgForm, userName: string) {
this.userService.login(form.value).subscribe(
(res: any) => {
localStorage.setItem('token', res.token);
this.router.navigateByUrl('/home');
this.toastr.success('Welcome ' + userName + '!' , 'Authentication granted');
},
(err: any) => {
if (err.status === 400) {
this.toastr.error('Incorrect User name of Password!', 'Authentication failed');
form.reset();
} else {
this.toastr.error('Our servers is down at the moment', 'Try again later');
form.reset();
}
}
If you want to implement the email confirmation into your application, you should:
Add instruction to Send email confirmation (with the link) into your registration method.
var token = await _userManager.GenerateEmailConfirmationTokenAsync(YourUserEntity);
It will generate a token to confirm the email of your user.
- Create a callback link for your angular application, for example:
var callBackUrl = "http://localhost:4200/Profile/ConfirmEmail?userId="+usrIdentity.Id + "&token=" + code;
And then , send the callBackUrl to the user email.
Into your angular application, you should create a route for the callBackUrl, once the user click on the link sent on the email.
You should send a request to your WebApi with the userId and the token (On The link) to confirm the user email and check if the token is valid, to do that you must implement a new post method on your controller named (ConfirmUserEmail), and bellow it, implement The following instruction:
var dbUserResult = await _userManager.FindByEmailAsync(user.IdIdentityNavigation.Email);
if (dbUserResult != null)
{
var result = await _userManager.ConfirmEmailAsync(dbUserResult, confirmUserEntity.token);
if (result.Succeeded)
{ \* Implement Your business logic here /* }
}
We are trying to use Microsoft Account Authentication in an ASP.Net 5 project. We don't require local authentication and don't require user names.
In the ASP.Net 5 Template for a web application, after signing in with an external provider, control returns to ExternalLoginCallback in the AccountController.
If the user is not registered locally ExternalLoginCallback returns the user to a registration screen. I have attempted to modify ExternalLoginCallback to automatically register the new user as below.
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null)
{
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
return RedirectToAction(nameof(Login));
}
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
if (result.Succeeded)
{
_logger.LogInformation(5, "User logged in with {Name} provider.", info.LoginProvider);
return RedirectToLocal(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl });
}
if (result.IsLockedOut)
{
return View("Lockout");
}
else
{
// If the user does not have an account, then ask the user to create an account.
//ViewData["ReturnUrl"] = returnUrl;
//ViewData["LoginProvider"] = info.LoginProvider;
//var email = info.ExternalPrincipal.FindFirstValue(ClaimTypes.Email);
//return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email });
// The user has not previously logged in with an external provider. Create a new user.
CreateUser(info);
return RedirectToLocal(returnUrl);
}
}
CreateUser implements code copied from ExternalLoginConfirmation as it appears in the ASP.Net 5 Template for a web application.
private async void CreateUser(ExternalLoginInfo info)
{
var email = info.ExternalPrincipal.FindFirstValue(ClaimTypes.Email);
var user = new ApplicationUser { UserName = email, Email = email };
var result = await _userManager.CreateAsync(user);
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation(6, "User created an account using {Name} provider.", info.LoginProvider);
}
}
AddErrors(result);
}
An error is thrown on the line
var result = await _userManager.CreateAsync(user);
The error is
System.ObjectDisposedException was unhandled Message: An unhandled exception of type 'System.ObjectDisposedException' occurred in mscorlib.dll. Additional information: Cannot access a disposed object.
I have rebooted my machine just in case it was 'just one of those things', but the error recurs.
Using an async void method is rarely a good idea and is the best way to introduce weird race conditions like the one you're experiencing: since your CreateUser method doesn't return a task, it can't be awaited by ExternalLoginCallback and the request completes before CreateUser has the time to execute the database operations (when the request completes, the DI system call Dispose on scoped dependencies like your EF context).
Update your CreateUser method to return a Task and await it from ExternalLoginCallback and it should work:
await CreateUser(info);