Custom Role Provider not called. What am I doing wrong? - asp.net

So I'm trying to create a Hello World custom Role Provider-solution in ASP.NET MVC 4.
Basically I've set authentication mode="Windows" in web.config along with defining a role provider like this:
<roleManager enabled="true" defaultProvider="MyRoleProvider">
<providers>
<clear />
<add name="MyRoleProvider" type="MyProject.Code.MyRoleProvider" />
</providers>
</roleManager>
Then I've decorated the About controller method like this:
[Authorize(Roles = "SomeRole")]
public ActionResult About()
{ /* ... */ }
The custom RoleProvider-class looks like this:
namespace MyProject.Code {
public class MyRoleProvider : RoleProvider
{
public override bool IsUserInRole(string username, string roleName)
{
if (roleName == "SomeRole" && username = "Administrator") return true;
return false;
}
public override string[] GetRolesForUser(string username)
{
return new string[] { "SomeRole" };
}
/* a bunch of other overridden methods simply throwing not implementedException */
}
}
Now the thing is I've put a breakpoint on every single executable line of code in MyRoleProvder but none are hit. I have tested that breakpoints elsewhere are hit so the debugger is not the problem. Why isn't my code in the role provided executed? I was expecting IsUserInRole and/or GetRolesForUser to be executed when I navigate to the About-page. Am I wrong? Have I configured something wrong?
Full web.config for reference
edit: The user is redirected to the login page when the about page is clicked. I now realize this is due to the user actually is not authenticated yet. Authorization naturally happens after authentication. Is IISExpress not providing Windows identity?

As this is the first result in a google search i want to give a solution, maybe for others:
To use the windows identity in IIEExpress you have to activate it in your applicationhost.config , which is located in
[SolutionDir].vs\config\applicationhost.config
there you can find the xml tag
<configuration>
<system.webServer>
<security>
<authentication>
<windowsAuthentication enabled="false">
change enabled="false" to enabled="true"
and you can use windows authenticatuion in IISExpress

I think your type declaration is incomplete, you should include both the full name and the assembly name.
type="MyProject.Code.MyRoleProvider, MyProject"
You might also need to set Version, Culture and PublicKeyToken if your assemblies are place in the GAC
Hope this helps

Related

SignalR /negotiate is making requests to /Account/Login - I have no Account/Login endpoint

I'm seeing lots of entries in my logs from this request:
/signalr/negotiate
The error is:
The controller for path '/Account/Login' was not found or does not implement IController
I have a JS client connecting to an AppHub that requires authentication:
[Authorize]
[HubName("appHub")]
public class AppHub : Hub
{
// content
}
This is happening because there's an 'signalr` session alive with an expired cookie attempting to connect:
I'm not sure why the request is automatically seeking out this page. It's not specified anywhere in my web.config, routes, or elsewhere. Why is this happening?
I'd like to prevent the signalR client from attempting to connect if the user is unauthenticated. How can this be achieved?
If I understand your issue correctly then you are going to want to create your own custom class to handle this by inheriting the AuthorizeAttribute class: https://msdn.microsoft.com/en-us/library/system.web.mvc.authorizeattribute(v=vs.118).aspx
For example:
public class MyCustAuthorize : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Result = new HttpUnauthorizedResult();
}
else
{
//modify this to do whatever you want to happen if unauthorized
filterContext.Result = new RedirectResult("/session/noaccess/");
}
}
}
Then you can decorate it with your custom class instead of the default authorize
(example is from a mvc controller but should be able to function the same on your hub)
So this:
[Authorize]
public class AdminController : Controller
{
Becomes this:
[MyCustAuthorize]
public class AdminController : Controller
{
I believe the /Account/Login is the default path for forms auth so that is why it is directing there if it is not defined within your config file.
Alternatively you could insert the specific url to redirect to if that is what you are searching for by placing the following loginUrl attribute value in your auth section > forms element in the web.config:
It looks like this may be similar to these other answers to questions already asked here and these may provide your solution:
Stackoverflow 1
Stackoverflow 2
Have you tried stopping the connection in the client when they are no longer authorized?
$.connection.hub.stop();
Your app is using FormsAuthentication, so the Authorize attribute is redirecting to the login page by default when it fails to authorize.
You can disable this by adding the following to your web.config:
<modules runAllManagedModulesForAllRequests="true">
<remove name="FormsAuthentication" />
</modules>
This will remove all the default behaviors.
You might have something in your app.config that looks like
<membership defaultProvider="ClientAuthenticationMembershipProvider">
<providers>
<add name="ClientAuthenticationMembershipProvider" type="System.Web.ClientServices.Providers.ClientFormsAuthenticationMembershipProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="" />
</providers>
</membership>
Which is what is adding these default behaviors for you.

ProfileCommon.GetProfile(string)' hides inherited member '.UserProfile.GetProfile(string)'. Use the new keyword if hiding was intended

I am using aspnet membership profile and Inherited the profileBase class to a class name UserProfile, where I have defined the method GetProfile. Everything working fine But while build getting the above warning.
Please help how to remove the warning.
Below the sample codes.
public static UserProfile GetProfile(string userName)
{
return (UserProfile)Create(userName);
}
public static UserProfile GetProfile(string username, bool authenticated)
{
return (UserProfile)Create(username, authenticated);
}
public static UserProfile Current()
{
return ((UserProfile)(HttpContext.Current.Profile));
}
<!-- Profile configuration -->
<profile enabled="true" defaultProvider="EFProfileProvider" inherits="Jan.DB.Provider.UserProfile" automaticSaveEnabled="true">
<providers>
<clear/>
<add name="EFProfileProvider" type="Jan.DB.Provider.EFProfileProvider" connectionStringName="JanEntities" applicationName=""/>
</providers>
</profile>
While migrating from simple membership to Identity we had made some mistake. Although we have removed all the membership related codes. One thing we have done is how profile is handled. I mean in idenity customization we have used the old UserProfile.cs of membership with some customization. But as we are using
in web.config so at runtime Asp.net memebrship automatically creates the ProfileCommon.cs and for that we are getting that warning.
so can avoid that warning by cleaning of the customization and moved to how identity handles the profile information.

Need help getting [Authorize(Roles="Admin")] and User.isInRole("Admin") working in Asp.Net identity 1

I just don't get how these two potentially incredibly useful functions are supposed to work.
[Authorize(Roles="Admin")] //Method A
and
User.isInRole("Admin") //Method B
They are clearly not working right now for me. I did a few hours of research:
I read that you need to implement System.Web.Security.RoleProvider, then set it up in the web config:
<roleManager defaultProvider="OdbcRoleProvider"
enabled="true"
cacheRolesInCookie="true"
cookieName=".ASPROLES"
cookieTimeout="30"
cookiePath="/"
cookieRequireSSL="false"
cookieSlidingExpiration="true"
cookieProtection="All" >
<providers>
<clear />
<add
name="OdbcRoleProvider"
type="Samples.AspNet.Roles.OdbcRoleProvider"
connectionStringName="OdbcServices"
applicationName="SampleApplication"
writeExceptionsToEventLog="false" />
</providers>
</roleManager>
This caused the RoleProvider I implemented to by constructed, but the role checking functions were certainly not calling any of the methods in there.
I then read that Asp.NET Identity does away with the RoleProvider, now you need to do this:
<modules runAllManagedModulesForAllRequests="true">
<remove name="FormsAuthenticationModule" />
<remove name="RoleManager" />
</modules>
And I did that.
I have a custom UserManager that connects to my postgres backend. The problem is that whenever I use it, I need to instantiate one. It seems to me that if Functions A and B are going to work, then the UserManager I have implemented needs to be referenced in some sort of config file so Asp.NET knows about it implicitly. This would be just like the RoleManager in the past.
How does ASP.NET identity alter how Functions A and B check the roles from the old RoleProvider using behavior?
I figured it out.
When you call the login code like this:
var user = await UserManager.FindAsync(model.Email, model.Password);
if (user != null && user.PasswordRequiresReset == false)
{
await SignInAsync(user, model.RememberMe); //triggers IUserRoleStore.GetRolesAsync
return RedirectToLocal(returnUrl);
}
SignInAsync triggers GetRolesAsync from the IUserRoleStore:
public Task<IList<string>> GetRolesAsync(TUser user) //where TUser is an ApplicationUser
{
Func<object, IList<string>> getRoles = (object user2) =>
{
TUser user3 = (TUser)user2;
return user3.Roles.Select(x => x.Role.Name).ToList();
};
return Task.Factory.StartNew(getRoles, user);
}
In the above, I chose to simply generate the roles from the roles I already loaded from the db and stored in the ApplicationUser object when I created it in FindAsync.
The roles from GetRolesAsync must be loaded in the cookie somewhere where they can be accessed quickly and easily by Functions A and B.

You must call the "WebSecurity.InitializeDatabaseConnection" method before you call any other method of the "WebSecurity" class

I can't make WebSecurity object work anywhere except what's already been generated in AccountController.cs file. Account controller has [InitializeSimpleMembership] attribute set at the top. Login functions don't complain about calling WebSecurity.Login(...), for example. I added a child action to AccountController:
[ChildActionOnly]
[AllowAnonymous]
public ActionResult NavBar()
{
NavBarViewModel viewModel = new NavBarViewModel();
viewModel.LinkItems = new List<NavBarLinkItem>();
if (Request.IsAuthenticated)
{
SimpleRoleProvider roleProvider = new SimpleRoleProvider();
if (roleProvider.IsUserInRole(User.Identity.Name, "User"))
{
viewModel.LinkItems.Add(new NavBarLinkItem()
{ Title = "Create Project", Action = "Create", Controller = "Project" });
}
}
viewModel.LinkItems.Add(new NavBarLinkItem() { Title="Help", Action="Index", Controller="Help" });
return PartialView("_NavBar", viewModel);
}
Left as is, the code crashes on "if (roleProvider.IsUserInRole(User.Identity.Name, "User"))" line with the subject error message. So I go into InitialzeSimpleMembershipAttribute.cs file and copy/paste this line at the top of my function:
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);
... and get an error message that WebSecurity.InitializeDatabaseConnection should only be called once. This makes sense, because there is an attribute at the top of the controller definition that should've called this function already (and it seems it does that just fine). So to be safe, I change above call to:
if (!WebSecurity.Initialized)
{
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId",
"UserName", autoCreateTables: true);
}
... and get back the original error message, that WebSecurity.InitializeDatabaseConnection should be called before blah blah blah. Any insight into this madness would be greatly appreciated
There's a better explanation here:
http://odetocode.com/blogs/scott/archive/2012/09/24/perils-of-the-mvc4-accountcontroller.aspx
Here's all you have to do:
Remove [InitializeSimpleMembership] from the top of AccountController
Copy the WebSecurity.InitializeDatabaseConnection(...) call from /Filters/InitializeSimpleMembershipAttribute.cs (line 39) to /AppStart/AuthConfig.cs
Feel free to remove InitializeSimpleMembershipAttribute.cs from your project
You don't have to add the InitializeDatabaseConnection() call to AuthConfig.RegisterAuth() but it seems like the logical place and keeps your Global.asax cleaner.
What you are essentially doing is extracting the initialization call from the original attribute and explicitly calling it on Application_Start. Everything else in the attribute is just conditional checking in case you aren't using (or don't need) SimpleMembership.
Adding the [InitializeSimpleMembership] (as mentioned and found in the AccountController) to the top of the controller I needed to access WebSecurity from did the trick for me. Not sure however if its the intended implementation method though...
[InitializeSimpleMembership]
public class DataController : Controller
{ ... }
I found this on the interwebs: http://forums.asp.net/t/1718361.aspx/1
Basically, don't use SimpleRoleProvider type. There is a Roles object available that allows simple calls like this:
if (Request.IsAuthenticated)
{
if( Roles.IsUserInRole(User.Identity.Name, "User"))
{
viewModel.LinkItems.Add(new NavBarLinkItem()
{ Title = "Create Project", Action = "Create", Controller = "Campaign" });
}
}
First you have to set your role and membership provider in the web.config:
<authentication mode="Forms">
<forms loginUrl="/Account/Login" slidingExpiration="true" timeout="60" />
</authentication>
<membership defaultProvider="p1">
<providers>
<add name="p1" type="WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData"/>
</providers>
</membership>
<roleManager enabled="true" defaultProvider="p1">
<providers>
<add name="p1" type="WebMatrix.WebData.SimpleRoleProvider, WebMatrix.WebData"/>
</providers>
</roleManager>
When you create a new instance of SimpleRoleProvider, use the non-null constructor, and supply the default RoleProvider set in the web.config as an argument:
SimpleRoleProvider srp = new SimpleRoleProvider(Roles.Provider);
The solution is the same in the case of SimpleMembershipProvider:
SimpleMembershipProvider msp = new SimpleMembershipProvider(Membership.Provider);
In my case I had to disable Anonymous Authentication within the IIS Authentication setting.
Then I had to enable Forms and Windows Authentication. This will of course depend on whichever authentication you require for your app.
Once I did that the error went away and the app worked as expected.

Using Routing without MVC: authentication form

Now I'm trying to work with System.Web.Routing. All is just fine, but I can't understand how to make form authentication work with url routing (return url, redirection, etc). Google says nothing. Help! :)
UPD: I forgot - I don't use MVC. That's the problem. How to use rounig and form authentication without MVC
UPD2: more about my problem
What I want to get: urls such “mysite.com/content/123”, “mysite.com/login/”, etc using Routes. It’s important to make login page works like “regular” ASP.NET login form (redirects to login from secure area when not login on, and redirect back to secure area when loggined).
That’s what I’m doing.
In global.asax on Application_Start, register routes like this:
routes.Add("LoginPageRoute", new Route("login/", new CustomRouteHandler("~/login.aspx")));
routes.Add("ContentRoute", new Route("content/{id}", new ContentRoute("~/content.aspx"))
{
Constraints = new RouteValueDictionary {{ "id", #"\d+" }}
});
Where CustomRouteHandler and ContentRoute – simple IRouteHandler classes, just like:
...
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var page = BuildManager.CreateInstanceFromVirtualPath(VirtualPath, typeof(Page)) as IHttpHandler;
return page;
}
...
All seems to be perfect: I’m getting content.aspx when go to “/content/10” and login.aspx when go to “/login/”. But…
When I make content secured (in web.config, with deny=”?”), login form doesn’t work like expected.
Now I can’t reach the “/content/10” page:
0. I’m typing “/content/10” in my browser.
1. Site redirects to “/login/?ReturnUrl=%2fcontent%2f10”. (Hm… seems like all problems starts here, right? :)
2. I’m trying to log in. No matter what credentials I’m entered…
3. …site redirects me to “login?ReturnUrl=%2fContent%2f10” (yellow screen of error - Access is denied. Description: An error occurred while accessing the resources required to serve this request. The server may not be configured for access to the requested URL.)
So, the problem is how to get ASP.NET understand real ReturnUrl and provide redirection after login.
These steps should allow you to implement the required behaviour.
To summarize:
You are using routing but not MVC. My example will map a url like http://host/Mysite/userid/12345 onto a real page at http://host/Mysite/Pages/users.aspx?userid=12345.
You want to control access to these addresses, requiring the user to logon. My example has a page http://host/Mysite/login.aspx with a standard login control, and the site is configured to use forms authentication.
Step 1
I've "hidden" the contents of the Pages folder using this web.config in the Pages folder:
<?xml version="1.0"?>
<configuration>
<system.web>
<httpHandlers>
<add path="*" verb="*"
type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>
<pages validateRequest="false">
</pages>
</system.web>
<system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
<handlers>
<remove name="BlockViewHandler"/>
<add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler"/>
</handlers>
</system.webServer>
</configuration>
This ensures that if anyone uses a url like http://host/Mysite/Pages/users.aspx?userid=12345, then they receive a standard 404 response.
Step 2
My top level web.config file contains (as well as all the standard stuff) this location element:
<location path="userid">
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</location>
This prevents anonymous access to urls of the form http://host/Mysite/userid/12345 which means users will be automatically redirected to login.aspx, then if they provide valid credentials, they will be redirected to the correct location.
Step 3
For reference here is my global.asax:
<script RunAt="server">
void Application_Start(object sender, EventArgs e)
{
// Code that runs on application startup
RegisterRoutes(RouteTable.Routes);
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.RouteExistingFiles = true;
routes.Add("UseridRoute", new Route
(
"userid/{userid}",
new CustomRouteHandler("~/Pages/users.aspx")
));
}
</script>
And here is my route handler:
using System.Web.Compilation;
using System.Web.UI;
using System.Web;
using System.Web.Routing;
using System.Security;
using System.Web.Security;
public interface IRoutablePage
{
RequestContext RequestContext { set; }
}
public class CustomRouteHandler : IRouteHandler
{
public CustomRouteHandler(string virtualPath)
{
this.VirtualPath = virtualPath;
}
public string VirtualPath { get; private set; }
public IHttpHandler GetHttpHandler(RequestContext
requestContext)
{
var page = BuildManager.CreateInstanceFromVirtualPath
(VirtualPath, typeof(Page)) as IHttpHandler;
if (page != null)
{
var routablePage = page as IRoutablePage;
if (routablePage != null) routablePage.RequestContext = requestContext;
}
return page;
}
}
The first result I got from a Google search is Frederiks excellent post on forms authentication in ASP.NET MVC. Note that the post was relevant for an early version of ASP.NET MVC, you will have to write and test the code.
HTH, Indy

Resources