Redirect after login in mvc5 - asp.net-mvc

In MVC, when writing a project, it writes a login scenario, the login screen, and the registry, and everything.
I created the admin page, but before you go to the page if I have not created a cookie, send me the login page. I want to do it after you log in. I do not know how to redirect to the admin page after logging in.
You have the code itself that writes the viewbag.retutnurl while you are building a project, but I do not know what the controller is.
Now I'm not sure where the value comes from.
If anyone knows exactly and done, please advise.

In the default generated method change the first case, which executes when the login is successful, and add you custom redirection here like this:
switch (result)
{
case SignInStatus.Success:
return RedirectToAction("Index", "Admin", null); // new code
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = false });
case SignInStatus.Failure:
default:
// If the user does not have an account, then prompt the user to create an account
ViewBag.ReturnUrl = returnUrl;
ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email });
}
And in your admin class you should put the [Authorize] data annotation
//admin controller
[Authorize]
public ActionResult Index()
{
return View();
}

You can try any of these two ways to deal with it.
public ActionResult Index() {
return RedirectToAction("AdminAction");
//Or you can try this
return RedirectToAction("whateverAction", "whateverController");
}

Related

Client Login asp.net mvc error model , How return error model to Index View

I use identity in mvc project for Authentication, I have two kind of user,
the first one is Admin user has an Login page , and the second is Client user
the Client user login form used in the layout in model , I made partial view
for Client Login and impeded it in the Layout, When I press Submit button and the data dose not exist( there is no user ) it Should return model message error for user like this
ModelState.AddModelError("", "Invalid login attempt.");
return PartialView("~/Views/Account/_LoginPartial.cshtml", model);
the problem is it return me to Partial view that has no layout
How can I return model Error to page that like Home/Index and view model error
this is the Action I post
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ClientLogin(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return PartialView("~/Views/Account/_LoginPartial.cshtml", model);
}
var user = await UserManager.FindByNameAsync(model.Email);
if (user == null)
{
ModelState.AddModelError("", "Invalid login attempt.");
return PartialView("~/Views/Account/_LoginPartial.cshtml", model);
//return PartialView("",model);
}
if (!await UserManager.IsEmailConfirmedAsync(user.Id))
{
ModelState.AddModelError("", "You need to confirm your email.");
return View(model);
}
var lang = RouteData.Values["lang"].ToString().ToLower();
if (string.IsNullOrEmpty(returnUrl))
returnUrl = "/" + lang + "/ControlPanel/Home";
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
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("", "Invalid login attempt.");
return View(model);
}
}

ASP Identity New user with externallogin

I'm refactoring an old website to use OWIN and claims with forms and external loigins with ASP Identity 2.
I have a question around the proper way to create a new user with external login.
I'm using the MVC scaffolding code but have a custom UserStore, UserManager and signinmanager and everything is mostly working.
The account control has in the ExternalLoginCallback method has a case with a comment to redirect to ExternalLoginConformation when a user is not found, but I am unsure where to short circuit the login logic so it wont throw an exception.
var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = false });
case SignInStatus.Failure:
default:
// If the user does not have an account, then prompt the user to create an account
ViewBag.ReturnUrl = returnUrl;
ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email });
}
The SignInManager.ExternalSignInAsync method flows through the userstore and usermanager and signinmanager in the following mannor. Where and how is the best way to short the logic to get the result as a failure?
AccountController : ExternalLogin(Provider, returnURL)
My Owin Middleware
AccountController : ExternalLoginCallBack
AuthenticationManager returns loginInfo with all the details
UserManager : Task FindAsync(UserLoginInfo)
Calls UserStore : Task FindAsync(UserLoginInfo)
UserLoginInfo has Provider and Key and this is where i find there is NO user in the system. No matter what Task i return it wont stop the flow.
...
UserStore : Lockout and other misc stuff - Needs a User object even if empty
...
SignInManager : Task SignInAsyn(User, Persistent, Remeber)- User object is empty
SignInManager : Task CreateUserIdentityAsync(User)- User object is empty
User : Task GenerateUserIdentityAsync(UserManager manager)
UserManager : CreateIdentityAsync(User, Auth type) user is empty and auth type = "external cookie" This throws a NULL Exception.
Solution Found. I can return a null task from the UserManager FindAsync method and it will result in a failure result.
return Task.FromResult<MyUser>(null);
Solution Found. I can return a null task from the UserManager FindAsync method and it will result in a failure result.
return Task.FromResult<MyUser>(null);

ASP.NET MVC Authentication using External Provider - Google/Facebook

I am using Microsoft/Google/Facebook Authentication in my asp.net MVC application, which authenticates user & redirect users to my site. This works fine.
Issue : Anyone having a Microsoft/Google/Facebook account can able to sign in to my application. I should only allow users those who are registered/mapped in our database ie if a person purchased a licences only he should able to login using external provider.
Example :
1) User1 has a Microsoft/Google account & user1 is a valid user in our database. So we can allow him to see the content from our site.
2) user2 has a microsoft/Google account, but he isn't valid user in our db. He shouldn't able to gain access to our site.
How can I achieve this in ASP.NET MVC. I am using client id & client secret key from external providers.
sample code from startup class
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
{
ClientId = "",
ClientSecret = ""
});
In AccountController.cs (default code if you haven't changed it)
[AllowAnonymous]
public ActionResult ExternalLoginCallback(string returnUrl)
{
AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
if (!result.IsSuccessful)
{
return RedirectToAction("ExternalLoginFailure");
}
if(EmailNotRegistered(result.ExtraData["userid"]))
{
FormsAuthentication.SignOut();
return RedirectToAction("ExternalLoginFailure");
}
var bresult = OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, createPersistentCookie: false);
if (bresult)
{
return RedirectToLocal(returnUrl);
}
// etc...
}
You will need to write the function bool EmailNotRegistered(string email) and do the logic where it checks the local database. There might be something already available in the Membership API to check but I don't know right now.
By the way, each provider is different so the ExtraData field might be "email" or something else - use the debugger to find out!
Here is my AccountController
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
// Sign in the user with this external login provider if the user already has a login
var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = false });
case SignInStatus.Failure:
default:
// If the user does not have an account, then prompt the user to create an account
ViewBag.ReturnUrl = returnUrl;
ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email });
}
}

How to persist data in MVC 5 project

I am wanting to develop a MVC 5 project that can provide the same look and feel in terms of styling, header, menu and footer etc, from the calling website.
So for example, the calling website will post a header, menu and footer, or a encrypted key for database lookup, so the MVC project can take this and create a view that will look like the calling website.
This works fine, and I could use TempData and TempData.Keep() to persist the page styling throughout all the requests.
However, now I want to allow the user on the MVC site to login / authenticate, so that they can see some privileged pages.
The problem I have is I can let the user login, and still persist the styling using TempData. The problem comes when the user session expires, and they click, I lose the styling information.
At the moment I have this controller and action which deals with the login;
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(User user)
{
TempData["User"] = null;
if (ModelState.IsValid)
{
if (user.IsValid(user.Email, user.Password, Request.UserHostAddress))
{
string userData = user.Email;
HttpCookie authCookie = FormsAuthentication.GetAuthCookie(user.Email, false);
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(authCookie.Value);
FormsAuthenticationTicket newTicket = new FormsAuthenticationTicket(ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration, ticket.IsPersistent, userData);
authCookie.Value = FormsAuthentication.Encrypt(newTicket);
Response.Cookies.Add(authCookie);
TempData.Keep("CmsContent");
return RedirectToAction("Index", "ManageAccount");
}
else
{
TempData["User"] = user;
TempData.Keep("CmsContent");
return RedirectToAction("Index", "Home");
}
}
else
{
user.Message = "Invalid email/password";
TempData["User"] = user;
TempData.Keep("CmsContent");
return RedirectToAction("Index", "Home");
}
}
Currently the styling information is being persisted using TempData.Keep("CmsContent"), but when the session expires it is lost.
How best can I deal with this sort of issue.

prevent users without confirmed email from logging in ASP.Net MVC with Identity 2

In microsoft Identity 2 there is ability to users can confirm there email addresses I downloaded Identity 2 sample project from here in this project there isn't any difference between users confirmed their emails and who doesn't I want to people how don't confirmed their emails can't login this is what I tried :
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: true);
switch (result)
{
case SignInStatus.Success:
{
var user = await UserManager.FindByNameAsync(model.Email);
if (user != null)
{
if (!await UserManager.IsEmailConfirmedAsync(user.Id))
{
//first I tried this.
//return LogOff();
HttpContext.Server.TransferRequest("~/Account/LogOff");
return RedirectToAction("Login");
}
}
return RedirectToLocal(returnUrl);
}
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
I tried to force user to Logoff by calling LogOff() action method but It didn't work and user remain authenticated .then I tried to use Server.TransferRequest() but I don't know why it did the job but it redirects users to login page with returnUrl="Account/Logoff"
so after they confirmed their email and tried to login they get logoff I get really confused!!
this is my LogOff() action method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
AuthenticationManager.SignOut();
return RedirectToAction("About", "Home");
}
I have googled it for days without any luck !!!!
Maybe its a little late but I hope it may help others.
Add this
var userid = UserManager.FindByEmail(model.Email).Id;
if (!UserManager.IsEmailConfirmed(userid))
{
return View("EmailNotConfirmed");
}
before
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
The first block of code just checks if the email in the model exists in the database and gets it's id to check if it is not confirmed and if so returns a view to the user wich says so and if it is confirmed just lets the user sign in.
And delete your changes to the result switch like this
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
Instead of moving to another page, why not finish this one and redirect to the right action / view:
if (!await UserManager.IsEmailConfirmedAsync(user.Id))
{
return RedirectToAction("ConfirmEmailAddress", new { ReturnUrl = returnUrl });
}
You do need an action (and possibly a view) with the name ConfirmEmailAddress though.
There is a solution, which may not be the best approach, but it works. First let me try to clarify why your approach did not work.
In one of the comments it is mentioned, the AuthenticationManager uses cookies. In order to update a cookie you need to send it to the client, using another page. That is why TransferRequest is not going to work.
How to handle the emailverification? The strategy I used:
1) On SignInStatus.Success this means that the user is logged in.
2) When email is not confirmed: send an email to the used e-mailaddress. This is safe since the user already signed in. We are just blocking further access until the e-mail is verified. For each time a user tries to login without having validated the email, a new email (with the same link) is sent. This could be limited by keeping track of the number of sent emails.
3) We cannot use LogOff: this is HttpPost and uses a ValidateAntiForgeryToken.
4) Redirect to a page (HttpGet, authorization required) that displays the message that an e-mail has been sent. On entering sign out the user.
5) For other validation errors, redirect to another method to sign out (HttpGet, authorization required). No view needed, redirect to the login page.
In code: update the code in AccountController.Login to:
case SignInStatus.Success:
{
var currentUser = UserManager.FindByNameAsync(model.Email);
if (!await UserManager.IsEmailConfirmedAsync(currentUser.Id))
{
// Send email
var code = await UserManager.GenerateEmailConfirmationTokenAsync(currentUser.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = currentUser.Id, code = code}, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(currentUser.Id, "Confirm your account", string.Format("Please confirm your account by clicking this link: link", callbackUrl));
// Show message
return RedirectToAction("DisplayEmail");
}
// Some validation
if (true)
{
return RedirectToAction("SilentLogOff");
}
return RedirectToLocal(returnUrl);
}
Add methods to AccountController:
// GET: /Account/SilentLogOff
[HttpGet]
[Authorize]
public ActionResult SilentLogOff()
{
// Sign out and redirect to Login
AuthenticationManager.SignOut();
return RedirectToAction("Login");
}
// GET: /Account/DisplayEmail
[HttpGet]
[Authorize]
public ActionResult DisplayEmail()
{
// Sign out and show DisplayEmail view
AuthenticationManager.SignOut();
return View();
}
DisplayEmail.cshtml
#{
ViewBag.Title = "Verify e-mail";
}
<h2>#ViewBag.Title.</h2>
<p class="text-info">
Please check your email and confirm your email address.
</p>
You'll notice that the user cannot reach other pages until email is verified. And we are able to use the features of the SignInManager.
There is one possible problem (that I can think of) with this approach, the user is logged in for the time that the email is sent and the user is being redirected to the DisplayMessage view. This may not be a real problem, but it shows that we are not preventing the user from logging in, only denying further access after logging in by automatically logging out the user.
=== Update ====
Please note that exceptions have to be handled properly. The user is granted access and then access is revoked in this scenario. But in case an exception occurs before signing out and this exception was not catched, the user remains logged in.
An exception can occur when the mailserver is not available or the credentials are empty or invalid.
===============
I would let the admin create the user without any password. The email with link should go to the user. The user then is directed to SetPassword page to set new password. This way no one can access the user account unless he confirms and sets the password.
Call CreateAsync without the password
var adminresult = await UserManager.CreateAsync(user);
Redirect admin to new custom view saying something like "Email is sent to user"
#{
ViewBag.Title = "New User created and Email is Sent";
}
<h2>#ViewBag.Title.</h2>
<p class="text-info">
The New User has to follow the instructions to complete the user creation process.
</p>
<p class="text-danger">
Please change this code to register an email service in IdentityConfig to send an email.
</p>
The answer by #INFINITY_18 may cause Object reference not set to an instance of an object error if the email does not exist in the data store at all. And why not return the Login view with model error in this case, too?
I would suggest the following:
var userid = UserManager.FindByEmail(model.Email)?.Id;
if (string.IsNullOrEmpty(userid) || !UserManager.IsEmailConfirmed(userid)))
{
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
Require email confirmation
It's a best practice to confirm the email of a new user registration to verify they are not impersonating someone else (that is, they haven't registered with someone else's email). Suppose you had a discussion forum, and you wanted to prevent "yli#example.com" from registering as "nolivetto#contoso.com." Without email confirmation, "nolivetto#contoso.com" could get unwanted email from your app. Suppose the user accidentally registered as "ylo#example.com" and hadn't noticed the misspelling of "yli," they wouldn't be able to use password recovery because the app doesn't have their correct email. Email confirmation provides only limited protection from bots and doesn't provide protection from determined spammers who have many working email aliases they can use to register.
You generally want to prevent new users from posting any data to your web site before they have a confirmed email.
Update ConfigureServices to require a confirmed email:
​
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc();
services.Configure<AuthMessageSenderOptions>(Configuration);
}

Resources