I am using MVC4 and I'm trying to modify the allocation procedure for authenticating a user and assign roles to users. Everything works fine for the attribute [Authorize (Users = "adminadmin")], but the [Authorize (Roles = "Admin")] every time there is a login page and lack of access.
Global.asax.cs:
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
// look if any security information exists for this request
if (HttpContext.Current.User != null)
{
// see if this user is authenticated, any authenticated cookie (ticket) exists for this user
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
// see if the authentication is done using FormsAuthentication
if (HttpContext.Current.User.Identity is FormsIdentity)
{
// Get the roles stored for this request from the ticket
// get the identity of the user
FormsIdentity identity = (FormsIdentity)HttpContext.Current.User.Identity;
//Get the form authentication ticket of the user
FormsAuthenticationTicket ticket = identity.Ticket;
//Get the roles stored as UserData into ticket
List<string> roles = new List<string>();
if (identity.Name == "adminadmin")
roles.Add("Admin");
//Create general prrincipal and assign it to current request
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(identity, roles.ToArray());
}
}
}
}
AccountController:
[InitializeSimpleMembership]
public class AccountController : Controller
{
public ActionResult Login()
{
return View();
}
[HttpPost]
public ActionResult Login(LoginModel model, string returnUrl)
{
// Lets first check if the Model is valid or not
if (ModelState.IsValid)
{
string username = model.UserName;
string password = model.Password;
bool userValid = username == password ? true : false;
// User is valid
if (userValid)
{
FormsAuthentication.SetAuthCookie(username, false);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
public ActionResult LogOff()
{
FormsAuthentication.SignOut();
return RedirectToAction("Index", "Home");
}
}
HomeController.cs:
public class HomeController : Controller
{
[AllowAnonymous]
public ActionResult Index()
{
ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";
return View();
}
[Authorize]
public ActionResult About()
{
ViewBag.Message = "Your app description page.";
return View();
}
[Authorize(Roles = "Admin")]
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
Web.config:
(...)
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880"/>
</authentication>
(...)
You're almost there. Right now what's happening is that you set the principal to a custom principal, and the SimpleMembership provider comes in after you and blows away your principal by setting it to a System.Web.Security.RolePrincipal. Move your current Application_AuthenticateRequest code into a new Application_PostAuthenticateRequest handler and your custom principal will remain in place.
This is what you want (although it uses a custom membership)
http://mycodepad.wordpress.com/2014/05/17/mvc-custom-authorizeattribute-for-custom-authentication/
Related
I need authorization and authentication to be implemented in my MVC4 C# project.
I don't want to implement login logic to input Username and Password; i.e. no login screen.
I have user Id by HttpContext.Current.User.Identity.Name.
All I need if user is of role "admin" he can access all the controller pages and if he is user he can only access user controller and if he try to click Admin's actionlink he will get a message you are not authorized.
Every time a user is accessing any Actionmethod, my authorization logic is to be executed and checked and verified first, independently of which URL he is accessing.
Till now I have done this.
In web config:
<authentication mode="Forms" />
<forms loginUrl="~/Appauth/appauth" timeout="2880" />
</authentication>
My route table:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters*
new { controller = "Appauth", action = "appauth", id = UrlParameter.Optional });
public class LoginController : Controller
{
public ActionResult appauth(returnUrl)
{
bool userValid = false;
currentuserID = HttpContext.Current.User.Identity.Name;
if(currentuserID!=null)
{
userValid = true;
}
// User valid
if (userValid)
{
FormsAuthentication.SetAuthCookie(username, false);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("UserHome", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
return RedirectToAction("Homeview");
}
}
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if (FormsAuthentication.CookiesSupported == true)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
//let us take out the username now
string username = FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value).Name;
string roles = string.Empty;
role =//roles logic ;
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(
new System.Security.Principal.GenericIdentity(username, "Forms"), roles.Split(';'));
}
catch (Exception)
{
//somehting went wrong
}
}
}
}
public class AdminHomeController : Controller
{
[Authorize(Roles="Admin")]
public ActionResult About()
{
//admin logic
return View("AdminViewHOme");
}
}
public class UserHomeController : Controller
{
[Authorize]
public ActionResult Index()
{
//user logic
return View(userviewhome);
}
}
When I run this code to goes loop always calling my login logic and setting ticket and then jump to global asax to Facilitating Roles extraction using the authentication ticket and again login logic, i want to do it one time for a user not every time.
PS: If it is asp.net I can achieve it by writing method in Onpageload() event.
confused this ticketing system and there flow please help with code.
I'm trying to make a section of a MVC 5 webpage restricted to users of a certain Active directory group, however the [Authorize] attribute (on controller) blocks logged in users aswell.
My Login page code behind looks as follows:
public class AccountController: Controller
{
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
{
ActiveDirectoryHelper ad = new ActiveDirectoryHelper();
if (Membership.ValidateUser(model.UserName, model.Password))
{
if (ad.CheckGroupMembership(model.UserName))
{
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("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "Credentials are correct but you are no authorised \n You Need membership in group: HKF-HIT-FortigateAPI-GS");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect");
}
}
// if we got this far, something failed, redisplay form
return View(model);
}
// POST: /Account/LogOff
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
FormsAuthentication.SignOut();
return RedirectToAction("Index", "Home");
}
}
public class ActiveDirectoryHelper
{
string group = "HKF-HIT-FortigateAPI-GS";
public bool CheckGroupMembership(string name)
{
var context = new PrincipalContext(
ContextType.Domain,
"AD-Domain", #"Username", "Password");
var userPrincipal = UserPrincipal.FindByIdentity(
context,
IdentityType.SamAccountName,
name);
var test = userPrincipal;
if (userPrincipal.IsMemberOf(context,
IdentityType.Name,
group))
{
return true;
}
return false;
}
}
The user passes and is redirected to Index in Home controller.
This controller however has the [Authorized] value set as follows:
[Authorize]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
And here the user in bounced back to the loginpage, as if he was not Authorized.
Also this is web.config:
In the browser i can see the ADAuthCookie.
Edit: Ading pictures of Request data:
Account Post:
Fiddler:
Index Get:
Fiddler:
EDIT: Question has been solved, after going trough the amazing guide linked by in the comments i realised i was never handling my cooke in the Global.asaz.cs Class.
Adding an overide to Application_PostAuthenticateRequest solved my problem.
The code i added ended up using:
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
JavaScriptSerializer serializer = new JavaScriptSerializer();
CustomPrincipalSerializeModel serializeModel = serializer.Deserialize<CustomPrincipalSerializeModel>(authTicket.UserData);
CustomPrincipal newUser = new CustomPrincipal(authTicket.Name);
newUser.Name = serializeModel.Name;
HttpContext.Current.User = newUser;
}
}
In global.asax and i also added:
CustomPrincipalSerializeModel serializeModel = new CustomPrincipalSerializeModel();
serializeModel.Name = model.UserName;
JavaScriptSerializer serializer = new JavaScriptSerializer();
string userData = serializer.Serialize(serializeModel);
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
1,
model.UserName,
DateTime.Now,
DateTime.Now.AddMinutes(15),
false,
userData);
string encTicket = FormsAuthentication.Encrypt(authTicket);
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
Response.Cookies.Add(faCookie);
To my login page.
AuthorizeAttribute checks the HttpContext.User value (an IPrincipal implementation) and the HttpContext.User.Identity value (an IIdentity implementation).
All of the security frameworks (Identity, Membership, etc.) from Microsoft use these interfaces to communicate with MVC/ASP.NET. If you are using a custom security framework, you also need to implement these interfaces and set them in the AcquireRequestState (if using session state) or PostAuthorizeRequest event.
See ASP.NET MVC - Set custom IIdentity or IPrincipal for an example of the latter along with custom IPrincipal and IIdentity implementations.
I'm creating ASP.NET MVC 4 Internet Application.
In that Application I created Login Page that any user can log in to, then I allowed to redirect user to different pages based on their role.
ASP.NET Identity is the membership system here.
This is my Login Controller method:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
var user = await UserManager.FindAsync(model.UserName, model.Password);
if (user != null)
{
if (user.ConfirmedEmail == true)
{
await SignInAsync(user, model.RememberMe);
if (String.IsNullOrEmpty(returnUrl))
{
if (UserManager.IsInRole(user.Id, "HEC_Admin"))
{
return RedirectToAction("Index", "HEC");
}
//role Admin go to Admin page
if (UserManager.IsInRole(user.Id, "HEI_User"))
{
return RedirectToAction("Index", "HEI");
}
}
else
{
return RedirectToLocal(returnUrl);
}
}
else
{
ModelState.AddModelError("", "Confirm Email Address.");
}
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
This is HEI Controller Class:
public class HEIController : Controller
{
//
// GET: /HEI/
[Authorize(Roles = "HEI_User")]
public ActionResult Index()
{
return View();
}
}
This is my HEC Controller Class:
public class HECController : Controller
{
//
// GET: /HEC/
[Authorize(Roles = "HEC_Admin")]
public ActionResult Index()
{
return View();
}
}
when I remove [Authorize(Roles = "HEC_Admin")] above the index action in HECController class and when I remove [Authorize(Roles = "HEC_User")] above the index action in HEIController class this is working fine,
but then How restrict unauthorized access to these pages?
I had the same problem as you and I still don't know the reason why it happens. What I did was to create my own custom Authorization Attribute and check the Roles myself.
public class CustomAuthorizationAttribute : AuthorizeAttribute
{
public string IdentityRoles
{
get { return _identityRoles ?? String.Empty; }
set
{
_identityRoles = value;
_identityRolesSplit = SplitString(value);
}
}
private string _identityRoles;
private string[] _identityRolesSplit = new string[0];
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//do the base class AuthorizeCore first
var isAuthorized = base.AuthorizeCore(httpContext);
if (!isAuthorized)
{
return false;
}
if (_identityRolesSplit.Length > 0)
{
//get the UserManager
using(var um = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
{
var id = HttpContext.Current.User.Identity.GetUserId();
//get the Roles for this user
var roles = um.GetRoles(id);
//if the at least one of the Roles of the User is in the IdentityRoles list return true
if (_identityRolesSplit.Any(roles.Contains))
{
return true;
}
}
return false;
}
else
{
return true;
}
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
//if the user is not logged in use the deafult HandleUnauthorizedRequest and redirect to the login page
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
base.HandleUnauthorizedRequest(filterContext);
}
else
//if the user is logged in but is trying to access a page he/she doesn't have the right for show the access denied page
{
filterContext.Result = new RedirectResult("/AccessDenied");
}
}
protected static string[] SplitString(string original)
{
if (String.IsNullOrEmpty(original))
{
return new string[0];
}
var split = from piece in original.Split(',')
let trimmed = piece.Trim()
where !String.IsNullOrEmpty(trimmed)
select trimmed;
return split.ToArray();
}
}
I also added the HandleUnauthorizedRequest method to redirect to a appropriated page if the user has is logged in but has no access to this action or controller
To use it just do this:
[CustomAuthorization(IdentityRoles = "HEI_User")]
public ActionResult Index()
{
return View();
}
Hope it helps.
I working on existing application. We are using ASP.NET MVC 4.
When I start the authentication mode was Windows but I want change it into Form Authentication. I create Account controller and Login Action for authenticate user from database. I did the following
First I configure Authentication mode into form in web.config file.
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880" cookieless="UseUri"/>
</authentication>**
Second I enable Form Authentication on IIS server
Third I modified Global.ASP by the following code
void Application_PostAuthenticateRequest()
{
if (User.Identity.IsAuthenticated)
{
Entities db = new Entities();
var name = User.Identity.Name;
var key = "Roles." + name;
var roles = HttpContext.Current.Cache[key] as string[];
if (roles == null)
{
//roles = db.GetRoles(HttpContext.Current.User.ToString()).ToArray();
roles = db.GetRoles(name).ToArray();
//HttpContext.Current.Cache.Remove(key);
HttpContext.Current.Cache.Insert(key, roles, null, DateTime.Now.AddHours(1), Cache.NoSlidingExpiration);
}
HttpContext.Current.User =
Thread.CurrentPrincipal =
new GenericPrincipal(User.Identity, roles);
}
my controller and action look like this
[Authorize]
// [InitializeSimpleMembership]
public class AccountController : Controller
{
Entities db = new Entities();
Users users = new Users();
//
// GET: /Account/
[AllowAnonymous]
public ActionResult Login()
{
return View(users);
}
// This Action is used to validate weather user is existing or not in a database
[HttpPost]
public ActionResult Login(Users user)
{
//Check Validation
if (ModelState.IsValid)
{
//In this Action we are doing Form Authentication
//Check the entred username and passwords are proper or not
int? flag = db.GET_USER(user.UserName, user.Password).FirstOrDefault();
if (flag == 1)
{
//use Form Authentication class to set cookie
FormsAuthentication.SetAuthCookie(Request.Form["txtUserName"], true);
//#Session["username"] = user.FirstName;
return View("~/Views/Home/Index.cshtml");
}
else
{
ViewBag.ErrorMessage = "The user name or password you entered is incorrect.";
return View("~/Views/Account/Login.cshtml");
}
}
else
{
return View("~/Views/Account/Login.cshtml");
}
}
But still "User.Identity.Name" property return my user name instead of the authenticated user's user name.
Is there anything I miss?
Thank you inadvance
Using MVC 5 and Identity 2.0, I add custom properties to ApplicationUserClass like FirstName, LastName, Address. Those will be new fields in the database. When the user register to the application he/she will enter the email address and password only. After they register, and they log in I want to force them to complete their profile, or at least each time they login they should be redirected to the profile completion page where they can mention the FirstName, Lastname and Address. Afther they complete the profile they will not be redirected to complete profile page each time they login.
Something like:
if UserProfile != Completed
go to CompleteProfilePage
else
go to MainPage
You can try a global filter. This will not allow your users to bypass the check by modifying the URL manually.
public class ProfileCompletionCheckAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//this will prevent redirect for still unauthenticated users
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
return;
//replace these to actual values of your profile completion action and controller
string actionOfProfilePage = "Index";
string controlerOfProfilePage = "Home";
bool areWeAlreadyInProfilePage = filterContext.ActionDescriptor.ActionName == actionOfProfilePage
&& filterContext.ActionDescriptor.ControllerDescriptor.ControllerName == controlerOfProfilePage;
if (areWeAlreadyInProfilePage) //this will prevent redirect loop
return;
bool isProfileComplete = false; //replace this with your custom logic
if (!isProfileComplete)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "controller", controlerOfProfilePage },
{ "action", actionOfProfilePage }
});
}
}
}
To enable it, just add this to FilterConfig.cs
filters.Add(new ProfileCompletionCheckAttribute());
In your AccountController something like this :
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
return RedirectToLocal(returnUrl);
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
Upgrade like this
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
UsersContext dbu = new UsersContext();
UserProfile usr = dbu.UserProfiles.Where(u => u.UserName == model.UserName).First();
if (usr.FirstName == null) return RedirectToAction("Profile", "Account");
return RedirectToLocal(returnUrl);
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}