Can't get custom ASP MVC 4 WebAPI MembershipProvider to be called - asp.net-mvc

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/

Related

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.

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.

SimpleMembership with SQL Server Compact (SDF) - code first with existing DB

Probably I'm missing something obvious but I cannot manage to get an SDF-based ASP.NET MVC 4 web app work with the new simple membership. I detail my steps so this can be useful as a reference for other newbies.
To start with, I found this very useful intro to the new membership system: http://weblogs.asp.net/jgalloway/archive/2012/08/29/simplemembership-membership-providers-universal-providers-and-the-new-asp-net-4-5-web-forms-and-asp-net-mvc-4-templates.aspx. My steps in a code-first with existing database (the SDF is a temporary placeholder for a full-fledged existing SQL Server db) were as follows:
I created a new internet app with VS 2012.
I added a new SDF file to App_Data (Accounts.sdf) and created there my tables for users and roles.
I added a new connection string to web.config:
<connectionStrings>
<clear/>
<add name="AccountsContext" connectionString="Data Source=|DataDirectory|\Accounts.sdf;Persist Security Info=False" providerName="System.Data.SqlServerCe.4.0" />
</connectionStrings>
I changed the InitializeSimpleMembershipAttribute.cs file to use my own datacontext, which is hosted in an intermediate data layer; here I paste the few relevant changes I made to the template code:
...
public SimpleMembershipInitializer()
{
Database.SetInitializer(null);
try
{
using (AccountsContext context = new AccountsContext())
{
if (!context.Database.Exists())
{
((IObjectContextAdapter)context).ObjectContext.CreateDatabase();
}
}
WebSecurity.InitializeDatabaseConnection("AccountsContext", "User", "Id", "Name", autoCreateTables: true);
// seed data here...
}
...
Here it is the data context (notice the connection string name in the default ctor):
public sealed class AccountsContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
public AccountsContext() : base("Name=AccountsContext")
{
Database.Initialize(false);
}
public AccountsContext(string nameOrConnectionString) : base(nameOrConnectionString)
{
Database.Initialize(false);
}
public AccountsContext(DbConnection connection, bool contextOwnsConnection) :
base(connection, contextOwnsConnection)
{
Database.Initialize(false);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
// user
modelBuilder.Entity<User>().Property(u => u.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
// role
modelBuilder.Entity<Role>().Property(r => r.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Role>().ToTable("webpages_Roles");
modelBuilder.Entity<Role>().Property(r => r.Id).HasColumnName("RoleId");
modelBuilder.Entity<Role>().Property(r => r.Name).HasColumnName("RoleName");
// user-role
modelBuilder.Entity<User>()
.HasMany(u => u.Roles)
.WithMany(r => r.Users)
.Map(m =>
{
m.MapLeftKey("UserId");
m.MapRightKey("RoleId");
m.ToTable("webpages_UsersInRoles");
});
}
}
Now when I run the web app I immediately get an exception telling me that the LocalSqlServer connection name was not found. This belongs to the machine.config where I can find these entries:
...
<membership>
<providers>
<add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" connectionStringName="LocalSqlServer" .../>
<add name="MySQLMembershipProvider" type="MySql.Web.Security.MySQLMembershipProvider, MySql.Web, Version=6.5.4.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" connectionStringName="LocalMySqlServer" ... autogenerateschema="true"/>
</providers>
...
Thus I tried to override these entries by adding these lines to my web.config:
<roleManager enabled="true" defaultProvider="SimpleRoleProvider">
<providers>
<clear/>
<add name="SimpleRoleProvider" type="WebMatrix.WebData.SimpleRoleProvider, WebMatrix.WebData"/>
</providers>
</roleManager>
<membership defaultProvider="SimpleRoleProvider">
<providers>
<clear/>
<add name="SimpleMembershipProvider" type="WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData"/>
</providers>
</membership>
If I run the app now I get an exception like this:
System.Configuration.ConfigurationErrorsException
Message=Default Membership Provider could not be found.
Source=System.Web
BareMessage=Default Membership Provider could not be found.
Yet, the web app has references to both WebMatrix.Data and WebMatrix.WebData (both version 2), already set by the VS template.
So how could I let this work?
In my current mvc 4 project with mssql,
its a simple one i so I just wanted very simple memmbership provider
I disabled
InitializeSimpleMembershipAttribute
by
[Authorize]
//[InitializeSimpleMembership]
public partial class AccountController : Controller
and added this code to global.asax under Application_Start
so you dont need anymore SimpleMembershipInitializer
WebSecurity.InitializeDatabaseConnection(
connectionStringName: "DefaultConnection",
userTableName: "UserProfile",
userIdColumn: "UserID",
userNameColumn: "UserName",
autoCreateTables: true);
in my sql database the application created some tables on of them was Roles and UserInRoles just added the roles I needed like Admin, customer, etc...
you can do the same with your database or build some interface to manage roles and memmbership.
and I restrict the access to some Controllers or Actions by adding this code
[Authorize(Roles = "Admin")]
public class MessagesController : Controller
Could not say this is obvious, yet it does work.
In order to have SimpleMembeship working with SQL Compact, the very first thing you need to do is to add with Nuget "EntityFramework.SqlServerCompact" and "Microsoft ASP.NET Universal Providers Core Library"
The name of your connection string being "AccountsContext", you should have the same in your Model:
public class UsersContext : DbContext
{
public UsersContext()
: base("AccountsContext")
{
}
public DbSet<UserProfile> UserProfiles { get; set; }
public DbSet<webpages_Membership> Membership { get; set; }
}
Then, your section should not have anything SQL related, as this is for ASP security, not the new SimpleMembership; here is how mine looks like:
<membership defaultProvider="simple">
<providers>
<clear />
<add name="simple" type="WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData" />
</providers>
</membership>

ASP.NET MVC 3 user Profiles not being generated

For my ASP.NET MVC 3 app (using Razor) my web.config has this:
<profile>
<providers>
<clear/>
<add name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="ApplicationServices" applicationName="/" />
</providers>
<properties>
<add name="FirstName"/>
<add name="LastName"/>
</properties>
</profile>
It is my understanding from reading the docs that ASP.NET will automatically generate properties off the HttpContext so that I can access these like this:
// MyController.cs
Email = u.Email;
FirstName = HttpContext.Profile.FirstName;
LasttName = HttpContext.Profile.LastName;
However, the compiler is bitching that .FirstName and .LastName don't exist.
What is going on here?
Try this approach:
Create a class (name it UserProfile for example) extending ProfileBase
Add your public properties Email etc (don't forget {get;set;})
Change your Web.config like the following (change the inherits to your namespace)
You should be able to access it now via (UserProfile)HttpContext.Current.Profile
Your Web.config:
<profile defaultProvider="AspNetSqlProfileProvider" inherits="Namespace.To.Your.UserProfile">
<providers>
<clear/>
<add name="AspNetSqlProfileProvider" connectionStringName="ConnectionStringName" type="System.Web.Profile.SqlProfileProvider" applicationName="App" />
</providers>
</profile>
Your profile class:
public class UserProfile : ProfileBase
{
public string Email
{
get { return (string)GetPropertyValue("Email"); }
set { SetPropertyValue("Email", value); }
}
}
When using ASP.NET Web Forms, the profile data is accessed through a proxy object whose properties correspond to the profile properties. This feature isn't available for MVC Framework applications.
u can get user profile property value just like this:
public ActionResult Index()
{
ViewBag.Name = HttpContext.Profile["Name"];
ViewBag.City = HttpContext.Profile.GetProfileGroup("Address")["City"];
return View();
}
it will return current logged in user profile property value.

Implementing Profile Provider in ASP.NET MVC

For the life of me, I cannot get the SqlProfileProvider to work in an MVC project that I'm working on.
The first interesting thing that I realized is that Visual Studio does not automatically generate the ProfileCommon proxy class for you. That's not a big deal since it's simpy a matter of extending the ProfileBase class. After creating a ProfileCommon class, I wrote the following Action method for creating the user profile.
[AcceptVerbs("POST")]
public ActionResult CreateProfile(string company, string phone, string fax, string city, string state, string zip)
{
MembershipUser user = Membership.GetUser();
ProfileCommon profile = ProfileCommon.Create(user.UserName, user.IsApproved) as ProfileCommon;
profile.Company = company;
profile.Phone = phone;
profile.Fax = fax;
profile.City = city;
profile.State = state;
profile.Zip = zip;
profile.Save();
return RedirectToAction("Index", "Account");
}
The problem that I'm having is that the call to ProfileCommon.Create() cannot cast to type ProfileCommon, so I'm not able to get back my profile object, which obviously causes the next line to fail since profile is null.
Following is a snippet of my web.config:
<profile defaultProvider="AspNetSqlProfileProvider" automaticSaveEnabled="false" enabled="true">
<providers>
<clear/>
<add name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" connectionStringName="ApplicationServices" applicationName="/" />
</providers>
<properties>
<add name="FirstName" type="string" />
<add name="LastName" type="string" />
<add name="Company" type="string" />
<add name="Phone" type="string" />
<add name="Fax" type="string" />
<add name="City" type="string" />
<add name="State" type="string" />
<add name="Zip" type="string" />
<add name="Email" type="string" >
</properties>
</profile>
The MembershipProvider is working without a hitch, so I know that the connection string is good.
Just in case it's helpful, here is my ProfileCommon class:
public class ProfileCommon : ProfileBase
{
public virtual string Company
{
get
{
return ((string)(this.GetPropertyValue("Company")));
}
set
{
this.SetPropertyValue("Company", value);
}
}
public virtual string Phone
{
get
{
return ((string)(this.GetPropertyValue("Phone")));
}
set
{
this.SetPropertyValue("Phone", value);
}
}
public virtual string Fax
{
get
{
return ((string)(this.GetPropertyValue("Fax")));
}
set
{
this.SetPropertyValue("Fax", value);
}
}
public virtual string City
{
get
{
return ((string)(this.GetPropertyValue("City")));
}
set
{
this.SetPropertyValue("City", value);
}
}
public virtual string State
{
get
{
return ((string)(this.GetPropertyValue("State")));
}
set
{
this.SetPropertyValue("State", value);
}
}
public virtual string Zip
{
get
{
return ((string)(this.GetPropertyValue("Zip")));
}
set
{
this.SetPropertyValue("Zip", value);
}
}
public virtual ProfileCommon GetProfile(string username)
{
return ((ProfileCommon)(ProfileBase.Create(username)));
}
}
Any thoughts on what I might be doing wrong? Have any of the rest of you successfully integrated a ProfileProvider with your ASP.NET MVC projects?
Thank you in advance...
Here's what you need to do:
1) In Web.config's section, add "inherits" attribute in addition to your other attribute settings:
<profile inherits="MySite.Models.ProfileCommon" defaultProvider="....
2) Remove entire <properties> section from Web.config, since you have already defined them in your custom ProfileCommon class and also instructed to inherit from your custom class in previous step
3) Change the code of your ProfileCommon.GetProfile() method to
public virtual ProfileCommon GetProfile(string username)
{
return Create(username) as ProfileCommon;
}
Hope this helps.
Not sure about the whole question, but one thing I noticed in your code:
ProfileCommon profile = (ProfileCommon)ProfileCommon.Create(user.UserName, user.IsApproved) as ProfileCommon;
You do not need both the (ProfileCommon) and the as ProfileCommon. They both do casts, but the () throws and exception while the as returns a null if the cast can't be made.
Try Web Profile Builder. It's a build script that automagically generates a WebProfile class (equivalent to ProfileCommon) from web.config.
The web.config file in the MVC Beta is wrong. The SqlProfileProvider is in System.Web.Profile, not System.Web.Security. Change this, and it should start working for you.

Resources