SimpleMembership: AuthorizeAttribute and User.IsInRole not working - asp.net-mvc

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.

Related

Unit Testing account MVC controller Error - real MembershipService is still being used

I have the following account controller
public class AccountController : Controller
{
public IMembershipService MembershipService { get; set; }
protected override void Initialize(RequestContext requestContext)
{
if (MembershipService == null) { MembershipService = new AccountMembershipService(); }
base.Initialize(requestContext);
}
public AccountController(IMembershipService membership)
{
MembershipService = membership;
}
[HttpPost]
public ActionResult Login(LoginModel model, string ReturnUrl)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateUser(model.EmailorUserName, model.Password))
{
.....
}
}
}
from my unit testing project I want to simulate a login
public class AccountControllerTest2
{
[Test]
public void Login_UserCanLogin()
{
string returnUrl = "/Home/Index";
string userName = "user1";
string password = "password1";
Mock<AccountMembershipService> Membership = new Mock<AccountMembershipService>();
AccountController Controller = new AccountController(Membership.Object);
var model = new LoginModel
{
EmailorUserName = userName,
Password = password
};
var result = Controller.Login(model, returnUrl) as RedirectResult;
Assert.NotNull(result);
Assert.AreEqual(returnUrl, result.Url);
}
}
my web config in my main application uses custommembership provider
<membership defaultProvider="CustomMembershipProvider">
<providers>
<clear />
<add name="CustomMembershipProvider" type="QUBBasketballMVC.Infrastructure.CustomMembershipProvider" connectionStringName="UsersContext" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" />
</providers>
</membership>
I keep getting this error
QUBBasketballMVC.Tests.Controllers.AccountControllerTest.Login_UserCanLogin:
System.Web.Management.SqlExecutionException : An error occurred during the execution of the SQL file 'InstallCommon.sql'. The SQL error number is 5123 and the SqlException message is: CREATE FILE encountered operating system error 5(Access is denied.) while attempting to open or create the physical file 'C:\PROGRAM FILES (X86)\NUNIT 2.6.3\BIN\APP_DATA\ASPNETDB_TMP.MDF'.
CREATE DATABASE failed. Some file names listed could not be created. Check related errors.
Creating the ASPNETDB_7b94db5a0b5b4fbbbe22fa8e91e4cc68 database...
It seems that you are still initializing the real membership database, meaning that the MembershipService hasn't been completely mocked out. You shouldn't need to add the membership config to your unit tests, given that you intend mocking it out completely.
You almost certainly want to mock the Interface to your service abstraction IMembershipService, viz:
Mock<IMembershipService> Membership = new Mock<IMembershipService>();
As an aside, the lazy initialization code
if (MembershipService == null)
MembershipService = new AccountMembershipService();
isn't ideal from a testing point of view, given that it means that the controller has 2 modes of operation, whereby it can either create the MembershipService itself, or accept one as a constructor dependency.
As an alternative, you might consider an IoC framework here to manage dependency lifespans, and this way there is only one set of code to be tested.

Authorize Users at Controller level from web.Config

In my controller the [Authorized] annotation.
I'd like to go get a list of authorized users that are setup in my web.config file.
<add key="authorizedUsers" value="jeff,dan,mindy,claudia"/>
I know in the controller you can do something like:
[Authorize Users="jeff,dan,mindy,claudia"]
But I'd rather just update the web.config file without having to re-compile. Is there anyway to do read the web.config file for my list and then add it to the [Authorize] attribute? I'm also using Windows Authenticationfor this rather than Form Authentication.
You can implement custom AuthorizeAttribute which inherits from AuthorizeAttribute.
I assume you are using FormAuthentication. Otherwise, it won't work.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class CustomUserAuthorizeAttribute : AuthorizeAttribute
{
private string[] _usersSplit
{
get
{
var authorizedUsers = ConfigurationManager.AppSettings["authorizedUsers"];
return authorizedUsers.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries);
}
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
throw new ArgumentNullException("httpContext");
IPrincipal user = httpContext.User;
return user.Identity.IsAuthenticated && (_usersSplit.Length <= 0 || Enumerable.Contains(_usersSplit, user.Identity.Name, StringComparer.OrdinalIgnoreCase));
}
}
Usage
[CustomUserAuthorize]
public ActionResult Test()
{
ViewBag.Message = "Your page.";
return View();
}
FYI: Ideally, you want to use role based authentication, and store them in database. It is a little bit easy to maintain. However, it is up to your need.
You don't do it like that. You need to setup these users into roles and then grant the roles access in the web.config file.
<system.web>
<authorization>
<allow roles="admin"/>
</authorization>
</system.web>
You can do this;
<authorization>
<allow users="?"/>
<deny users="*"/>
</authorization>
but that opens the website to potential hacking IMO

ASP.Net MVC 4 Generic Principal Difficulties

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.

Can't get custom ASP MVC 4 WebAPI MembershipProvider to be called

I'm trying to wire up a custom membership privider in ASP MVC 4 WebAPI but I cannot figure out how to replace the current provider. I've put a breakpoint in the constructor of the provider and it never lands on it. Running the test below gives me a "401 Unauthorized" message on the GetResponse call so SOMETHING is trying to validate the request. Where am I going wrong? Nearly this identical code runs in ASP MVC 4 site (not WebAPI).
I know the general logic in the test is working because the test succeeds if i remove the Authorize attribute.
This is my WebConfig:
<authentication mode="Forms" />
<profile enabled="false">
<providers>
<clear/>
</providers>
</profile>
<membership defaultProvider="MyMembershipProvider">
<providers>
<clear/>
<add name="MyMembershipProvider"
type="CustomWebApiMembershipProvider"
applicationName="AppName"/>
</providers>
</membership>
<roleManager enabled="true" defaultProvider="MyWebApiRoleProvider">
<providers>
<clear/>
<add name="MyWebApiRoleProvider" type="CustomWebApiRoleProvider" />
</providers>
</roleManager>
This is my provider.
public class CustomWebApiMembershipProvider: MembershipProvider
{
public override bool ValidateUser(string username, string password)
{
return password.Contains("please");
}
public CustomWebApiMembershipProvider()
{
var x = "hello";
}
}
This is my controller:
[Authorize]
public class TestApiSecureController : ApiController
{
// GET api/<controller>
public IEnumerable<string> Get()
{
return new string[] {"CustomWebApi Secured Access"};
}
}
and this is my test:
[Test]
public void Authorize_WhenApiKeyProvided_ReturnsValidResponse()
{
var request = WebRequest.Create("http://localhost:4011/api/TestApiSecure") as HttpWebRequest;
AddAuthorizationInfo(request, "username", "pleaseLetMeIn");
//we keep timing out when stepping through in debug, so set huge timeout
request.Timeout = 100000;
var response = request.GetResponse();
var reader = new StreamReader(response.GetResponseStream());
var result = reader.ReadToEnd();
Assert.IsTrue(result.Contains("CustomWebApi"));
}
private void AddAuthorizationInfo(HttpWebRequest request, string username, string password)
{
var usernamePwString = username + ":" + password;
var authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(usernamePwString));
request.Headers["Authorization"] = "Basic" + authInfo;
}
It looks like forms authentication requires some cookies magic, that isn't handled by an HttpWebRequest. You'll need to create your own Authorize attribute - check out : http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-membership-provider/

accessing profile.newproperty in MVC web applications

I recently asked this question How to persist anon user selection (ex: theme selection). and started to learn about ASP.NET profiles and their properties in the web config. I tried the answer from the link but i was unable to access profile.newproperty
How to assign Profile values?
This question specifies that web-applications don't support profile out of the box and a custom model based on ProfileBase must be created. The question was answered in 2009 and I wanted to know if this is still the same case.
In a ASP.NET 4.0 web application can I access profile.newproperty with a property i defined in the section in the web.config without needing to code C# except when accessing it.
I just saw your question, yes you are right, the answer I posted was related to web sites and therefore, it doesn't work with Web Applications or MVC
Here I will show you code to work with profiles in MVC using anonymous and authenticated user profiles
Output
Anonymous user - No profile set yet
Anonymous user - profile set
Authenticated user - profile migrated
Web.config
<anonymousIdentification enabled="true"/>
<profile inherits="ProfileInWebApplicationMVC1.UserProfile">
<providers>
<clear/>
<add name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="ApplicationServices" applicationName="/" />
</providers>
</profile>
UserProfile class
public class UserProfile : ProfileBase
{
public static UserProfile GetProfile()
{
return HttpContext.Current.Profile as UserProfile;
}
[SettingsAllowAnonymous(true)]
public DateTime? LastVisit
{
get { return base["LastVisit"] as DateTime?; }
set { base["LastVisit"] = value; }
}
public static UserProfile GetProfile(string userID)
{
return ProfileBase.Create(userID) as UserProfile;
}
}
Home controller
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
var p = UserProfile.GetProfile();
return View(p.LastVisit);
}
[HttpPost]
public ActionResult SaveProfile()
{
var p = UserProfile.GetProfile();
p.LastVisit = DateTime.Now;
p.Save();
return RedirectToAction("Index");
}
Index view
#if (!this.Model.HasValue)
{
#: No profile detected
}
else
{
#this.Model.Value.ToString()
}
#using (Html.BeginForm("SaveProfile", "Home"))
{
<input type="submit" name="name" value="Save profile" />
}
And finally, when you are an anonymous user you can have your own profile however, once you register to the site, you need to migrate your current profile to be used with your new account. This is because ASP.Net membership, creates a new profile when a user logs-in
Global.asax, code to migrate profiles
public void Profile_OnMigrateAnonymous(object sender, ProfileMigrateEventArgs args)
{
var anonymousProfile = UserProfile.GetProfile(args.AnonymousID);
var f = UserProfile.GetProfile(); // current logged in user profile
if (anonymousProfile.LastVisit.HasValue)
{
f.LastVisit = anonymousProfile.LastVisit;
f.Save();
}
ProfileManager.DeleteProfile(args.AnonymousID);
AnonymousIdentificationModule.ClearAnonymousIdentifier();
Membership.DeleteUser(args.AnonymousID, true);
}

Resources