In an MVC5 application I am using Windows Authentication and wanted to use our Active Directory Groups as roles as this is strictly and intranet application. I am using the WindowsTokenRoleProvider as so:
<roleManager defaultProvider="WindowsProvider" enabled="true" cacheRolesInCookie="false">
<providers>
<add name="WindowsProvider" type="System.Web.Security.WindowsTokenRoleProvider" applicationName="/" />
</providers>
</roleManager>
I have tried a few other variations of that config including using the cookie cache, but the end result is that it takes ~20 seconds per group check. I check role like this:
User.IsInRole(#"Domain\UserName")
Is there something I have completely missed in setting this up? I can't believe it would be normal for authentication to take 20 seconds per check. The user I am checking is in ~50 groups, but I didn't think that would be enough to slow it down so much.
So best solution at this point seems to be this stackoverflow question
I will probably play around with how the roles get checked/added to see if I can pinpoint the issue, but I wouldn't expect this slowness from only being in 50 groups.
UPDATE:
So while actually enumerating my groups I found my user was in over 400 groups which might explain why it took so long. I still don't under stand why the IsInRole method on user would call GetRolesForUser instead of just calling IsUserInRole directly, but this makes things exponentially faster
UPDATE 2:
Old answer was deleted so here is my class:
public class CustomWindowsTokenRoleProvider : WindowsTokenRoleProvider
{
public override string[] GetRolesForUser(string username)
{
List roles = null;
string key = String.Concat(username, ":", base.ApplicationName);
Cache cache = HttpContext.Current.Cache;
if (cache[key] != null)
{
roles = new List(cache[key] as string[]);
}
if (roles == null)
{
roles = new List();
// AppSettings.APPLICATION_GROUPS is an IEnumerable that returns just the groups that my application is interested in checking against
foreach (string role in AppSettings.APPLICATION_GROUPS)
{
if (base.IsUserInRole(username, role));
{
roles.Add(role);
}
}
cache.Insert(key, roles.ToArray(), null, DateTime.Now.AddHours(1), Cache.NoSlidingExpiration);
}
return roles.ToArray();
}
}
Related
For the life of me I cannot figure out how to simply add roles to my MVC 5 application. This was such a breeze in MVC 4.
The tables are there out of the box,
AspNetRoles, which has an Id and Name ("Admin", "User", etc...)
AspNetUsers, which has an Id and other user fields
AspNetUserRoles - this table has a UserId and RoleId, which is mapped to the tables above.
I want to be able to check to see if a signed in user has the "admin" role or just a "user" role.
Here is what I have tried:
if (User.IsInRole("Admin"))
{
//do something
}
The value always come back false.
I also tried:
var UserManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(context));
var userRole = UserManager.GetRoles(user.GetUserId());
if (userRole[0] == "Admin")
{
// do something
}
This throws error :
"Index was out of range. Must be non-negative and less than the size
of the collection."
Is there a simple method that can get the role for the current logged in user? I have seen references to using Role Manager, but I have been unable to get that to work. I believe that was for Identity 1.0
Thanks
The error is telling you that userRole is empty, as even an index of 0, is not "less than the size of the collection". That means, the user in question does not belong to any roles.
The methodology for this is really not much different than it ever has been. The role must exist, first, and the user needs to be associated with that role. As a result, the first step is to populate your roles:
db.Roles.Add(new Role { Name = "Admin" });
You can use RoleManager for this, but I find that to be overkill when you can just utilize the context directly.
Then:
UserManager.AddToRole(userId, "Admin");
That's literally all there is to it. If you like, you can override Seed in Migrations\Configuration.cs to seed some users/roles into the database automatically. For roles, that would just look like:
context.Roles.AddOrUpdate(
r => r.Name,
new Role { Name = "Admin" },
...
);
I had a similar problem. Using MySQL and I had this in my config file:
<providers>
<provider invariantName="MySql.Data.MySqlClient" type="MySql.Data.MySqlClient.MySqlProviderServices, MySql.Data.Entity.EF6, Version=6.9.6.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d"></provider>
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
</providers>
changed to:
<providers>
<provider invariantName="MySql.Data.MySqlClient" type="MySql.Data.MySqlClient.MySqlProviderServices, MySql.Data.Entity.EF6, Version=6.9.6.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d"></provider>
</providers>
I have production IIS server with some ASP.NET MVC app. I got some tricky bug which I can't capture. It's linked to session data. How can I export/see/view such user session? There is default IIS configuration for session storing -- in-process.
EDIT
By the way I have necessary appropriate user session ID.
EDIT2
Ok, guys, so even if I can't export that data right now, could you please point me at some session state server or something similar, which I can use for storing session data and view it further?
I kniw SQL Server can, but it is very heavy for such issue.
Chris is right following on his Idea, you could write a routine that would output the content of your session objects to a file (a kind of a custom log).
//Controller Action where you store some objects in session
public ActionResult Index()
{
var myObj = new { strTest = "test string", dtTestValue = DateTime.Now, listTest = new List<string>() { "list item 1", "list item 2", "list item 3" }};
Session["test1"] = "Test";
Session["test2"] = myObj;
return View();
}
//Controller Action where you output session objects to a file
[HttpPost]
public ActionResult Index(FormCollection form)
{
//Routine to write each sessionObject serialized as json to a file
foreach (string key in Session.Keys)
{
var obj = Session[key];
JavaScriptSerializer serializer = new JavaScriptSerializer();
using (System.IO.StreamWriter file = new System.IO.StreamWriter(#"C:\Users\Public\CustomAspNetLog.txt", true))
{
file.WriteLine(DateTime.Now.ToString() + "\t" + serializer.Serialize(obj));
}
}
return View();
}
If you need to call that routine often, you can put it in some helper class and call it whenever you want in your controller actions. Then you are able to inspect true data inside Session at every step you find necessary.
No, you will need to write a routine to export the session data as and when required.
KSeen
There is a better option to store sessions than a StateServer i.e Distributed Cache provider.
Alachisoft provides NCache Express which is totally free.You can use it to store your sessions.Here is how you do it.
Install NCache on each web server.
Define a distributed cache: Make sure you test the distributed cache to ensure it is properly working.
Modify web.config file: to add the SessionState Provider information and the name of the cache you've just created.
<sessionState cookieless="false" regenerateExpiredSessionId="true"
mode="Custom"
customProvider="NCacheSessionProvider" timeout="1">
<providers>
<add name="NCacheSessionProvider"
type="Alachisoft.NCache.Web.SessionState.
NSessionStoreProvider"
cacheName="myreplicatedcache"
writeExceptionsToEventLog="false"
AsyncSession="false"/>
</providers>
</sessionState>
Please note that Version=3.2.1.0 should match the specific NCache Express version you've downloaded. Once you do this, you're ASP.NET application is ready to start using distributed sessions.
I'm following this article in which is described how to assign roles to users when theiy log-in using forms authentication:
public void Application_AuthenticateRequest( Object src , EventArgs e )
{
if (!(HttpContext.Current.User == null))
{
if (HttpContext.Current.User.Identity.AuthenticationType == "Forms" )
{
System.Web.Security.FormsIdentity id;
id = (System.Web.Security.FormsIdentity)HttpContext.Current.User.Identity;
String[] myRoles = new String[2];
myRoles[0] = "Manager";
myRoles[1] = "Admin";
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(id,myRoles);
}
}
}
I put the role logic in the event handler, so I basically don't need a role provider. Nonetheless, in order to run this, appears that I must enable Role Provider in web.config. Sadly, if I just put:
<roleManager enabled="true"/>
it results in runtime errors related to a failed connection to the SQL server, like if I chose AspNetSqlRoleProvider as Role Provider.
What should I do to have roles working this way? How can I choose to use no role provider, or how should I implement a dummy one (if it makes any sense)?
You shouldn't need to enable roleManager in web.config - after all, people used to use roles with .NET 1.x before roleManager came along.
One thing that roleManager will do for you that you haven't done in your code is set Thread.CurrentPrincipal to HttpContext.Current.User. If you're relying on this (e.g. using PrincipalPermissionAttribute), then you need to add this:
Thread.CurrentPrincipal = HttpContext.Current.User;
Otherwise, I'd expect it to work: what symptoms are you seeing that makes you think it isn't working?
As for implementing a dummy RoleProvider, it's easy enough: for example see this MSDN article.
You only need to implement the GetRolesForUser and IsInRole methods; the other methods can simply throw NotSupportedException.
I'm building my site, and I want to restrict a part of my site (The admin parts) from normal public display.
I am using LINQ for database access.
I have a Service class to handle calls to the database through LINQ
I have the whole site running, except for the Login part.
So far I have only been able to find examples using MembershipProvider and/or RoleProviders etc. And to be honest, it does seem like too much work for what I want. All this has to do is to let you in if you type the correct password in the input fields.
Can i really not avoid the Providers?
Since you only have a single user you don't need to create a database dependency. You can make a very simple authorization service based off of a hard coded credentials. For example,
public class AuthorizationService{
private AuthorizationService(){}
public static readonly AuthorizationService Instance = new AuthorizationService();
private const string HardCodedAdminUsername = "someone";
private const string HardCodedAdminPassword = "secret";
private readonly string AuthorizationKey = "ADMIN_AUTHORIZATION";
public bool Login(string username, string password, HttpSessionStateBase session){
if(username.ToLowerInvariant().Trim()==HardCodedAdminUsername && password.ToLowerInvariant().Trim()==HardCodedAdminPassword){
session[AuthorizationKey] = true;
return true;
}
return false;
}
public void Logout(HttpSessionStateBase session){
session[AuthorizationKey] = false;
}
public bool IsAdmin(HttpSessionStateBase session){
return session[AuthorizationKey] == true;
}
}
Then you can build a custom IAuthorizationFilter like:
public class SimpleAuthFilterAttribute: FilterAttribute, IAuthorizationFilter{
public void OnAuthorization(AuthorizationContext filterContext){
if(!AuthorizationService.Instance.IsAdmin(filterContext.HttpContext.Session)){
throw new UnauthorizedAccessException();
}
}
}
Then all you have to do is decorate the protected controller actions with the SimpleAuthFilter and you're application's login suddenly works. Yay! (Note, I wrote all this code in the StackOverflow answer window, so you may need to clean up typos, etc. before it actually works)
Also, you could refactor this to omit the username if you find that unnecessary. You will need to create a controller action for Login and Logout that make the corresponding calls to the AuthorizationService, if you want your protected controller actions to ever be accessible.
Its worth building a light-weight Membership Provider with minimal implementation; GetUser, ValidateUser etc methods. YOu dont need to implement the whole thing. It just helps with authorising pages and checking User.Identity etc when needed. You also dont need the RoleProvider or ProfileProvider to do this.
Its also scalable for the future.
UPDATE
You just need to implement the core methods to valudate and get the user and insert your own validation/data access code.
Something like this....
web.config settings:
<membership defaultProvider="ApplicationMembershipProvider">
<providers>
<clear/>
<add name="ApplicationMembershipProvider" type="YourNamespace.ApplicationMembershipProvider"/>
</providers>
</membership>
Login Code:
if (Membership.ValidateUser(username, password))
{
FormsAuthentication.SetAuthCookie(username, false);
}
You can set the status (logged in or not) in a session variable. Set the variable to true if the user entered the correct password, then on every page you want to restrict access, check if the variable is true.
#KristianB a while ago I gave an answer to this SO question. I believe it may be useful since it's very straightforward to implement and at the same time it's better than hardcoding a username and a password in your code.
Good luck!
Ok, here's my problem, i want to maintain session data between two applications or domains (eg:- www.abc.com and secure.abc.com).
I have read on net about this, but many people pointing many different ways to do it, with people commenting +ve and -ve responses to all. Plus many are just providing theoretical answer, do this and that ,but no code at all.
are these steps all that is required?
1) in web.config: <httpCookies domain=".abc.com"/>
2) store session data in sql DB as:(after preparing the db for storing sessions)
<sessionState mode="SQLServer" sqlConnectionString="Data Source=YourServer;
Integrated Security=True;database=MySessionDB" sqlCommandTimeout="30"
allowCustomSqlDatabase="true"/>
<machineKey decryption="AES" validation="SHA1" decryptionKey="..." validationKey="..." />
3)Am confused about this one: i want to set the domain for the session cookie like this
Response.Cookies["ASP.NET_SessionId"].Domain = ".abc.com";
But where should this code be written?
this entry: http://mgrzyb.blogspot.com/2007/12/aspnet-and-subdomains.html says: use System.Web.SessionState.SessionIDManager as a base class but the SaveSessionID method is not virtual so cannot be overridden. Options are: either explicitly re-implement the interface method or decorate SessionIDManager class and after calling SessionIDManager.SaveSessionID set Response.Cookies[SessionIdCookieName].Domain to our domain.
Only if the author had provided real code, step 3 would have been clear.
Can anyone provide the code for it.
Plus all this 3 steps enough to share session among the domains?
the 3rd step statement can be written in global.asax according to: http://www.know24.net/blog/ASPNET+Session+State+Cookies+And+Subdomains.aspx
protected void Application_PreRequestHandlerExecute(Object sender, EventArgs e)
{
/// only apply session cookie persistence to requests requiring session information
#region session cookie
if (Context.Handler is IRequiresSessionState || Context.Handler is IReadOnlySessionState )
{
/// Ensure ASP.NET Session Cookies are accessible throughout the subdomains.
if (Request.Cookies["ASP.NET_SessionId"] != null && Session != null && Session.SessionID != null)
{
Response.Cookies["ASP.NET_SessionId"].Value = Session.SessionID;
Response.Cookies["ASP.NET_SessionId"].Domain = ".abc.com"; // the full stop prefix denotes all sub domains
Response.Cookies["ASP.NET_SessionId"].Path = "/"; //default session cookie path root
}
}
#endregion
}