I have just added Piranha CMS to an existing ASP.NET MVC 5 site but am having a strange issue.
If I am logged into the site and then try to access site.com/manager and enter the piranha login details then the page just refreshes and nothing happens (trying with an incorrect password gives me an incorrect password message but correct login details just refreshes page)
If I try accessing site.com/manager without being logged into the site then it logs me in OK the piranha manager area.
Any ideas why this is? Ideally I'd like the user to only need to login once is this possible?
The current version of Piranha CMS doesn't integrate with other authentications. For now the only solution is to keep the user accounts separate like you noticed. This feature has however been planned for a couple of years but hasn't been implemented due to various reasons.
Pulling out the authentication mechanics imposes a lot of changes on the database schema and makes setting user priviliges for specific pages much harder.
Regards
HÃ¥kan
I found that this can be supported with a small change to the handling of the Piranha user class to support both FormsIdentity and ClaimsIdentity. I've posted the fix on the Piranah GitHub Issue log - I'm sure it will get included in a build in the future, but you might want to give the fix a try. With this change I can support MVC4/5 and Piranha (and HTTPS as well).
I use OWIN identity management in my application and switching from my application to Manager correctly asks me to login (Piranha uses Forms). This isn't a problem and I have no problem with the need to have separate logins between application users and content managers.
I noticed a problem under Https where the LocalUserProvider class was expecting to get a FormsIdentity back from the HttpContent.Current.User.Identity but in many cases it was getting a ClaimsIdentity. This caused it to refuse the login and keep cycling round the login form.
I locally modified the LocalUserProvider to add the following method;
/// <summary>
/// Piranha explicitly uses FormsAuthentication - this method is used to replace a calls to HttpContext.Current.User.Identity in this
/// module which would ordinarily return the users Idenity as an ClaimsIdentity, and the Name part of the ClaimsIdentity is the login name
/// not the Forms Authentication Guid expected by Piranha. The OWIN and Forms can co-exist side by side, but when Piranha gets the users
/// identity, it expects to get a Forms Identity.
/// </summary>
/// <returns></returns>
private System.Web.Security.FormsIdentity extractFormsIdentityFromHttpContext()
{
if (HttpContext.Current.User != null)
{
System.Security.Claims.ClaimsPrincipal claimsPrincipal = HttpContext.Current.User as System.Security.Claims.ClaimsPrincipal;
if(claimsPrincipal != null)
{
return claimsPrincipal.Identities.OfType<System.Web.Security.FormsIdentity>().FirstOrDefault();
}
}
return null;
}
and use it like this;
/// <summary>
/// Gets if the current user is authenticated.
/// </summary>
public bool IsAuthenticated {
get {
System.Security.Principal.IIdentity formsIdentity = extractFormsIdentityFromHttpContext();
if (formsIdentity != null && formsIdentity.IsAuthenticated) {
try {
// Check if this user has a Guid id.
var id = new Guid(formsIdentity.Name);
return true;
} catch { }
}
return false;
}
}
This means the LocalUserProvider will always deal with the contexts FormsIdentity explicitly and has the happy side effect of allowing manager login to work under https.
Hope its a useful suggestion
Related
I need to achieve to authenticate users with their domain user/password, if they're are in the domain controller, but the application should be available for other users as well, who should be authenticated with their own username/password; this should be stored in the application database, and their username/password to be checked against the DB.
So far i started with new asp.net template in vs2015, choosing Individual User Accounts.
I'm able to authenticate users agains domain controller, but if that is succeeded I'm unable to store the user to HttpContext.User property.
In SignInManager i call PasswordSignIn and return Success or Failure depending on AD check.
public SignInStatus PasswordSignIn(string userName, string password, bool isPersistent, bool shouldLockout) {
if(AuthenticateAD(userName, password)) {
//
// to create identity/principal and assign to HttpContext.User
//
return SignInStatus.Success;
}
else {
return SignInStatus.Failure;
}
}
public bool AuthenticateAD(string username, string password) {
using(var context = new PrincipalContext(ContextType.Domain, "domainname")) {
return context.ValidateCredentials(username, password);
}
}
thanks for any hint!
The only way this really works is if you create proxy users in your application for users in AD. Essentially, you just set up a script that populates new users/updates existing users based on the data in AD on a schedule (nightly, etc. based on your needs). Then, you're dealing with just one type of user whether they're part of the domain or external. The only change you need to make is to selectively authenticate via AD or via the standard password authentication. Either way, the same user principal is in play.
You can use ADFS and allow users to choose where to authenticate. It is quite trivial to implement using default template. Just like usual login mechanics with Sign-in via google and local account.
I think this is most correct way of doing things, because domain users may end up with Kerberos/Ntlm, if they want, and it lowers complexity of your system.
Here is a WS-Fed example: Using Claims in your Web App is Easier with the new OWIN Security Components
For other stuff you can create app with default template. This app will have external authentication stuff as example.
Given that the FormsAuthentication module fires before a custom http module that handles the OnAuthenticateRequest, I'm curious if one can cancel or invalidate the forms authentication based on my own criteria.
Basically I have a process where the user logs in. After that they get a token. I get the token back after the forms authentication fires upon subsequent requests. What I want to do is then validate that the token hasn't expired against our back end server. If it's expired I need to do something so that they are forced to log back in. My thought was to do something in my OnAuthenticateRequest handler that would get picked up later in the pipeline and force a redirect back to login page or something. Is that possible?
In an ASP.NET MVC application in order to handle custom Authentication and Authorization people usually write custom Authorize attributes. They don't deal with any OnAuthenticateRequest events. That's old school. And by the way if you are going to be doing some custom token authentication why even care about Forms Authentication? Why not replace it?
So:
public class MyAuthorizeAttribute: AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
string token = GetTokenFromHttpContext(httpContext);
if (IsTokenValid(token))
{
// The user has provided a valid token => you need to set the User property
// Obviously here based on the token value you already know which is the
// associated user and potential roles, so you could do additional checks
var identity = new GenericIdentity("john.smith");
var user = new GenericPrincipal(identity, new string[0]);
httpContext.User = user;
return true;
}
// Notice that here we are never calling the base AuthorizeCore method
// but you could call it if needed
return false;
}
private string GetTokenFromHttpContext(HttpContextBase httpContext)
{
// TODO: you know what to do here: get the token from the current HTTP Context
// depending on how the client passed it in: HTTP request header, query string parameter, cookie, ...
throw new NotImplementedException();
}
private bool IsTokenValid(string token)
{
// TODO: You know what to do here: go validate the token
throw new NotImplementedException();
}
}
Now all that's left is to decorate your controllers/actions with this custom attribute instead of using the default one:
[MyAuthorize]
public ActionResult SomeAction()
{
// if you get that far you could use the this.User property
// to access the currently authenticated user
...
}
Is that possible?
This is definitely possible. You could even set your autehtication scheme to None so that forms module isn't there in the pipeline and have only your own module.
However, even if forms is there, your custom module can override the identity set for the current request. Note also that until the forms cookie is issued, forms module doesn't set the identity. This was quite common to use both forms module and the SessionAuthenticationModule - forms does the job of redirecting to the login page and the session auth module handles its own authentication cookie.
This means that you can safely mix the two: the forms module and your own custom module for a similar scenario.
Darin suggests another approach and this of course is valid too. An advantage of an authentication module (versus the authentication filter) is that the authentication module could support other ASP.NET subsystems (web forms / wcf / webapi).
I have a problem with my MVC 4.0/Razor site.
It's a (not yet launched) public site that I recently inherited.
90% of all pages should be available to everyone, the rest are for superusers and need authentication.
This is handled via an AllowAnonymous attribute on the public facing pages, implemented like this;
public class RequireAuthenticationAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
var skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true) ||
filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(
typeof(AllowAnonymousAttribute), true);
if (!skipAuthorization)
base.OnAuthorization(filterContext);
}
}
Now, the problem is that I want a few customizations of the public facing sites (for the sake of argument, let's assume a "Currently logged in: XYZ"-label somewhere). What I've tried is using the User.Identity, but on all pages with AllowAnonymous, User.Identity.Name == "", even though a super user did log in. (And if he changes the url to a page with authentication, he's logged in again, and User.Identity.Name is correct).
Is there any way to both use Allow Anonymous and keep track of who's logged in?
I think I have solved this.
The problem was our custom subdomain routing. I had to override the "domain" setting so it points to .example.com instead of example.com for the cookies (mainly aspxauth).
The reason this took some time to realize was a number of other custom made parts, potentially interfering in the application, especially:
Custom membership provider
Custom AllowAnonymous attribute (even when there's a standard attribute now)
And the fact that I thought this was the normal behavior when in fact it's not.
Summary:
If implementing a subdomain routing rule AND need authentication that follows along the subdomains, you must change the base domain of the cookies.
I have the following ntier app: MVC > Services > Repository > Domain. I am using Forms authentication. Is it safe to use Thread.CurrentPrincipal outside of my MVC layer to get the currently logged in user of my application or should I be using HttpContext.Current.User?
The reason I ask is there seems to be some issues around Thread.CurrentPrincipal, but I am cautious to add a reference to System.Web outside of my MVC layer in case I need to provide a non web font end in the future.
Update
I have been following the advice recieved so far to pass the username into the Service as part of the params to the method being called and this has lead to a refinement of my original question. I need to be able to check if the user is in a particular role in a number of my Service and Domain methods. There seems to be a couple of solutions to this, just wondering which is the best way to proceed:
Pass the whole HttpContext.Current.User as a param instead of just the username.
Call Thread.CurrentPrincipal outside of my web tier and use that. But how do I ensure it is equal to HttpContext.Current.User?
Stick to passing in the username as suggested so far and then use Roles.IsUserInRole. The problem with this approach is that it requires a ref to System.Web which I feel is not correct outside of my MVC layer.
How would you suggest I proceed?
I wouldn't do either, HttpContext.Current.User is specific to your web layer.
Why not inject the username into your service layer?
Map the relevant User details to a new Class to represent the LoggedInUser and pass that as an argument to your Business layer method
public class LoggedInUser
{
public string UserName { set;get;}
//other relevant proerties
}
Now set the values of this and pass to your BL method
var usr=new LoggedInUser();
usr.UserName="test value "; //Read from the FormsAuthentication stuff and Set
var result=YourBusinessLayerClass.SomeOperation(usr);
You should abstract your user information so that it doesn't depend on Thread.CurrentPrincipal or HttpContext.Current.User.
You could add a constructor or method parameter that accepts a user name, for example.
Here's an overly simplified example of a constructor parameter:
class YourBusinessClass
{
string _userName;
public YourBusinessClass(string userName)
{
_userName = userName;
}
public void SomeBusinessMethodThatNeedsUserName()
{
if (_userName == "sally")
{
// do something for sally
}
}
}
I prefer option number 2( use Thread.CurrentPrincipal outside of web tier ). since this will not polute your service tier & data tier methods. with bonuses: you can store your roles + additional info in the custom principal;
To make sure Thread.CurrentPrincipal in your service and data tier is the same as your web tier; you can set your HttpContext.Current.User (Context.User) in Global.asax(Application_AuthenticateRequest). Other alternative location where you can set this are added at the bottom.
sample code:
//sample synchronizing HttpContext.Current.User with Thread.CurrentPrincipal
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
//make sure principal is not set for anonymous user/unauthenticated request
if (authCookie != null && Request.IsAuthenticated)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
//your additional info stored in cookies: multiple roles, privileges, etc
string userData = authTicket.UserData;
CustomPrincipal userPrincipal = PrincipalHelper.CreatePrincipal(authTicket.Name, authTicket.UserData, Request.IsAuthenticated);
Context.User = userPrincipal;
}
}
of course first you must implement your login form to create authorization cookies containing your custom principal.
Application_AuthenticateRequest will be executed for any request to server(css files, javascript files, images files etc). To limit this functionality only to controller action, you can try setting the custom principal in ActionFilter(I haven't tried this). What I have tried is setting this functionality inside an Interceptor for Controllers(I use Castle Windsor for my Dependency Injection and Aspect Oriented Programming).
I believe you are running into this problem because you need to limit your domains responsibility further. It should not be the responsibility of your service or your document to handle authorization. That responsibility should be handled by your MVC layer, as the current user is logged in to your web app, not your domain.
If, instead of trying to look up the current user from your service, or document, you perform the check in your MVC app, you get something like this:
if(Roles.IsUserInRole("DocumentEditorRole")){
//UpdateDocument does NOT authorize the user. It does only 1 thing, update the document.
myDocumentService.UpdateDocument(currentUsername, documentToEdit);
} else {
lblPermissionDenied.InnerText = #"You do not have permission
to edit this document.";
}
which is clean, easy to read, and allows you to keep your services and domain classes free from authorization concerns. You can still map Roles.IsUserInRole("DocumentEditorRole")to your viewmodel, so the only this you are losing, is the CurrentUserCanEdit method on your Document class. But if you think of your domain model as representing real world objects, that method doesn't belong on Document anyway. You might think of it as a method on a domain User object (user.CanEditDocument(doc)), but all in all, I think you will be happier if you keep your authorization out of your domain layer.
I'm working on an application using ASP.NET MVC 1.0 and I'm trying to inject a custom IPrincipal object in to the HttpContext.Current.User object.
With a traditional WebForms application I've used the Application_AuthenticateRequest event to do this as follows.
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (HttpContext.Current.User != null)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
if (HttpContext.Current.User.Identity is FormsIdentity)
{
// Get Forms Identity From Current User
FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
// Get Forms Ticket From Identity object
FormsAuthenticationTicket ticket = id.Ticket;
// Create a new Generic Principal Instance and assign to Current User
SiteUser siteUser = new SiteUser(Convert.ToInt32(id.Name));
HttpContext.Current.User = siteUser;
}
}
}
}
So using this I was able to access my custom IPrincipal by either explicitly casting the User object to type SiteUser. I actually did this by having a custom class that all Pages were inheriting from which did this under the covers for me.
Anyhow, my problem is that with ASP.NET MVC the Application_AuthenticateRequest seems to fire whenever any request is made (so for JS files, images etc.) which causes the application to die.
Any help or suggestions as to how I can go about injecting my custom IPrincipal in to the HttpContext.Current.User object within ASP.NET MVC 1.0 would be greatly appreciated. I did see the following post on SO, but it didn't seem to cater for what I'm trying to achieve: ASP.NET MVC - Set custom IIdentity or IPrincipal
TIA.
my problem is that with ASP.NET MVC
the Application_AuthenticateRequest
seems to fire whenever any request is
made (so for JS files, images etc.)
which causes the application to die.
This isn't an uniquely MVC problem - if you ran your application on IIS7 with the integrated pipeline in place then you would see the same thing.
If the problem with the lookup is scalability then I assume the actual problem is within
FormsAuthenticationTicket ticket = id.Ticket;
SiteUser siteUser = new SiteUser(Convert.ToInt32(id.Name));
I'd guess that your SiteUser class does some sort of database lookup. If you examine how forms auth works the ticket contains all the information necessary to produce a FormsIdentity (this doesn't hold true for roles, unless you specifically enable roles caching to a cookie). So you ought to look at the same approach. The first time you construct your siteUser object cache it within a signed cookie, then use the cookie to rehydrate your SiteUser properties on subsequent requests.
If you do this then you can go one step further, replacing the Thread principle with your SiteUser, or at least a custom IPrincipal/IUser combination which has the same information as your SiteUser class would have.
So inside AuthenticateRequest you'd have some flow like
SiteUserSecurityToken sessionToken = null;
if (TryReadSiteUserSecurityToken(ref sessionToken) && sessionToken != null)
{
// Call functions to attach my principal.
}
else
{
if (HttpContext.Current.User != null &&
HttpContext.Current.User.Identity.IsAuthenticated &&
HttpContext.Current.User.Identity is FormsIdentity)
{
// Get my SiteUser object
// Create SiteUserSecurityToken
// Call functions to attach my principal.
}
}
And the function to attach the principal would contain something like
HttpContext.Current.User = sessionSecurityToken.ClaimsPrincipal;
Thread.CurrentPrincipal = sessionSecurityToken.ClaimsPrincipal;
this.ContextSessionSecurityToken = sessionSecurityToken;
You'll want to make sure that the functions which write the Security Token to a cookie add, at a minimum, a checksum/MAC value, and, if you like, support encryption using the machine key if it's configured to do so. The read functions should validate these values.
This sounds like a job for a custom Authorization Filter.