I have implemented Forms authentication in one project. In the forms authentication cookie I store the Login Id of the user. i.e.
FormsAuthentication.SetAuthCookie(LoginId, false);
I now need to read the cookie value on every request to get more information about the user and put this information in the HttpContext.Items property. The project is a MVC project that has both regular MVC Controllers as well as Web API controllers. Currently I have created two action filters - one for the MVC controllers and other for Web API Controllers where I read this value. So like
public class MyMvcFilter : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
var cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (cookie != null)
{
var ticket = FormsAuthentication.Decrypt(cookie.Value);
filterContext.HttpContext.Items.Add("LoginId",ticket.Name);
}
base.OnAuthorization(filterContext);
}
}
and
public class MyFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
base.OnActionExecuting(actionContext);
var cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (cookie == null)
{
return;
}
var ticket = FormsAuthentication.Decrypt(cookie.Value);
if (ticket == null)
{
return;
}
actionContext.Request.Properties.Add("LoginId", userId);
}
}
However the more I think of it, the more it looks like an ugly hack to me. What would be correct location where I can decrypt the authentication cookie and remains the same for MVC controller as well as Web API controller ?
I would:
Read the cookie on the
protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
}
In the above method....Convert the cookie to a ClaimsPrincipal.
Like the below:
protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
IList<Claim> claimCollection = new List<Claim>
{
new Claim("http://www.mycompany.com/claims/LoginId, "123456" /* use info from cookie instead of 123456*/)
};
ClaimsIdentity claimsIdentity = new ClaimsIdentity(claimCollection, "My e-commerce website");
Console.WriteLine(claimsIdentity.IsAuthenticated);
ClaimsPrincipal customPrinc = new ClaimsPrincipal(claimsIdentity);
if (null != customPrinc)
{
Thread.CurrentPrincipal = customPrinc; /* Set here. But when you need to "get" it, use "System.Security.Claims.ClaimsPrincipal.Current" */
/* ASP.NET Authorization depends on value of HttpContext.Current.User.
* Consider putting ClaimsPrincipal into both HttpContext.Current.User and Thread.CurrentPrincipal */
HttpContext.Current.User = customPrinc;
/* Note the second setter is necessary so you don't lose it later on, learned the hard way by experience */
}
}
Retrieve the Claims Principal as needed..
/* MVC */
public override void OnAuthorization(System.Web.Mvc.AuthorizationContext filterContext)
{
/* the below should be what you set in the Application_PostAuthenticateRequest method */
IPrincipal currentClaimsPrinc = ClaimsPrincipal.Current;
}
/* WebAPI */
public override void OnAuthorization(HttpActionContext actionContext)
{
IPrincipal claimsPrincCurrent = ClaimsPrincipal.Current;
}
You could also do this:
adding claims to forms authentication in asp.net
or this:
http://brockallen.com/2013/01/26/replacing-forms-authentication-with-wifs-session-authentication-module-sam-to-enable-claims-aware-identity/
or this:
http://chris.59north.com/post/Claims-based-identities-in-ASPNET-MVC-45-using-the-standard-ASPNET-providers
MVC and WebAPI (until .NET Core) do not share the same pipeline so you need to use two different filters.
What you can do is share the code, if you want, maybe with an utility method or something. Just to avoid having two codes doing the same stuff
Related
I have an ancient Classic ASP website that used to use Windows authentication for access control but this is no longer appropriate. I thought I might try and wrap this website in an ASP.NET MVC 5 app, so I:
created a new website complete with ASP.NET Identity
created a user
copied the Classic ASP website into the root
viewed the classic ASP website
Now what I need to do is require authorization for all .asp pages so that only authorized users can see them. What's the best way of doing this? Maybe I could do something with OWIN?
Crispin
Following this example I created an HttpModule that came out like this:
public class ClassicAspAuthorization : IHttpModule
{
private MyEventHandler _eventHandler = null;
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(OnBeginRequest);
}
public delegate void MyEventHandler(Object s, EventArgs e);
public event MyEventHandler MyEvent
{
add { _eventHandler += value; }
remove { _eventHandler -= value; }
}
public void OnBeginRequest(Object s, EventArgs e)
{
HttpApplication app = s as HttpApplication;
if (app.Request.CurrentExecutionFilePathExtension.EndsWith(".asp") == true && blnIsAuthenticated() == false)
{
app.Context.Response.Redirect("/Account/Login");
}
if (_eventHandler != null)
{
_eventHandler(this, null);
}
}
and the boolean (blnIsAuthenticated) method that determined whether or not the user was authenticated was derived from a Stackoverflow answer where I removed the lines:
var identity = new ClaimsIdentity(claims, authenticationType, ClaimTypes.Name, ClaimTypes.Role);
var principal = new ClaimsPrincipal(identity);
System.Threading.Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;
and replaced this with my own claims checking to establish if the user was authenticated. An appropriate boolean value was returned.
i am doing this in order to authorize user.
[Authorize(Users = #"user1, user2, user3")]
public class MyController : Controller
{
// my stuff
}
i want to do authorization from the list of user which are in database table..
This is how I got it done:
Create a new class (which inherits from AuthorizeAttribute class).
public class CustomAuthorizeAttribute : AuthorizeAttribute
Override the AuthorizeCore method (in CustomAuthorizeAttribute class) and include your custom logic in it.
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool isUserAuthorized = false;
// custom logic goes here
// You can get the details of the user making the call using httpContext
// (httpContext.User.Identity.Name)
// Then get the information you have stored on your db, and compare it
// with these details.
// Set isUserAuthorized to true if the values match
return isUserAuthorized;
}
Decorate your controller action method with the attribute that you just created.
[CustomAuthorize]
public ActionResult DoSomething(string something, string someOtherThing)
This link form Gotalove is helpful.
try the following:
"using the link shared by #VikasRana http://www.codeproject.com/Articles/578374/AplusBeginner-splusTutorialplusonplusCustomplusF
I got rid of my enum Role and my method
public CustomAuthorizeAttribute(params object[] roles)
{ ...}
I then changed Role in my model to be a string e.g. User.Role="Admin" instead of int. In my onAuthorization method I changed it to:
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Controller.TempData["ErrorDetails"] = "You must be logged in to access this page";
filterContext.Result = new RedirectResult("~/User/Login");
return;
}
if (filterContext.Result is HttpUnauthorizedResult)
{
filterContext.Controller.TempData["ErrorDetails"] = "You don't have access rights to this page";
filterContext.Result = new RedirectResult("~/User/Login");
return;
}
}
and in my global.asax added this.
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if (FormsAuthentication.CookiesSupported == true && Request.IsAuthenticated== true)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
//let us take out the username now
string username = FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value).Name;
string roles = string.Empty;
using (GManagerDBEntities db = new GManagerDBEntities())
{
User user = db.Users.SingleOrDefault(u => u.Username == username);
roles = user.Role;
}
//let us extract the roles from our own custom cookie
//Let us set the Pricipal with our user specific details
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(
new System.Security.Principal.GenericIdentity(username, "Forms"), roles.Split(';'));
}
catch (Exception)
{
//something went wrong
}
}
}
}
"
Source: Custom user authorization based with roles in asp.net mvc
PS.: In this link, in the same post, there is a second way to fix your problem.
In the bottom of the post.
If this can't to help you, you should try it to.
Is HttpContext.Current.User in global asax not the same as HttpContext.User in an action method? I assigned the user some roles, but they seem to get lost.
The code below shows what is happening. Both Asserts get hit when a user is logged on, first in global asax, then the action method. However they give different results.
First this:
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
// ... omitted some code to check user is authenticated
FormsIdentity identity = (FormsIdentity)HttpContext.Current.User.Identity;
string[] roles = new string[] { "admin", "user" };
HttpContext.Current.User =
new System.Security.Principal.GenericPrincipal(identity, roles);
Assert(HttpContext.User.IsInRole("admin"));
}
Then this in my action method:
public ActionResult Index()
{
bool isAdmin = HttpContext.User.IsInRole("admin");
Assert(isAdmin); // this fails, isAdmin is false
// ...
}
I used the following resources
This SO answer
http://csharpdotnetfreak.blogspot.com/2009/02/formsauthentication-ticket-roles-aspnet.html
Your question tags say "aspnet-mvc (3 and 4)", so do you have the option of using the following to make your life easier? If you are using Simple Membership from the MVC 4 Internet Application template in VS2012 this will just work out of the box for you):
WebSecurity.CreateUserAndAccount(name, password) - to create a user
Roles.AddUserToRole (and AddUserToRoles) - add a user to a role
Roles.IsUserInRole - tests if a user is in a role
[Authorize(Roles = "admin")] - [Authorize] can enforce roles on an entire controller, or on an action
CreateUserAndAccount has the advantage that it's easy to set properties for the UserProfile as well, for example:
WebSecurity.CreateUserAndAccount(newUser.UserName, newUser.Password,
new { FullName = newUser.FullName, Email = newUser.Email, Timezone = newUser.TZ });
Roles.AddUserToRoles(newUser.UserName, new[] {"admin", "user"});
Edit, I realise the above doesn't answer your original question about .User property equivalence.
HttpContext in a Controller is a property: Controller.HttpContext. HttpContext in global.asax.cs is the static class, so that's why you use HttpContext.Current. They refer to the same thing.
If you run the following code, you can see they are apparently the "same principal". So the question is what happened to the roles you assigned?
protected void Application_AuthenticateRequest(object sender, EventArgs e) {
...
FormsIdentity identity = (FormsIdentity)HttpContext.Current.User.Identity;
string[] roles = new string[] { "admin", "user" };
identity.Label = "test label";
System.Security.Principal.GenericPrincipal ppl = new System.Security.Principal.GenericPrincipal(identity, roles);
HttpContext.Current.User = ppl;
... }
public ActionResult Index() {
bool isAdmin = HttpContext.User.IsInRole("admin");
bool isAdmin2 = System.Web.HttpContext.Current.User.IsInRole("admin");
System.Web.Security.FormsIdentity identity = (System.Web.Security.FormsIdentity)HttpContext.User.Identity;
// The label is carried through from Application_AuthenticateRequest to Index.
string label = identity.Label;
}
The problem is, you assigned a GenericPrincipal to .User. Depending on the RoleProvider, this can be overwritten (e.g. by the RoleManagerModule) during PostAuthenticateRequest and (for example) turned into a RolePrincipal. This can then defer back to the database (again depending on provider) to get the roles, so over-writing your roles. If you do the work in Application_OnPostAuthenticateRequest you might be ok.
I have enabled Basic Authentication in IIS7 for my site and followed this link to create handler for basic authentication requests.
The problem is that no matter what credentials user enters, the site keeps returning 401, even if entering correct credentials. This is just a test and credentials are checked against hardcoded values.
Here is relevant code:
public class BasicAuthenticationHttpModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.BeginRequest+=context_BeginRequest;
context.AuthenticateRequest += context_AuthenticateRequest;
}
void context_AuthenticateRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
TryAuthenticate(application);
}
private void context_BeginRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
TryAuthenticate(application);
}
private static void TryAuthenticate(HttpApplication application)
{
if (!Authenticate(application.Context))
{
application.Context.Response.Status = "401 Unauthorized";
application.Context.Response.StatusCode = 401;
application.Context.Response.AddHeader("WWW-Authenticate", "Basic");
application.CompleteRequest();
}
}
private static bool Authenticate(HttpContext context)
{
if (context.User!=null && context.User.Identity.IsAuthenticated)
{
return true;
}
if (!context.Request.Headers.AllKeys.Contains("Authorization"))
return false;
string authHeader = HttpContext.Current.Request.Headers["Authorization"];
IPrincipal principal;
if (TryGetPrincipal(authHeader, out principal))
{
context.User = principal;
return true;
}
return false;
}
private static bool TryGetPrincipal(string[] creds, out IPrincipal principal)
{
if (creds[0] == "Administrator" && creds[1] == "SecurePassword")
{
principal = new GenericPrincipal(
new GenericIdentity("Administrator"),
new string[] { "Administrator", "User" }
);
return true;
}
if (creds[0] == "BasicUser" && creds[1] == "Password")
{
principal = new GenericPrincipal(
new GenericIdentity("BasicUser"),
new string[] { "User", "SystemUser" }
);
return true;
}
else
{
principal = null;
return false;
}
}
When client enters correct credentials (i.e. "BasicUser", "Password"), GenericPrincipal object is created and assigned to HttpContext's User property. Looking into Request.IsAuthenticated tells that it's true.
And this is why I don't understand is why client receives 401 again and again and again.
I'm not sure how all the pipeline works - may be basic authentication goes further to some IIS HttpModule which also serves the request? Or may be code is incomplete and context_BeginRequest needs to be extended? (I know that in case of Forms authentication type, you do something like Response.Redirect(goodguy.aspx))
Anyway, any help/questions are appreciated.
Forgot to mention that in web.config I also placed
<system.webServer>
<modules>
<add name="BasicAuthenticationHttpModule" type="Analytics.BasicAuthenticationHttpModule" />
</modules>
</system.webServer>
Apparently implemements it's own Basic authentication. Thus our module could authenticate request succussfully, it would get passed to built-in IIS module, which rejected authentication. It is really helpful not to copy paste, but also to think yourself indeed. So to answer my question - disable all authentication on IIS except Anonymous.
I am having a hard time implementing "Remember Me" functionality in an MVC application with a custom principal. I have boiled it down to ASP.NET not retrieving the authentication cookie for me. I have included a snapshot below from Google Chrome.
Shows the results of Request.Cookies that is set within the controller action and placed in ViewData for the view to read. Notice that it is missing the .ASPXAUTH cookie
Shows the results from the Chrome developer tools. You can see that .ASPXAUTH is included here.
What may be the issue here? Why does ASP.NET not read this value from the cookie collection?
My application uses a custom IPrincipal. BusinessPrincipalBase is a CSLA object that ust implements IPrincipal. Here is the code for that:
[Serializable()]
public class MoralePrincipal : BusinessPrincipalBase
{
private User _user;
public User User
{
get
{
return _user;
}
}
private MoralePrincipal(IIdentity identity) : base(identity)
{
if (identity is User)
{
_user = (User)identity;
}
}
public override bool Equals(object obj)
{
MoralePrincipal principal = obj as MoralePrincipal;
if (principal != null)
{
if (principal.Identity is User && this.Identity is User)
{
return ((User)principal.Identity).Equals(((User)this.Identity));
}
}
return base.Equals(obj);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public static bool Login(string username, string password)
{
User identity = User.Fetch(username, password);
if (identity == null || !identity.IsAuthenticated)
{
identity = (User)User.UnauthenicatedIdentity;
}
MoralePrincipal principal = new MoralePrincipal(identity);
Csla.ApplicationContext.User = principal;
Context.Current.User = identity;
return identity != null && identity.IsAuthenticated;
}
public static void Logout()
{
IIdentity identity = User.UnauthenicatedIdentity;
MoralePrincipal principal = new MoralePrincipal(identity);
ApplicationContext.User = principal;
Context.Current.User = identity as User;
}
public override bool IsInRole(string role)
{
if (Context.Current.User == null || Context.Current.Project == null)
{
return false;
}
string userRole = Context.Current.User.GetRole(Context.Current.Project.Id);
return string.Compare(role, userRole, true) == 0;
}
The application also uses a custom membership provider. Here is the code for that.
public class MoraleMembershipProvider : MembershipProvider
{
public override bool ValidateUser(string username, string password)
{
bool result = MoralePrincipal.Login(username, password);
HttpContext.Current.Session["CslaPrincipal"] = ApplicationContext.User;
return result;
}
#region Non-Implemented Properties/Methods
public override string ApplicationName
{
get
{
return "Morale";
}
set
{
throw new NotImplementedException();
}
}
// Everything else just throws a NotImplementedException
#endregion
}
I do not think that any of this is related because the bottom line is that the Request.Cookies does not return the authentication cookie. Is it related to the size of the cookie? I heard there are issues to the size of the cookie.
UPDATE: It seems that the issue revolves around subdomains. This site was being hosted with a subdomain and the cookie domain was left blank. Does anyone have any pointers on how I can get the auth cookie to work with all domains (e.g. http://example.com, http://www.example.com, and http://sub.example.com)?
If you are trying to store the actual User object in the cookie itself, it is probably too big to store as a cookie. I am not too familiar with the MVC authentication stuff, but in web forms I generally do the following:
FormsAuthentication.RedirectFromLoginPage(user_unique_id_here, false);
The second parameter is for the persistency you are looking for.
From there I create a custom context (UserContext) that I populate via HttpModule that gives me access to all the user and role information.
Since I do not develop in MVC (yet) or CSLA, I'm not sure how much more help I can be. If I were you, I would also ditch the custom membership provider. You might as well just call MoralePrincipal.Login directly in your Authentication controller.
The rememberMe stuff should be set by the FormsAuthenticationService (in MVC2) or the FormsAuthentication static class in MVC1, if you're using the 'regular' AccountController's code. If you changed that code, did you remember to add in the (optional) boolean param indicating whether to use a persistent cookie or not?
It sounds to me like you're getting a session cookie, but not a persistent cookie.