So I am brand new to the whole ASP.NET MVC 5 thing and I am creating my first mini application. I need to show the profile link only after a user has logged in. I have a profile controller and the link will redirect the user to the profile controller. Here is the code I have but unfortunately it is not working.
I am using the built-in ASPNet.Identity. I have only modified it to require an email address during signup. Here is the sample code I am using.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.Owin.Security;
using artisan.Models;
namespace artisan.Controllers
{
[Authorize]
public class AccountController : Controller
{
public AccountController()
: this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
{
}
public AccountController(UserManager<ApplicationUser> userManager)
{
UserManager = userManager;
}
public UserManager<ApplicationUser> UserManager { get; private set; }
public ActionResult Profile()
{
return View();
}
//
// GET: /Account/Login
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
So you are saying, after a user successfully logs in, you want to redirect them to some action located in another controller called ProfileController?
If that's what you're after than it's pretty simple. After you authenticated the user in your login method you simply throw a return RedirectToAction("Index", "Profile"); in there and you should be good to go. Here is an example that does just that. It's a little more convoluted but I commented everything so you can understand. There are more than a couple redirects in there for different reasons.
[AllowAnonymous]
[HttpPost]
public async Task<ActionResult> Login(Models.AccountViewModel vm, string returnUrl)
{
//first make sure they filled in all mandatory fields
if (ModelState.IsValid)
{
//try to find the user by the credentials they provided
var user = await UserManager.FindAsync(vm.LoginModel.Username, vm.LoginModel.Password);
//if user is null then they entered wrong credentials
if (user != null)
{
//if user has confirmed their email already
if (user.EmailConfirmed == true)
{
//attempt to sign in the user
await SignInAsync(user, vm.LoginModel.RememberMe);
//if the return url is empty then they clicked directly on login instead of trying to access
//an unauthorized area of the site which redirected them to the login.
if (!string.IsNullOrEmpty(returnUrl))
return RedirectToLocal(returnUrl);
//returnUrl was empty so user went to log in first
else
{
//lets check and see which roles this user is in so we can direct him to the right page
var rolesForUser = UserManager.GetRoles(user.Id);
//users can be in multiple roles but the first role dictates what they see after they sign in
switch (rolesForUser.First())
{
case "Normal_User":
return RedirectToAction("Feed", "Account");
default:
//user is not in any roles send him to the default screen
break;
}
}
}
//user has not confirmed their email address redirect to email confirmation
else
{
//resend confirmation
await SendConfirmationEmail(user.Id);
//redirect user to unconfirmed email account view
return RedirectToAction("UnconfirmedAccount", "Account", new { Email = user.Email, UserId = user.Id });
}
}
else
{
//add errors to the view letting the user know they entered wrong credentials. Code will fall through and return
//the view below with these errors
ModelState.AddModelError("", "Invalid username or password.");
}
}
// If we got this far then validation failed
return View(vm);
}
Related
To use my app, a user must go through the sign in process to be granted access to the various parts of the app. So regardless of the entrance to the app (whether they start at /home, or /somethingElse), I'd like the user to be authorized and given their permissions.
Their permissions come from the SignUpSignIn() function in the Account controller.
When a user tries to access /somethingElse, I have an [Authorize] attribute that sends them to the Azure B2C sign in. But after being "Authorized" the SignUpSignIn() function is never hit and their permissions aren't assigned to their session. So whatever location they end up at, doesn't work properly (
and likely errors).
How can I get a user to run through the SignUpSignIn() after being authorized?
This is how I'm Authorizing
namespace TaskWebApp.Controllers
{
[Authorize] //<---
public class SomethingElseController : Controller
{
// GET: SomethingElse
public ActionResult Index()
{
return View();
}
}
}
This is how a user gets their permissions.
public class AccountController : Controller
{
public async Task SignUpSignIn()
{
if (!Request.IsAuthenticated)
{ ///}
if (Request.IsAuthenticated)
{ //apply permissions to user's session }
}
}
I expect a user to go to /somethingelse, and be redirected to the Azure B2C SignIn page. Once they click "sign in" to run SignUpSignIn() from the Account controller and have their permissions assigned to their session.
After some research I found that this isn't a good method of reapplying the permissions via session.
Instead I have created something that looks like this:
namespace TaskWebApp.Controllers
{
[Authorize]
public class SomethingElseController : Controller
{
public async Task<ActionResult> Index(){
if(Session["foo"] == null){ //there is no session data
await SignUpSignIn("/somethingelse") //This goes to the account controller and applies permissions on the session
}
else
{
return View();
}
}
private async Task SignUpSignIn(string redirectLink = "")
{
var controller = DependencyResolver.Current.GetService<AccountController>();
controller.ControllerContext = new ControllerContext(Request.RequestContext, controller);
await controller.SignUpSignIn(redirectLink);
}
}
}
When a user logs in. I'm looking for a way to route a user to the correct area based on their role in identity.
I've tried:
You have the default controller that you can redirect to that area return RedirectToAction("Index", "Dashboard", new { area = "Admin" });,
but I have multiple roles.
Add a rewrite option in the StartUp Configure method. Which works in the beginning, but if you have a link to another area - it doesn't work.
On the default controller I created a view. Added some razor #if (User.Identity.IsAuthenticated && User.IsInRole("Admin")) else if(). Then used <text>and <script> tags in between the if statements to call a function that would redirect the user. It didn't provide an error, just didn't work. I figured it wouldn't, but... trying to think outside the box
Thanks ahead for the ideas!
If you are using asp.net core identity,in controller, you could directly use var isInRole = User.IsInRole("Admin") to check whether current user has an Admin role.
Or use UserManager to get current user and all his roles:
private readonly UserManager<IdentityUser> _userManager;
public HomeController(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
[Authorize(Roles = "Admin")]
public async Task<IActionResult> TestView()
{
var user = await _userManager.GetUserAsync(HttpContext.User);
var roles = await _userManager.GetRolesAsync(user);
var matchingvalues = roles.SingleOrDefault(stringToCheck => stringToCheck.Equals("Admin"));
if(matchingvalues != null)
{
return RedirectToAction("Index", "Dashboard", new { area = "Admin" });
}
return View();
}
I have a class called PasswordChangeChecker.csthat has a method that returns from the database whether or not a user has changed their password. The signature for that method is:
public bool IsPasswordChangedFromInitial(string IdOfUser)
where IdOfUser is the Id field from the Identity framework User. If it returns true, that means the change password page should not be displayed, otherwise, it should navigate to the change password form. Once the user successfully changes their password, the database flag gets set appropriately, and they should not be prompted for a password change again (unless manually forced to by an admin). How can I put this method in the RouteConfig.cs file, where currently I have:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace IdentityDevelopment
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
}
How can I add a conditional construct into the defaults parameter so I can use the IsPasswordChangedFromInitial method to decide whether or not to go to the password change page? That page is at /Account/ChangePassword.
EDIT
As per the comments, the appropriate action methods for my specific need are (I have omitted irrelevant code):
Login post action:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginModel details, string returnUrl)
{
if (ModelState.IsValid)
{
AppUser user = await UserManager.FindAsync(details.Name,
details.Password);
if (user == null)
{
AppUser userByName = await UserManager.FindByNameAsync(details.Name);
if(userByName == null)
{
ModelState.AddModelError("", "Invalid username.");
}
else
{
//If this else is reached, it means the password entered is invalid.
//code for incrementing count of failed attempts and navigation to lock out page if needed
}
}
else
{
if(user.LockedOut)
{
//navigate to locked out page
}
else
{
PasswordChangeChecker PassCheck = new PasswordChangeChecker();
string userId = user.Id.ToString();
bool proceed = PassCheck.IsPasswordChangedFromInitial(userId);
//Authorize the user
ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user,
DefaultAuthenticationTypes.ApplicationCookie);
ident.AddClaims(LocationClaimsProvider.GetClaims(ident));
ident.AddClaims(ClaimsRoles.CreateRolesFromClaims(ident));
AuthManager.SignOut();
AuthManager.SignIn(new AuthenticationProperties
{
IsPersistent = false
}, ident);
//persist login into db code
if (proceed)
{
//reset failed logins count
return Redirect(returnUrl);
}
else
{
return ChangePassword();
}
}
}
}
ViewBag.returnUrl = returnUrl;
return View(details);
}
ChangePassword() get action:
[HttpGet]
[Authorize]
public ActionResult ChangePassword()
{
return View();
}
Somehow the view returned is the view in the RouteConfig.cs instead of the ChangePassword.cshtml page.
Thank you.
I would do it with global action filters
you can make a action filter with method
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (showChagePwPage)
{
//redirect to the change password page
filterContext.Result = new RedirectToActionResult("ChangePassword", "Account");
}
base.OnActionExecuting(filterContext);
}
and then adding it into global action filter by
GlobalFilters.Filters.Add(yourFilterContext);
After several days of this hellish exercise similar to yours, where I was trying to route users at login, I realized that I wasn't going to be able to get the value of the UserId while in the login of the Account controller. I did some experimenting and came up with this approach that solved my problem.
I create an ActionResult in my Home controller and called it Purgatory (of course I renamed it to something more suitable once it proved functional). There, I stuffed all my login logic for routing the logged-in user to their respective page upon login.
Then, in the RedirectToLocal in Account controller, I changed the
return RedirectToAction("Index", "Home");
to
return RedirectToAction("Purgatory", "Home");
So now when a user logs in, if the returnTo param isn't set to a particular page, the returnTo param will be null and when it gets to the RedirectToLocal, it'll drop to what used to be the redirect to the home page, which will now go into purgatory.
This sounds like a good time to use an action filter, which you can apply globally or per controller/action.
Here's a simple example:
using System;
using System.Web.Mvc;
using System.Web.Routing;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class VerifyPasswordChangedAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if(!filterContext.ActionDescriptor.ActionName.Equals("changepassword", StringComparison.OrdinalIgnoreCase))
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
var userName = filterContext.HttpContext.User.Identity.Name;
PasswordChangeChecker PassCheck = new PasswordChangeChecker();
if (!PassCheck.IsPasswordChangedFromInitial(userName))
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "account", action = "changepassword", area = string.Empty }));
}
}
}
base.OnActionExecuting(filterContext);
}
}
I would modify your IsPasswordChangedFromInitial method to simply use the authenticated user's username, rather than trying to figure out how to get access to a UserManager instance in an action filter. Otherwise, assuming you're using the OWIN-based ASP.NET Identity, add a claim to store that user.Id field when you create your ClaimsIdentity, so that you don't have to keep looking it up.
The outermost conditional handles the case of this being a global filter - without it, you would get an infinite redirect.
I have an ASP.NET MVC 4 web application where i use Parse as database in the back-end (https://www.parse.com/) and C# as programming language.
I use ParseUser class to log in registered users (https://www.parse.com/docs/dotnet_guide#users-login) like this:
ParseUser.LogInAsync("my_username", "my_password");
Then i have created a custom authorization attribute and i apply it in some controllers and action methods of my project.
public class AuthorizeParseUserAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (ParseUser.CurrentUser != null && ParseUser.CurrentUser.IsAuthenticated)
{
return true;
}
return false;
}
}
This is Parse's documentation for CurrentUser property https://www.parse.com/docs/dotnet_guide#users-current
So, I have the following problem: I successfully log in using my credentials. After log in, i enter the main page of my application (the AuthorizeParseUserAttribute has been applied to the corresponding action method). Then i send the url of this main page to another person, in another computer and the user (which is not even a registered user) can see the main page of my application and is logged in with my credentials!!! Parse's documentation for security for user objects is the following https://www.parse.com/docs/dotnet_guide#users-security
Can you please propose any solution to solve this very serious problem? Thank you.
The Parse SDK for .NET assumes you are building an app that is running on one device per user - it's not designed to integrate with ASP.NET.
From the docs:
Whenever you use any signup or login methods, the user is cached on disk.
ParseUser.CurrentUser is a static method that returns the cached user from the latest call to a signup or login method. This is why in your code, after one user logs in, everybody else that makes a request is also logged in as that user!
I am attempting to integrate Parse with an ASP.NET MVC site I'm currently developing. My plan to work around this limitation is to set the authentication cookie after logging in with Parse, then log out the user (their authentication cookie will still be set though).
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginModel model, string returnUrl)
{
ParseUser user;
try
{
user = await ParseUser.LogInAsync(model.UserName, model.Password);
}
catch (ParseException e)
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
FormsAuthentication.SetAuthCookie(user.Username, model.RememberMe);
ParseUser.LogOut();
return RedirectToLocal(returnUrl);
}
The Register method looks like this:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
try
{
var user = new ParseUser
{
Username = model.UserName,
Password = model.Password,
};
await user.SignUpAsync();
FormsAuthentication.SetAuthCookie(model.UserName, false);
ParseUser.LogOut();
return RedirectToAction("Index", "Home");
}
catch (ParseException e)
{
ModelState.AddModelError("", e.Message);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
When the user enters the correct username and password, he/she will be presented with the MyNewPage.aspx page. This part works properly. But, when I type http://localhost:49296/Pages/MyNewPage directly in the browser window without logging in I still am able to access this page. What I want to do is to restrict users accessing this page without signing in.
How can I do this?
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("MyNewPage", "Pages");
}
}
}
return View(model);
}
Put
[Authorize]
on the controller/action
Making sure a user is logged in to gain access to a view
The easiest way to accomplish this is to use the Authorize attribute above a controller's action method. For instance, we only want to allow users to change thier password if they are already logged into the site. In order to prevent non-authorized users from reaching the change password view we can restrict access like this:
[Authorize]
public ActionResult ChangePassword()
{
ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
return View();
}
You can also accomplish this manually by checking against the User object like this:
public ActionResult ChangePassword()
{
if (!User.Identity.IsAuthenticated)
return RedirectToAction("LogOn", "Account");
ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
return View();
}
Making sure a user is in a particular role to gain access to a view
You may have some views which should only be accessable to users of a particular role. This can also be accomplished using the Authorize attribute like this:
[Authorize(Roles = "Administrator")]
public ActionResult Index()
{
return View();
}
You can accomplish this in code as well using the following method:
public ActionResult Index()
{
if (!User.IsInRole("Administrator"))
return RedirectToAction("LogOn", "Account");
return View();
}
reference: Using our Role and Membership Providers