Asp.Net MVC Authentication Roles without Providers - asp.net

I know this has been answered here before, however even after following all the solutions I could find, I cannot still get my roles working in my system.
I have a Asp.Net MVC application, with Forms based authentication. Instead of using a local database, it uses OpenAuth/OpenID for authentication, and a database lookup table for application roles.
As per main suggestion, I implemented the roles in Global.asax like:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
//Fires upon attempting to authenticate the use
if (HttpContext.Current.User != null &&
HttpContext.Current.User.Identity.IsAuthenticated &&
HttpContext.Current.User.Identity.GetType() == typeof (FormsIdentity))
Thread.CurrentPrincipal = HttpContext.Current.User = OpenAuthPrincipal.Get(HttpContext.Current.User.Identity.Name);
}
Here OpenAuthPrincipal.Get is a very straightforward static method wrapping the openauth id with the roles:
public static IPrincipal Get(string userId)
{
var db = new WebData();
var user = db.Users.Find(userId);
return new GenericPrincipal(new Identity(user), user.Roles.Split('|'));
}
However when I reach a function like:
[Authorize(Roles = "Admin")]
public ActionResult Edit(int id)
{
...
}
It fails. If I remove the Roles restriction, and check User.IsInRole("Admin") in the debugger I get a false. However, if I do the check in the Global.asax, I get true.
I know that the User.Identity.Name is coming correctly. And also the IIdentity is not modified at all. However only the roles are lost.
What could be the cause of this issue?
Update:
The solution recommended below did not directly work, however this change fixed the issue for me:
protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
{
httpContext.User = OpenAuthPrincipal.Get(httpContext.User.Identity.Name);
return base.AuthorizeCore(httpContext);
}

As per main suggestion, I implemented the roles in Global.asax like:
No idea where did you get this main suggestion from but in ASP.NET MVC you normally use authorization action filters. And since the default Authorize filter doesn't do what you need, you write your own:
public class OpenIdAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var authorized = base.AuthorizeCore(httpContext);
if (authorized)
{
httpContext.User = OpenAuthPrincipal.Get(httpContext.User.Identity.Name);
}
return authorized;
}
}
and then:
[OpenIdAuthorize(Roles = "Admin")]
public ActionResult Edit(int id)
{
...
}

Related

Umbraco :: Catching or Extending User Login

I am trying to catch the login event of Umbraco Users (login in the CMS).
I have tried to extend from MembersMembershipProvider and override the ValidateUser method. I also changed the web.config to use my class.
When i put a breakpoint in this overrited method it doesnt stop and logsin the user as usual.
public class CustomUmbracoMembershipProvider : Umbraco.Web.Security.Providers.UsersMembershipProvider
{
public override bool ValidateUser(string username, string password)
{
return base.ValidateUser(username, password);
}
}
Thanks in advance.
As Jannik said, Umbraco uses ASP.NET Identity in the latest couple of versions, so the MembershipProvider is not used to validate and authenticate the user anymore.
So after a couple hours of research, i found a workaround solution:
1º - Create a custom UserManager, and override the method CheckPasswordAsync:
public class CustomBackOfficeUserManager : BackOfficeUserManager
{
public CustomBackOfficeUserManager(
IUserStore<BackOfficeIdentityUser, int> store,
IdentityFactoryOptions<BackOfficeUserManager> options,
MembershipProviderBase membershipProvider) :
base(store, options, membershipProvider)
{
}
/// <summary>
/// Returns true if the password is valid for the user
/// </summary>
/// <param name="user"/><param name="password"/>
/// <returns/>
public override Task<bool> CheckPasswordAsync(BackOfficeIdentityUser user, string password)
{
//Your implementation
var result = base.CheckPasswordAsync(user, password).Result;
return Task.FromResult(result);
}
}
2º - Then in your owin startup, you can use this block of code for your ConfigureUserManagerForUmbracoBackOffice:
var appCtx = ApplicationContext;
app.ConfigureUserManagerForUmbracoBackOffice<BackOfficeUserManager, BackOfficeIdentityUser>(
appCtx,
(options, context) =>
{
var membershipProvider = MembershipProviderExtensions.GetUsersMembershipProvider().AsUmbracoMembershipProvider();
var store = new BackOfficeUserStore(
appCtx.Services.UserService,
appCtx.Services.ExternalLoginService,
membershipProvider);
return new CustomBackOfficeUserManager(store, options, membershipProvider);
});
This solution ill keep using the umbraco UserStore and the base methods of UserManager.
I have a similar situation. This worked for me in Umbraco 7.6.5. I know it's not elegant. It's only a workaround:
public class MyCustomEvents: ApplicationEventHandler
{
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
base.ApplicationStarted(umbracoApplication, applicationContext);
UserService.SavedUser += UserServiceSaved;
}
private void UserServiceSaved(IUserService sender, SaveEventArgs<IUser> e)
{
foreach(IUser user in e.SavedEntities)
{
if (!user.IsNewEntity()) //Is not creating a new user
{
IUser alreadyLoggedUser = UmbracoContext.Current.Security.CurrentUser;
if (alreadyLoggedUser == null) //Is not a user management via backoffice
{
if (user.FailedPasswordAttempts == 0) //Is a successful login?
{
DateTime justNow = DateTime.Now.AddSeconds(-5);
if (user.LastLoginDate.CompareTo(justNow) >= 0) //Logged in just now?
{
//Do your stuff
}
}
}
}
}
}
}
As the Umbraco backend uses ASP.NET Identity in the latest couple of versions, it may also be relevant to ask which exact version you are using.
I think ASP.NET Identity has an OnLoggedIn event you could try: https://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.login.onloggedin.aspx
But I'm not 100% sure the link is current.

Using a Custom Authentication/Authorization attribute for an action

We have a website that uses ASP Identity and works great with the [Authorize] attribute sprinkled on all the appropriate classes.
What i'm looking to do is create a separate authentication system for a specific set of actions. It's a page that isn't exactly anonymous, but can be viewed if a PIN is entered correctly.
I started looking into Authentication/Authorization attributes and got to a point where it redirects to my PIN entry page if not authenticated.
So I guess what i'm asking is how do I authenticate a virtual user (aka: not in the database) to be able to access those pages after entering in the correct PIN?
You could create your own version of the AuthorizeAttribute by inheriting from it and overriding the AuthorizeCore method.
public class PinAuthorizeAttribute : AuthorizeAttribute
{
private readonly string _password;
public PinAuthorizeAttribute(string password)
{
_password = password;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//Check if user has entered the correct PIN, perhaps
//by keeping the PIN in the Session
if(Session["PIN") == _password)
return true;
return false;
}
}
Now add it to your action method:
[PinAuthorize("1234")]
public ActionResult ProtectedIndex()
{
//snip
}

Asp.Net MVC5 How to ensure that a cookie exists?

I'm new to MVC (5). In order to add localization support to my website I added a "Language" field to my ApplicationUser : IdentityUser
What's the best approach to now store this information in the browser and ensure that it gets re-created even if the user manually deletes it?
TL; but I've got time
What I've tried until now:
I started creating a cookie in my method private async Task SignInAsync(ApplicationUser user, bool isPersistent) but I notice that:
This method is not used if the user is already authenticated and automatically logs in using the .Aspnet.Applicationcookie and my language cookie could be meanwhile expired (or been deleted).
A user could manually delete the cookie, just for fun.
I thought about checking its existence in the controller (querying the logged user and getting it from the db) and it works but I'd need to do it in EVERY controller. I'm not sure is the correct way to do this.
Any suggestion about how to approach this problem and guarantee that the application has a valid "language cookie" on every request?
It sounds to me like what you want here is a Custom Action Filter. You can override the OnActionExecuting method which means the logic is run before any action is called
public class EnsureLanguagePreferenceAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var langCookie = filterContext.HttpContext.Request.Cookies["LanguagePref"];
if (langCookie == null)
{
// cookie doesn't exist, either pull preferred lang from user profile
// or just setup a cookie with the default language
langCookie = new HttpCookie("LanguagePref", "en-gb");
filterContext.HttpContext.Request.Cookies.Add(langCookie);
}
// do something with langCookie
base.OnActionExecuting(filterContext);
}
}
Then register your attribute globally so it just becomes the default behaviour on every controller action
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new EnsureLanguagePreferenceAttribute());
}
To me, the easiest way would be to create your own Authorize attribute (since your language options are tied to an authenticated user account). Inside of your new authorize attribute, simply perform the check if the cookie exists. If it does, then life is good. Else, query the user's database profile and reissue the cookie with the stored value
public class MyAuthorization : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//no point in cookie checking if they are not authorized
if(!base.AuthorizeCore(httpContext)) return false;
var cookie = httpContext.Request.Cookies["LanguageCookie"];
if (cookie == null) {
CreateNewCookieMethod();
}
return true;
}
}
To use, replace [Authorize] with [MyAuthorization] in your project.
If you don't want to mess with the [Authorize] attribute, you could create your own attribute that does the cookie checking and decorate your controller with that one as well.
One last alternative is to create your own Controller class that does the checking on the OnActionExecuting.
public class MyBaseController : Controller
{
public string Language {get;set;}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var cookie = filterContext.HttpContext.Request.Cookies["LanguageCookie"];
if(cookie == null){
cookie = CreateNewCookieMethod();
filterContext.HttpContext.Request.Cookies.Add(cookie);
}
Language = cookie.Value;
base.OnActionExecuting(filterContext);
}
How to use (note that we inherit from MybaseController now)
public class HomeController : MyBaseController{
public ActionResult Index(){
//Language comes from the base controller class
ViewBag.Language = Language;
Return View();
}
}
This method is neat because now that Language variable will be available in any controller that inherits from this new class.
Either of these will give you a single, cookie checking point. Additionally, you are only going back to the database only in the instance that the cookie does not exist.

Unable to pass data form SecurityAttribute class to controller in MVC3

i am using mvc 3 code first. facing problem while passing data form SecurityAttribute class to Controller. i actually want to redirect user on login page with displaying Message. for this i override AuthorizeCore method in SecurityAttribute class. in this method i am unable to direct use session, cookies, tempdate, and viewbag etc. any other solution to solve this problem. Thanks
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext.Session["UserID"] == null)
{
//here i am unable to pass message to User/LogOn action.
httpContext.Response.Redirect("~/User/LogOn");
// httpContext.Session["lblMsg"] = "You are not authroize to perform this action.Please Login through different account";
return false;
}
First things first, you should not redirect inside the AuthorizeCore method. You should use the HandleUnauthorizedRequest method which is intended for this purpose. As far as passing an error message to the LogOn action is concerned you could use TempData:
public class SecurityAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// perform the custom authorization logic here and return true or false
// DO NOT redirect here
return httpContext.Session["UserID"] != null;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Controller.TempData["ErrorMessage"] = "You are not authroize to perform this action.Please Login through different account";
// calling the base method will actually throw a 401 error that the
// forms authentication module will intercept and automatically redirect
// you to the LogOn page that was defined in web.config
base.HandleUnauthorizedRequest(filterContext);
}
}
and then inside the LogOn action:
public ActionResult LogOn()
{
string errorMessage = TempData["ErrorMessage"] as string;
...
}
or if you want to access it inside the LogOn.cshtml view:
<div>#TempData["ErrorMessage"]</div>
Another possibility is to pass the message as a query string parameter instead of using TempData:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
var values = new RouteValueDictionary(new
{
controller = "User",
action = "LogOn",
errormessage = "You are not authroize to perform this action.Please Login through different account"
});
filterContext.Result = new RedirectToRouteResult(values);
}
and then you could have the LogOn action take the error message as action parameter:
public ActionResult LogOn(string errorMessage)
{
...
}

Disable Session state per-request in ASP.Net MVC

I am creating an ActionResult in ASP.Net MVC to serve images. With Session state enabled, IIS will only handle one request at a time from the same user. (This is true not just in MVC.)
Therefore, on a page with multiple images calling back to this Action, only one image request can be handled at a time. It's synchronous.
I'd like this image Action to be asynchronous -- I'd like multiple image requests to each execute without needing the previous one to complete. (If the images were just static files, IIS would serve them up this way.)
So, I'd like to disable Session just for calls to that Action, or to specify that certain requests do not have Session state. Anyone know how this is done in MVC? Thanks!
If anyone is in the situation I was in, where your image controller actually needs read only access to the session, you can put the SessionState attribute on your controller
[SessionState(SessionStateBehavior.ReadOnly)]
See http://msdn.microsoft.com/en-us/library/system.web.mvc.sessionstateattribute.aspx for more info.
Thanks to https://stackoverflow.com/a/4235006/372926
Rather than implementing an action filter for this, why don't you implement a RouteHandler?
Here's the deal - IRouteHandler has one method - GetHttpHandler. When you make an ASP.Net MVC request to a controller, by default the routing engine handles the request by creating a new instance of MvcRouteHandler, which returns an MvcHandler. MvcHandler is an implementation of IHttpHandler which is marked with the (surprise!) IRequiresSessionState interface. This is why a normal request uses Session.
If you follow my blog post on how to implement a custom RouteHandler (instead of using MvcRouteHandler) for serving up images - you can skip returning a session-tagged IHttpHandler.
This should free IIS from imposing synchronicity on you. It would also likely be more performant because it's skipping all the layers of the MVC code dealing with filters.
I also came across the same problem and after doing R&D this link worked for me
Reference:
https://techatfingers.wordpress.com/2016/06/14/session-state-on-action/
Create custom Attribute
Override the “GetControllerSessionBehavior” method present in class DefaultControllerFactory.
Register it in global.aspx
1> Create custom Attribute
public sealed class ActionSessionStateAttribute : Attribute
{
public SessionStateBehavior SessionBehavior { get; private set; }
public ActionSessionStateAttribute(SessionStateBehavior sessionBehavior)
{
SessionBehavior = sessioBehavior;
}
}
2. Override
public class SessionControllerFactory : DefaultControllerFactory
{
protected override SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
return SessionStateBehavior.Default;
var actionName = requestContext.RouteData.Values["action"].ToString();
Type typeOfRequest=requestContext.HttpContext.Request.RequestType.ToLower() =="get"?typeof(HttpGetAttribute):typeof(HttpPostAttribute);
// [Line1]
var cntMethods = controllerType.GetMethods()
.Where(m =>
m.Name == actionName &&
( ( typeOfRequest == typeof(HttpPostAttribute) &&
m.CustomAttributes.Where(a => a.AttributeType == typeOfRequest).Count()>0
)
||
( typeOfRequest == typeof(HttpGetAttribute) &&
m.CustomAttributes.Where(a => a.AttributeType == typeof(HttpPostAttribute)).Count() == 0
)
)
);
MethodInfo actionMethodInfo = actionMethodInfo = cntMethods != null && cntMethods.Count() == 1 ? cntMethods.ElementAt(0):null;
if (actionMethodInfo != null)
{
var sessionStateAttr = actionMethodInfo.GetCustomAttributes(typeof(ActionSessionStateAttribute), false)
.OfType<ActionSessionStateAttribute>()
.FirstOrDefault();
if (sessionStateAttr != null)
{
return sessionStateAttr.Behavior;
}
}
return base.GetControllerSessionBehavior(requestContext, controllerType);
}
3. Register class in Global.asax
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
// --- other code ---
ControllerBuilder.Current.SetControllerFactory(typeof(SessionControllerFactory));
}
}
Try serving the images from another domain. So something like images.mysite.com.
This will provide you two benefits: One, sessions are tracked by a cookie, so images.mysite.com won't have the cookie. Two, it will give you an additional two concurrent requests to retrieve images.
Have you considered setting up a HttpHandler to serve up your images?
SessionState attribute is quite helpful if u use mvc3. How to achieve this with mvc2 needs a little more coding.
Idea is to tell the asp.net that specific request wont use session object.
So, Create a custom route handler for specific requests
public class CustomRouteHandler : IRouteHandler
{
public System.Web.IHttpHandler GetHttpHandler(RequestContext requestContext)
{
requestContext.HttpContext.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.ReadOnly);
return new MvcHandler(requestContext);
}
}
SessionStateBehavior enum has 4 members, you should use "disabled" or "readonly" modes to get async behavior.
After creating this custom route handler, be sure that your specific requests goes through this handler. This can be done via defining new routes at Global.asax
routes.Add("Default", new Route(
"{controller}/{action}",
new RouteValueDictionary(new { controller = "Home", action = "Index"}),
new CustomRouteHandler()
));
Adding this route makes all your requests to be handled by your custom route handler class. You can make it specific by defining different routes.
Change DefaultCOntrollerFactory to custom ControllerFactory class. Default Controller.TempDataProvider use SessionStateTempDataProvider. you can change it.
1.Set web.config/system.web/sessionState:mode="Off".
2.create DictionaryTempDataProvider class.
public class DictionaryTempDataProvider : ITempDataProvider
{
public IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
{
return new Dictionary<string, object>();
}
public void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values)
{
}
}
3.Create DictionaryTempDataControllerFactory
public class DictionaryTempDataControllerFactory : DefaultControllerFactory
{
public override IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
{
var controller = base.CreateController(requestContext, controllerName) as Controller;
if (controller!=null)
controller.TempDataProvider = new DictionaryTempDataProvider();
return controller;
}
}
4.In global.asax.cs Apprication_Start event set DictionaryTempDataControllerFactory.
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(
new DictionaryTempDataControllerFactory()
);
}
On our server, IIS doesn't even know about sessions - it's the ASP.NET stack that handles one request per session at a time. Static files, like images, are never affected.
Is it possible that your ASP.NET app is serving the files instead of IIS?
Create new Controller
Decorate controler with [SessionState(SessionStateBehavior.Disabled)]
Refactor code you want seesion stated disabled for to that controller
I would to share my solution for disable ASP.NET Session for an specific request (in my case, a WCF Service) using an HttpModule:
public class AspNetSessionFilterModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.PostMapRequestHandler += OnPostMapRequestHandler;
}
private void OnPostMapRequestHandler(object sender, EventArgs e)
{
var context = (sender as HttpApplication).Context;
DisableSessionForSomeRequests(context);
}
private void DisableSessionForSomeRequests(HttpContext context)
{
if ("~/Services/MyService.svc".Equals(context.Request.AppRelativeCurrentExecutionFilePath, StringComparison.InvariantCultureIgnoreCase))
{
context.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Disabled);
}
}
public void Dispose()
{ }
}

Resources