ASP.Net MVC 4 Generic Principal Difficulties - asp.net-mvc

I am developing an ASP.Net MVC 4 web application. Previously my MVC applications have been developed using MVC 3 and with this new MVC 4 application I have just copied/ reused my authentication and authorisation code from previous applications.
When a user logs into my site I do the following
Account Controller
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
{
User user = _userService.GetUser(model.Email.Trim());
//Create Pipe Delimited string to store UserID and Role(s)
var userData = user.ApplicantID.ToString();
foreach (var role in user.UserRoles)
{
userData = userData + "|" + role.description;
}
_formAuthService.SignIn(user.ApplicantFName, false, userData);
return RedirectToAction("Index", "Portfolio");
}
return View(model);
}
FormsAuthenticationService
public class FormsAuthenticationService : IFormsAuthenticationService
{
public void SignIn(string userName, bool createPersistentCookie, string UserData)
{
if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");
// Create and tuck away the cookie
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1, userName, DateTime.Now, DateTime.Now.AddDays(15), createPersistentCookie, UserData);
// Encrypt the ticket.
string encTicket = FormsAuthentication.Encrypt(authTicket);
//// Create the cookie.
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
HttpContext.Current.Response.Cookies.Add(faCookie);
}
}
Global.asax
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
// Get the authentication cookie
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = Context.Request.Cookies[cookieName];
// If the cookie can't be found, don't issue the ticket
if (authCookie == null) return;
// Get the authentication ticket and rebuild the principal
// & identity
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
string[] UserData = authTicket.UserData.Split(new Char[] { '|' });
GenericIdentity userIdentity = new GenericIdentity(authTicket.Name);
GenericPrincipal userPrincipal = new GenericPrincipal(userIdentity, UserData);
Context.User = userPrincipal;
}
This code works well in my previous MVC 3 applications, but in this MVC 4 application, inside the Razor View, the following code does not seem to be accessing the IsInRole property to perform the role check
#if (HttpContext.Current.User.IsInRole("Applicant"))
{
<p>text</text>
}
Again, this worked perfectly in my MVC 3 applications.
Does anyone have any ideas or suggestions as to why this won't work with my MVC 4 application?
Any help is much appreciated.
Thanks.
Extra Info
My MVC 4 application is using .Net Framework 4.0
The screenshot below shows my Generic Principal which is assigned to Context.User. You can see that for this User, the m_roles contains two strings, the UserID (100170) and their Role(Applicant). But for some reason, The IsInRoles cannot be accessed or seen in my MVC 4 Razor View, however, it could in my identical MVC 3 Razor View.

Folks
I finally resolved this issue. It appears by default the SimpleMembershipProvider is enabled when you create a new ASP.NET MVC 4 application. I did not want to use the SimpleMembershipProvider on this occasion, however, I needed to disable it in my web config with the following line
<appSettings>
<add key="enableSimpleMembership" value="false" />
</appSettings>
My call to User.IsInRole works great now.
Hope this helps someone else.

In MVC 4 you can access the User from the WebPageRenderingBase, so inside razor syntax you have direct access to the User instance:
#if (Request.IsAuthenticated && User.IsInRole("Applicant"))
{
<p>text</p>
}
I see that you are creating a FormsAuthenticationTicket and an HttpCookie manually. The FormsAuthentication class would do this for you using SetAuthCookie(string, bool[, string]). In this sense your auth service can be reduced to:
public class FormsAuthenticationService : IFormsAuthenticationService
{
public void SignIn(string userName, bool createPersistentCookie, string UserData)
{
if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");
FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
}
}
It turns out you also need to change Application_AuthenticateRequest to Application_OnPostAuthenticateRequest:
protected void Application_OnPostAuthenticateRequest(Object sender, EventArgs e)

In MVC 4 HttpContext.Current.User is not exposed so you can't use it. What i did was create a custom BaseViewPage and added following code in it.
public abstract class BaseViewPage : WebViewPage
{
public virtual new Principal User
{
get { return base.User as Principal; }
}
}
public abstract class BaseViewPage<TModel> : WebViewPage<TModel>
{
public virtual new Principal User
{
get { return base.User as Principal; }
}
}
And then make following changes to system.web.webPages.razor/pages section your web.config in Views folder.
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="WebApp.Views.BaseViewPage">
<namespaces>
...
</namespaces>
</pages>
Hope this solves your problem.

Related

Asp.Net 3.1 using Windows Authentication and Role Authorisation

setup with Asp.net & .Net Core 3.1, I have upgraded a previous Identity system using UserName/Password with Roles to use Windows Authentication.
I have created a ClaimsTransformation which gets the windows Identity and creates a new ClaimsPrincipal with the users associated roles. This part is working
My startup.cs looks like this (some parts removed)
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IClaimsTransformation, KiwaClaimsTransformation>();
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddAuthorization();
...
services.AddControllers();
services.AddControllersWithViews()
.AddSessionStateTempDataProvider();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env,
ILoggerFactory loggerFactory, IServiceProvider serviceProvider)
{
...
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
// catch all for not found
endpoints.MapControllerRoute("NotFound", "{*url}",
new {controller = "Error", action = "ResourceNotFound"});
});
...
}
The ClaimsTransformation looks like this
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var identity = (ClaimsIdentity)principal.Identity;
if (identity == null) return principal;
var userName = _config["LoginUserName"];
if (userName == null)
{
userName = identity.Name;
if (userName == null) return principal;
}
// need to go and build the Roles claims for the user based on the User Name as a lookup on User table
var claims = new List<Claim>
{
new Claim(#"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", userName, "Name")
};
claims.AddRange(_userLookup.GetUserRolesByNetworkId(userName)
.Select(role => new Claim(ClaimTypes.Role, role)));
//The claim identity uses a claim with the claim type below to determine the name property.
// Get User Roles from database and add to list of claims.
var newClaimsIdentity = new ClaimsIdentity(claims, "Kerberos", "", "http://schemas.microsoft.com/ws/2008/06/identity/claims/role");
return new ClaimsPrincipal(new ClaimsPrincipal(newClaimsIdentity));
}
I have a basic HomeController which looks like this
public class HomeController : Controller
{
private readonly LoggedOnUser _loggedOnUser;
public HomeController(LoggedOnUser loggedOnUser)
{
_loggedOnUser = loggedOnUser;
}
[Authorize]
[HttpGet]
public IActionResult Index()
{
// check and make sure the user is allowed in
if (!_loggedOnUser.IsValidKiwaUser)
{
return RedirectToActionPermanent("NotAuthorised");
}
return View();
}
[Authorize]
public IActionResult OperationResults()
{
ViewBag.Title = (string)TempData["Title"];
string jsonString = (string)TempData["OperationResults"];
if (string.IsNullOrWhiteSpace(jsonString))
{
return RedirectToPage("/Error/NoResults");
}
return View(JsonConvert.DeserializeObject<List<OperationResult>>(jsonString));
}
public IActionResult NotAuthorised()
{
return View();
}
All of the Controllers have [Authorize(Role="...")], and the Authorisation is happening correctly and the Roles are added as claims via the ClaimsTransformation.
The issue i am having is that if i hit the root of the Website (debugging this is https://localhost:44391), then the routing sends me to the NotAuthorised page on the controller??? It should be default go to https://localhost:44391/Home/index as defined in the default Endpoint.
If I type in https://localhost:44391/Home/index it works and shows the correct main landing page, but if i do NOT include the https://localhost:44391/Home/index in its entirety then it comes back as unauthorized.
Am i missing something here? Also can i turn
I eventually found the issue. During the transition to change over to Windows Authentication, i had left the cookie support i the product. But what this had done was store the starting page as being the NotAuthorised page. Clearing the cookie (and subsequently removing the cookie support from the app), fixed the issue and the Roles were evaluated all the time. Hence why I used a lookup (memory Cache) for accessing the user and their claims - as it gets called for all User requests
Oh by the way. The check for _loggedOnUser.IsValidKiwaUser in the HomeController/Index is actually no longer required if you use this as an example

In MVC Authorize filter, From where roles will be picked

I am learning MVC. I want to know , from where roles will be picked up .
ex
[Authorize(Roles ="admin")]
public class HomeController : Controller
{
// GET: Search
public ActionResult search()
{
return View();
}
}
Here where should we write authorize code to make home controller only applicable for admin.
Assuming that you are not using ASP.NET Core, in the controller when you log in, use this code to register the login with a role attached:
var role = "admin"; // or whatever role you want to use here
var authTicket = new FormsAuthenticationTicket(1, userName, DateTime.Now, DateTime.Now.AddHours(8), false, role);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(authTicket));
Response.Cookies.Add(cookie);
Then in the Global.asax.cs add this:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
var authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
//get the user cookie and get the roles from it and apply them
var authTicket = FormsAuthentication.Decrypt(authCookie.Value);
var roles = authTicket.UserData.Split(','); //note that you can use multiple roles with this
var userPrincipal = new GenericPrincipal(new GenericIdentity(authTicket.Name), roles);
Context.User = userPrincipal;
}
}
With that in place your Authorize attribute will pick up the role the user logged in with.
Roles are provided by the RoleManager (or RoleProvider), see more here and here.
For example, you can use the WindowsTokenRoleProvider will pull roles from Windows Authentication. Or you can roll your own by inheriting RoleProvider:
public class MyRoleProvider : RoleProvider
{}
PS. This is for .net 4.x, asp.net core might be something different.

AspNet Identity MVC - How to catch Signed In Event

I am using ASPNet Identity 2.0 (Full framework, not the core framework) and MVC.
I would like to execute C# code once the user successfully login to the site.
i know that i can write some code right after the SignInManager.PasswordSignInAsync command and it will work for new login but not will not work for users who used "remember me" feature and returned to the site later (Cookie authentication).
I am looking for an option to catch the event of all the users who signed in to the site either by entering the password and by using the "remember me" cookie.
One way you can do it is by handling the application event in your global.asax file.
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (Request.IsAuthenticated)
{
Response.Write("HELLO");
}
}
There are many ways to do it. You can create and use custom attribute.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
readonly IAuthentication _authentication;
public CustomAuthorizeAttribute(IAuthentication authentication)
{
_authentication = authentication;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (!_authentication.Authorize(filterContext.HttpContext))
filterContext.Result = new HttpUnauthorizedResult();
//your code .....
}
}
And now rather than using [Authorize] use your new [CustomAuthorizeAttribute] attribute
[CustomAuthorizeAttribute]
public ActionResult Index()
{
ViewBag.Title = "Welcome";
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
What you're after is FormsAuthentication_OnAuthenticate (I appreciate Forms-based authentication was not mentioned in the question, but it's an example of Cookie-based remember me authentication, adapt at will)
Unfortunately, there is no trigger for OnCookieBasedFormsAuthenticate_SessionCreate :)
So what you can do is check the Forms-based authentication every so often, because it (and Application_AuthenticateRequest) is fired for every request, CSS pages, images etc, going off to the database to check multiple times per request is an overly resource hungry idea. Luckily the forms cookie ticket has an issued on date and we can use that to check:
public void FormsAuthentication_OnAuthenticate(object sender, FormsAuthenticationEventArgs args)
{
if (FormsAuthentication.CookiesSupported)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(
Request.Cookies[FormsAuthentication.FormsCookieName].Value);
if ((DateTime.Now - ticket.IssueDate).TotalMinutes > 10)
{
if(/*Insert logic to check username here with ticket.Name*/)
{
//recreate cookie with new issuedate
FormsAuthentication.SetAuthCookie(ticket.Name, ticket.IsPersistent);
}
else
{
FormsAuthentication.SignOut();
}
}
Debug.WriteLine($"{ticket.Name} {ticket.IssueDate.ToUniversalTime()}");
}
catch (Exception e)
{
//Elmah
ErrorSignal.FromCurrentContext().Raise(e);
//Cannot decrypt cookie, make the user sign in again
FormsAuthentication.SignOut();
}
}
}
else
{
throw new HttpException("Cookieless Forms Authentication is not supported for this application.");
}
}
This is not the same as cookie expiration, because the user will still retain login status; the user should never see any login request unless there account is no longer valid.

SimpleMembership: AuthorizeAttribute and User.IsInRole not working

I've been racking my brain over this for the past week, and none of the answers I've found here or elsewhere seem to be doing anything. I have an ASP.NET MVC5 application that uses SimpleMembership. I have a controller called OrganisationsController that has the following attribute:
[Authorize(Roles = "Administrator")]
I've checked the database and the user I'm logging in with is indeed in the "Administrator" role. However, neither the Authorize attribute nor User.IsInRole() return "true" for this role.
In Authorize attribute not working with roles it is suggested that
The AuthorizeAttribute calls the IsInRole method on the IPrincipal instance stored in HttpContext.User. By default IPrincipal has no roles, and in this case IsInRole will always return false. This is why access to your action is denied.
I've used the following code as suggested in that answer, but authTicket.UserData remains empty.
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
string[] roles = authTicket.UserData.Split(',');
GenericPrincipal userPrincipal = new GenericPrincipal(new GenericIdentity(authTicket.Name), roles);
Context.User = userPrincipal;
}
}
I can't figure out what's going wrong. Why can I log in, but can't any of the roles be found?
Here's some relevant parts of the web.config:
<roleManager enabled="true" defaultProvider="SimpleRoleProvider">
<providers>
<add name="SimpleRoleProvider" type="WebMatrix.WebData.SimpleRoleProvider, WebMatrix.WebData" />
</providers>
</roleManager>
<membership defaultProvider="SimpleMembershipProvider">
<providers>
<add name="SimpleMembershipProvider" type="WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData" />
</providers>
</membership>
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880" cookieless="UseCookies" />
</authentication>
and this is the InitializeSimpleMembershipAttribute I've defined:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class InitializeSimpleMembershipAttribute : ActionFilterAttribute
{
private static SimpleMembershipInitializer _initializer;
private static object _initializerLock = new object();
private static bool _isInitialized;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Ensure ASP.NET Simple Membership is initialized only once per app start
LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
}
private class SimpleMembershipInitializer
{
public SimpleMembershipInitializer()
{
Database.SetInitializer<UsersContext>(null);
try
{
using (var context = new UsersContext())
{
if (!context.Database.Exists())
{
// Create the SimpleMembership database without Entity Framework migration schema
((IObjectContextAdapter)context).ObjectContext.CreateDatabase();
}
}
if (!WebSecurity.Initialized)
{
WebSecurity.InitializeDatabaseConnection("VerhaalLokaalDbContext", "UserProfile", "UserId", "UserName", autoCreateTables: true);
}
}
catch (Exception ex)
{
throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex);
}
}
}
}
The InitializeSimpleMembershipAttribute is only set on the AccountController.
What's exactly is going on here? Why can't the roles, which are defined and tied to users in the database, be found?
We used the System.Web.Security.Roles.GetRolesForUser(...) call and then brute force check those role arrays for an intersection. In our case this all happens inside a custom AuthorizeAttribute classes' () call. An extended attribute may not necessary for you but I wanted to give the following code snippet some context. We only used the principal to get the user name.
e.g.,
var userRoles = System.Web.Security.Roles.GetRolesForUser(username);
var allowedRoles = Roles.Split(','); // Roles is a property on the Authorize attribute
var matches = userRoles.Intersect(allowedRoles).ToArray();
if ( matches.Length > 0 ) // true if user is in an allowed role, otherwise it is not
Hope that helps! I'm sure there is a more efficient way, I just dusted off what we have been using for two years now.

ASP.NET MVC Identity:how do I Extend AspNetUserRoles table

In ASP.NET MVC Identity,the relations data for Users and Roles is saved in AspNetUserRoles table, this table has two field:UserId,RoleId, but i want to add other fields to this table, such as department field.
So if an user logins in different departments,he will have different roles.
Anyone knows how to do it? Thanks in advance!
I Would Suggest you investigate ASPNet User Claims. You can assign different claims to a user with the identity manager, and based on the claim type of the user you will allow him access or not. Create a custom Claims Attribute which will be placed on top of the various controller to authenticate the user. this must be implemented based on your needs. the custom attribute will then fire before the controller gets executed and if the uses is allowed he will pass. else return to error page of you choice.
Sample Attribute usage
[ClaimsAuthorize(ClaimsData.EditAddress)]
public ActionResult CitiesPartial()
Attribute Authentication
public class ClaimsAuthorizeAttribute : AuthorizeAttribute
{
private readonly string _claimType;
public ClaimsAuthorizeAttribute(string type)
{
_claimType = type;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
var user = (ClaimsPrincipal)HttpContext.Current.User;
if (user.HasClaim(_claimType, "True"))
{
base.OnAuthorization(filterContext);
}
else
{
HandleUnauthorizedRequest(filterContext, _claimType + " Not Allowed ");
}
}
protected void HandleUnauthorizedRequest(AuthorizationContext filterContext, string message)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "action", "ClaimNotAuthorized" },
{ "controller", "Home" },
{"errorMessage", message }
});
}
public static bool AuthorizedFor(string claimType)
{
var user = (ClaimsPrincipal)HttpContext.Current.User;
return user.HasClaim(claimType, "True");
}
}
hope this helps.

Resources