I have an ASP.NET MVC web site which also has WEB API and OData end points. Is there a way to take the site offline but keep the API and OData working?
I tried placing an App_Offline.htm on the root, but that made all areas offline.
Write a ActionFilterAttribute and register it as global filter:
public class MyFilterAttribute : ActionFilterAttribute
{
private string _offlineUrl;
public MyFilterAttribute(string offlineUrl)
{
_offlineUrl = offlineUrl;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// considering you have a API controller
// and you have a Boolean app setting named BringMVCRoutesOffline
// to activate this filter form web.config
// in real world scenario you could check area or what URL
// or route data you want
if (Convert.ToBoolean(ConfigurationManager.AppSettings["BringMVCRoutesOffline"])
&& filterContext.RouteData.Values.ContainsKey("controller")
&& !filterContext.RouteData.Values["controller"]
.ToString().ToLower() == "api"
// avoiding infinite loop
&& filterContext.HttpContext.Request
.AppRelativeCurrentExecutionFilePath != _offlineUrl)
{
filterContext.Result = new RedirectResult(_offlineUrl);
}
}
}
and in the App_Start > FilterConfig add:
filters.Add(new MyFilterAttribute("~/error/offline"));
Deploy the WEB API and OData endpoints as separate IIS applications so the App_Offline.htm can be used to take one application down without affecting the other deployments. You may be able to accomplish this with existing URLs with nested IIS applications.
Related
I have a MVC4 web application set up, which uses Forms authentication and Web API for interaction. All API controllers use the [Authorize] attribute.
This was working just fine out of the box, until we started adding role support. Instead of implementing a full-fledged RoleProvider, I added a list of roles to the ticket's UserData, and created the following module:
public class SecurityModule : IHttpModule
{
public void Init(HttpApplication context)
{
var roleManager = (RoleManagerModule)context.Modules["RoleManager"];
roleManager.GetRoles += GetRoles;
}
void GetRoles(object sender, RoleManagerEventArgs e)
{
var user = e.Context.User;
if (user.Identity.IsAuthenticated && !(user is MyCustomPrincipal))
{
var roles = GetRolesFromFormsAuthCookie();
if (roles != null)
e.Context.User = new MyCustomPrincipal(user.Identity, roles,
otherData);
}
e.RolesPopulated = true;
}
}
This works flawlessly for MVC calls. For API, however, even though GetRoles gets called, when it reaches the corresponding method, it's back to GenericPrincipal.
How can I make this work with Web API too? Do I have to create a DelegatingHandler?
I'm also storing some custom data in my Principal, which might be a reason not to rely on just a RoleProvider (since I'd end up with a RolePrincipal), although I could just store that in the request context.
Update: I've now added a DelegatingHandler that does the same as the IHttpModule and sets Thread.CurrentPrincipal. Is this a reasonable approach?
Have you tried to set the Thread.CurrentPrincipal in the HttpModule as well ?. You can also use a Katana handler, which will work for both, ASP.NET MVC and ASP.NET Web API.
Where can I plug in a custom provider to set up the request context?
I want to run an ASP.NET MVC application in "slave" mode while gradually transitioning features from a legacy system. Each request will have a cookie, and I want to grab the cookie, make an external call to resolve it to a user identity, and set up that user identity for the remainder of the request.
I might set a forms authentication cookie, or use Session, but the source of truth about authentication has to be the externally-set cookie, on every request.
What's the best way to do this? Where do I plug in? I've looked at Authentication providers, and the Authorization attribute, but neither of those seems the right place for this.
I would have thought an HttpModule would be ideal for this scenario?
If I understand you correctly, I did something similar on a project I was working on recently:
public class UserSessionHttpModule : IHttpModule
{
private HttpApplication mApplication;
public void Dispose()
{
}
public void Init(HttpApplication context)
{
mApplication = context;
context.BeginRequest += new EventHandler(CheckUserSession);
}
private void CheckUserSession(Object sender, EventArgs e)
{
var extension = Path.GetExtension(mApplication.Context.Request.Path);
if (extension == "" || extension == ".aspx")
{
var userSessionService = ObjectFactory.GetInstance<IUserSessionService>();
userSessionService.CheckUserSession();
}
}
}
you could override the Init method in Global.asax page and listen to the PostAuthenticateRequest event, the events fires after authentication so you could change whatever values you like from the form authenticate cookies and inject your own
after a lot of googling and reading several solutions on how to manage mixed mode authentication in ASP.NET apps, I still have no fitting solution for my problem.
I've got to implement an intranet application for a bunch of different user groups. Until now i've used windows authenthication which was very simple to implement. My problems arise when it comes to authorizing usergroups for special application functionalities.
Using [Authorize(Users = "DOMAIN\\USER")] works great but due to that i have no access to the active directory managament, it is impossible to me to configure rolemanagement in the way I need it for my application.
What I'd like to do is defining custom roles and memberships in addition to the ones that are defined within the active directory (is such an extension possible? e.g. by implementing an own membershipprovider?).
What do you think is the best solution for my problem. Do I really have to implement a complex mixed mode authentication with forms authentication in addition to windows authentication?
Used Technologies:
MS SQL Server 2008
MS VS 2010
ASP.NET MVC 3 - Razor View Engine
Telerik Extensions for ASP.NET MVC
IIS 7 on Windows Server 2008
EDIT (final solution thanks to the help of dougajmcdonald):
After pointing me to use a custom IPrincipal implementation I've found some solutions here and here. Putting everything together I came to the following solution:
1.Create a custom principal implementation:
public class MyPrincipal: WindowsPrincipal
{
List<string> _roles;
public MyPrincipal(WindowsIdentity identity) : base(identity) {
// fill roles with a sample string just to test if it works
_roles = new List<string>{"someTestRole"};
// TODO: Get roles for the identity out of a custom DB table
}
public override bool IsInRole(string role)
{
if (base.IsInRole(role) || _roles.Contains(role))
{
return true;
}
else
{
return false;
}
}
}
2.Integrate my custom principal implementation into the application through extending the "Global.asax.cs" file:
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (Request.IsAuthenticated)
{
WindowsIdentity wi = (WindowsIdentity)HttpContext.Current.User.Identity;
MyPrincipal mp = new MyPrincipal(wi);
HttpContext.Current.User = mp;
}
}
3.Use my custom roles for authorization in my application
public class HomeController : Controller
{
[Authorize(Roles= "someTestRole")]
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
}
It works!!! yeah!
I'm not sure if this still applies in MVC, but in Webforms one way to do this would be as follows:
Create a new IPrincipal implementation perhaps extending WindowsPrincipal
In this class, give it a collection of roles (your own custom roles)
Populate those roles, by perhaps getting them from the DB.
Override IsInRole to return true if the role provided is EITHER true from the base call (WindowsAuthentication/Role) OR from your own custom role collection.
This way you can still hook into Principal.IsInRole("MyRole") and also the principal [PrincipalPermission()] annotation.
Hope it helps.
EDIT in answer to q's:
To integrate the principal into the authorisation you need to write your own method for OnAuthenticate in the global.asax for the type of authentication, so I would guess for you, something like this:
void WindowsAuthentication_OnAuthenticate(object sender, WindowsAuthenticationEventArgs e)
{
// ensure we have a name and made it through authentication
if (e.Identity != null && e.Identity.IsAuthenticated)
{
//create your principal, pass in the identity so you know what permissions are tied to
MyCustomePrincipal opPrincipal = new MyCustomePrincipal(e.Identity);
//assign your principal to the HttpContext.Current.User, or perhaps Thread.Current
HttpContext.Current.User = opPrincipal;
}
}
I believe Authorize came in at a later date to the PrincipalPermission, but I'm not too sure as to when/why of the differences I'm afraid :( - sorry!
I have made my Login page as Https enabled by adding the attribute [RequireSSL] on controller Action and it works fine. But after successful login it remains in https environment, however the page is non https page.
Can anybody give me workaround how to step out from https to http mode?
Any help in this regard will be greatly appreciated.
You basically need to do the opposite, which is have a [DoesNotRequireSSL] attribute, which effectively does the opposite of the {RequireSSL] attribute, i.e., redirect to http protocol
public class DoesNotRequireSSL: ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var request = filterContext.HttpContext.Request;
var response = filterContext.HttpContext.Response;
if (request.IsSecureConnection && !request.IsLocal)
{
string redirectUrl = request.Url.ToString().Replace("https:", "http:");
response.Redirect(redirectUrl);
}
base.OnActionExecuting(filterContext);
}
}
Also, if you would like to ensure that multiple pages have this behaviour, you can set up a base controller, from which all your non-http controllers can inherit from so you dont have to worry about having to repeat yourself for every page which needs this.
CAUTION: I had a similar question. One important thing I learnt was that your auth cookie will be sent over plain text after switching back to HTTP. See this.
CAUTION 2 : Don't forget to consider the dreaded You are about to be redirected to a connection that is not secure message
If you're writing a bank application you need to be real careful - and also realize the increasing number of users on public wifi connections that could well [esily] be funneled through some sneaky proxy. Probably a much bigger concern for mainstream sites but a concern for us all to be aware of.
See also my other question (no answers at time of writing - but then I only just asked it!)
I know this is quite an old question, but many of the links presented above are dead and this code addresses it for ASP.NET MVC 5 by making some slight modifications to the RequireHttpsAttribute that is included in the System.Web.Mvc namespace:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ForbidHttpsAttribute : FilterAttribute, IAuthorizationFilter
{
public virtual void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (filterContext.HttpContext.Request.IsSecureConnection)
{
HandleHttpsRequest(filterContext);
}
}
protected virtual void HandleHttpsRequest(AuthorizationContext filterContext)
{
// only redirect for GET requests, otherwise the browser might not propagate the verb and request
// body correctly.
if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("The requested resource can only be accessed *without* SSL.");
}
// redirect to HTTP version of page
var url = "http://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
}
The code comes from this article, which briefly discusses some of the security concerns of forcing users to redirect from HTTPS to HTTP as well.
For Asp.net Mvc project, I need to redirect every request to configuration page when user(should be admin of this website) visit this website at the first time. This operation like default login page(every request will be redirect to default login page if access denied).
After user config the configuration file, Route table will be mapped to normal controllers.
Ps. This page should helps Admin for detect error configuration and easy to deploy.
Update #1
I try to use ASP.NET MVC WebFormRouting Demo on Codeplex. But I can't redirect when user visit some existing page like "~/AccessDenied.aspx" or "~/web.config".
routes.MapWebFormRoute("RedirectToConfig", "{*anything}", "~/App_Config");
Thanks,
From your description, this appears to be an authorization concern, so I would recommend a custom Authorize attribute class (inherit from AuthorizeAttribute).
From here you can override the OnAuthorization method where you can check if the user has completed your required configuration steps and set the filterContext.Result accordingly. A basic implementation would look something like this (this assumes you have a valid /Account/Configure route):
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
var user = ; // get your user object
if(user.IsConfigured == false) // example
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{
"ConfigureUserRoute",
filterContext.RouteData.Values["ConfigureUserRoute"]
},
{"controller", "Account"},
{"action", "Configure"}
});
return;
}
}
}
You can find other examples of how to create a custom AuthorizeAttribute class here on StackOverflow.
2 ideas:
Use a catch-all rule on top of your routing table and put a constraint on it that checks for the config status
Put the code for this check in Application_BeginRequest in GlobalAsax
Details for the catch-all idea:
Create a rule with url "{*path}" and put it first in your list
Create a constraint to activate this rule only in case the configuration is not done yet
Create a simple controller e.g. ConfigController with a single action that does nothing but a RedirectToUrl("config.aspx")
But the solution in Application_BeginRequest would be simpler, since the whole code to handle this in one place
Now, I can apply technique from my another question to solve this problem. By keep some value in static instance when application is starting. Please look at the following code.
partial ConfigBootstapper.cs
public class ConfigBootstapper
{
public static EnableRedirectToConfigManager = false;
}
partial ConfigModule.cs
void HttpApplication_BeginRequest(object sender, EventArgs e)
{
HttpApplication app = sender as HttpApplication;
if (ConfigBootstapper.EnableRedirectToConfigManager)
{
app.Response.Redirect("~/App_Config");
}
}
partial Global.asax
protected void Application_Start()
{
[logic for setting ConfigBootstapper.EnableRedirectToConfigManager value]
}
PS. Don't forget to checking some condition that cause infinite-loop before redirect.