ActionFilterAttribute to turn off SSL on Asp.Net MVC2 controller doesn't work consistently - asp.net-mvc-2

This Action Filter doesn't seem to work consistently. Some times it turns SSL off and sometimes it doesn't. I have it applied to the entire controller at it's declaration.
public class SSLFilter:ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpRequestBase req = filterContext.HttpContext.Request;
HttpResponseBase res = filterContext.HttpContext.Response;
if (req.IsSecureConnection)
{
var builder = new UriBuilder(req.Url)
{
Scheme = Uri.UriSchemeHttp,
Port = 80
};
res.Redirect(builder.Uri.ToString());
}
base.OnActionExecuting(filterContext);
}
}
It's kind of odd...any ideas why it might be working sporadically?

Have you tried decorating your controllers/actions with the [RequireHttps] attribute?
Oops, haven't noticed you was asking about ASP.NET MVC 2. This attribute is available in ASP.NET MVC 3 only, so here's the source code for it (as implemented in ASP.NET MVC 3):
using System;
using System.Diagnostics.CodeAnalysis;
using System.Web.Mvc.Resources;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class RequireHttpsAttribute : FilterAttribute, IAuthorizationFilter {
public virtual void OnAuthorization(AuthorizationContext filterContext) {
if (filterContext == null) {
throw new ArgumentNullException("filterContext");
}
if (!filterContext.HttpContext.Request.IsSecureConnection) {
HandleNonHttpsRequest(filterContext);
}
}
protected virtual void HandleNonHttpsRequest(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(MvcResources.RequireHttpsAttribute_MustUseSsl);
}
// redirect to HTTPS version of page
string url = "https://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
}
Notice that how instead of doing any redirects it uses a RedirectResult which is the correct way of performing redirects in ASP.NET MVC => by returning action results:
filterContext.Result = new RedirectResult(url);
Not only that this will perform the correct redirect but that's how to short-circuit the execution of an action. Also semantically your filter should actually be an IAuthorizationFilter as you are blocking access to some resource here.

Related

.Net core controller tempdata add/remove

Implementing ExceptionFilterAttribute OnException method and need to redirect
The old implementation (.Net 48) was
exceptionContext.Controller.TempData.Remove("");
exceptionContext.Controller.TempData.Add("");//exception message is added
exceptionContext.Controller.ControllerContext.HttpContext.Response.Redirect(url);
For the redirect I guess I can:
exceptionContext.HttpContext.Response.Redirect(url);//Is this correct?
Please suggest alternate for the remove and add?
In .net 6,there's no exceptionContext.Controller.TempData if you want to access TempData in your ExceptionFilter,you could try to inject the TempdataDictionaryFactory into the filter
For example, I tried as below:
public class MyExceptionFilter : ExceptionFilterAttribute
{
private readonly ITempDataDictionaryFactory _tempDataDictionaryFactory;
public MyExceptionFilter(ITempDataDictionaryFactory tempDataDictionaryFactory)
{
_tempDataDictionaryFactory = tempDataDictionaryFactory;
}
public override void OnException(ExceptionContext context)
{
var tempData = _tempDataDictionaryFactory.GetTempData(context.HttpContext);
tempData.Add("key", "value");
context.Result = new RedirectToActionResult("Error", "Home", null);
}
}
in Program.cs:
builder.Services.AddControllersWithViews(x=>x.Filters.Add(typeof(MyExceptionFilter)));
The Result:

How does REST authentication work for client-side apps?

I'm trying to design my first public API, and I'm trying to learn how REST works with authentication, especially in the context of completely client-side apps using js-frameworks, e.g., angularJS.
Say you have a client which is a browser application (i.e., HTML, JS, CSS only) served as static files from something like nginx using a javascript framework to consume a REST service from, e.g. something that requires a secret access key that's used to create a signature for each request to the service, something like Amazon S3.
In terms of authentication in this scenario, where you don't have a server-side application, how would the secret access key be handled, i.e., how do you get it, where do you store it, etc.? It would seem like a horrible security situation to serve the key for each request (even if it only happens once to bootstrap the application).
And even if you do have a light server-side application--how do you securely inform the client (which still calls the authenticated 3rd party API itself) what the signature should be for every request it could possibly make? I'm very confused by how this is supposed to be designed from either end.
I've done a few AngularJS apps and the way that I've found is to use an HttpModule like this one:
using System;
using System.Net.Http.Headers;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Web;
namespace YourSolution.WebApp.Modules
{
public class BasicAuthenticationHttpModule : IHttpModule
{
public BasicAuthenticationHttpModule()
{
}
public void Init(HttpApplication context)
{
context.AuthenticateRequest += OnApplicationAuthenticateRequest;
context.EndRequest += OnApplicationEndRequest;
}
private static void SetPrincipal(IPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
}
private static bool CheckPassword(
string username, string password)
{
return username == password;
}
private static void AuthenticateUser(string credentials)
{
try
{
var encoding = Encoding.GetEncoding(
"iso-8859-1");
credentials = encoding.GetString(
Convert.FromBase64String(credentials));
var separator = credentials.IndexOf(':');
var name = credentials.Substring(0, separator);
var password = credentials.Substring(separator + 1);
var validated = CheckPassword(name, password);
if (!validated) return;
var identity = new GenericIdentity(name);
SetPrincipal(new GenericPrincipal(identity, null));
}
catch (FormatException)
{
}
}
private static void OnApplicationAuthenticateRequest(
object sender, EventArgs e)
{
var request = HttpContext.Current.Request;
var authHeader = request.Headers["Authorization"];
if (authHeader == null) return;
var authHeaderVal = AuthenticationHeaderValue.Parse(authHeader);
if (authHeaderVal.Scheme.Equals(
"basic",
StringComparison.OrdinalIgnoreCase)
&& authHeaderVal.Parameter != null)
{
AuthenticateUser(authHeaderVal.Parameter);
}
}
private static void OnApplicationEndRequest(
object sender, EventArgs e)
{
var response = HttpContext.Current.Response;
if (response.StatusCode == 401)
{
//response.Headers.Add(
// "WWW-Authenticate",
// string.Format("Basic realm=\"{0}\"", Realm));
}
}
public void Dispose()
{
}
}
}
The most important part is inside CheckPassword method, there is where you should validate the credentials.
Another point is this line response.Headers.Add("WWW-Authenticate", string.Format("Basic realm=\"{0}\"", Realm)); if you don't comment this line, the classic login requested form will show up, and if you do comment this line you have to catch the 401 error in your requests.
If you want to know about realm: What is the “realm” in basic authentication.
Plus, you will need to register the module in your web.config file:
<system.webServer>
<modules>
<add
name="BasicAuthenticationHttpModule"
type="YourSolution.WebApp.Modules.BasicAuthenticationHttpModule" />
</modules>
</system.webServer>
Then I've added these two methods to deal with the authentication token:
// u: username; p: password
CreateBasicAuthenticationToken = function (u, p) {
var t = u + ':' + p;
var hat = btoa(t);
window.sessionStorage.setItem('basicauthtoken', 'basic ' + hat);
};
DestroyBasicAuthenticationToken = function () {
window.sessionStorage.removeItem('basicauthtoken');
};
The btoa method: The btoa() method of window object is used to convert a given string to a encoded data (using base-64 encoding) string.. Taken from: http://www.w3resource.com/javascript/client-object-property-method/window-btoa.php.
And last I've added the authtoken to the request header using the beforeSend:
$.ajax({
type: 'GET',
url: 'your url',
beforeSend: function (xhr) {
window.sessionStorage.getItem('basicauthtoken');
}
}).done(function (data, textStatus, xhr) {
//...
});
Please do note using jQuery outside an angular directive is not recommended, AngularJS best practices dictates jQuery code must be always placed inside a directive.
Hope it helps.

Is it possible to turn off ssl at the controller level of an ASP.Net MVC application?

Is there a way maybe via an action filter to turn off ssl for specific methods?
Example...if someone somehow hits my Index method and has ssl on...can I redirect to the method again and turn it off?
Global.asax.cs
protected void Application_BeginRequest(){
if (Context.Request.IsSecureConnection)
Response.Redirect(Context.Request.Url.ToString().Replace("https:", "http:"));
}
You could add the same code to an action filter:
public class SSLFilter : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext){
if (filterContext.HttpContext.Request.IsSecureConnection){
var url = filterContext.HttpContext.Request.Url.ToString().Replace("https:", "http:");
filterContext.Result = new RedirectResult(url);
}
}
}

How to attach a certificate to a C# client for an ODATA service?

We have an ODATA service which is being secured using certificates. We are using AddWebReference to get the proxy in our C# code.
Is there a way to attach the certificate that is in our certificate store to this generated proxy class?
We can add the certificate using HTTPClient, but we would like to avoid using HTTPClient to talk to our ODATA service and prefer to use the AddWebReference method.
I guess this is probably 3 years too late for you, but hopefully someone else will be helped.
This article actually explains in great detail what is required.
You need to add a ClientCertificate property to the container generated by adding a web reference and use it. You can do this by creating a partial class that adds the behaviour:
public partial class YourContainer
{
private X509Certificate clientCertificate = null;
public X509Certificate ClientCertificate
{
get
{
return clientCertificate;
}
set
{
if (value == null)
{
// if the event has been hooked up before, we should remove it
if (clientCertificate != null)
this.SendingRequest -= this.OnSendingRequest_AddCertificate;
}
else
{
// hook up the event if its being set to something non-null
if (clientCertificate == null)
this.SendingRequest += this.OnSendingRequest_AddCertificate;
}
clientCertificate = value;
}
}
private void OnSendingRequest_AddCertificate(object sender, SendingRequestEventArgs args)
{
if (null != ClientCertificate)
{
((HttpWebRequest)args.Request).ClientCertificates.Add(ClientCertificate);
}
}
}
On the instantiated container you can now set the ClientCertificate property with the certificate you need:
// Get the store where your certificate is in.
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
// Select your certificate from the store (any way you like).
X509Certificate2Collection certColl = store.Certificates.Find(X509FindType.FindByThumbprint, yourThumbprint, false);
// Set the certificate property on the container.
container.ClientCertificate = certColl[0];
store.Close();
WCF DataServices 5+ (not completely working)
Now if you're using WCF Dataservice 5+, then the SendingRequest event has been deprecated as indicated with the attribute [Obsolete("SendingRequest2 has been deprecated in favor of SendingRequest2.")] (not a typo on my side ;) ). I think you can still use it though, however, if you want to use SendingRequest2, your partial should look something like the following:
public partial class YourContainer
{
private X509Certificate clientCertificate = null;
public X509Certificate ClientCertificate
{
get
{
return clientCertificate;
}
set
{
if (value == null)
{
// if the event has been hooked up before, we should remove it
if (clientCertificate != null)
this.SendingRequest2 -= this.OnSendingRequest_AddCertificate;
}
else
{
// hook up the event if its being set to something non-null
if (clientCertificate == null)
this.SendingRequest2 += this.OnSendingRequest_AddCertificate;
}
clientCertificate = value;
}
}
private void OnSendingRequest_AddCertificate(object sender, SendingRequest2EventArgs args)
{
if (null != ClientCertificate)
{
((HttpWebRequestMessage)args.RequestMessage).HttpWebRequest.ClientCertificates.Add(ClientCertificate);
}
}
}
This worked for me for non-batch requests (found by trial and error as I couldn't find a lot of documentation about the differences between the SendingRequest and SendingRequest2).
However I seem to be experiencing some trouble now with args.RequestMessage being of type instead of HttpWebRequestMessage resulting in InvalidCastExceptions. Which is actually the reason I ended up on this question. It seems that it only goes wrong with Batch operations. The InternalODataRequestMessage has a private member requestMessage of type ODataBatchOperationRequestMessage. It doesn't seem to have any property to which I can add client certificates.
I have posted another question about that specific issue and will change this answer if the implementation I provided here turns out to be the problem.

How can I change a route value then redirect to that route?

I have a UserAccountController that takes routes like this "/{username}/{action}".
I'd like to create some functionality so that I can take a user to an account-specific page without knowing their username up front. I'd like to be able to use the URL "/your/{action}" which would catch the fact that "your" was sent as their username, get their real username (because they are logged in), and redirect them to "/their-actual-username/{action}".
I could do this in each of the controller actions, but I'd rather have it happen some place earlier that would do this for all of the controller actions. I attempted to do this in the Controller's Initialize method by changing the RouteData.Values["username"] to the real username then attempting to Response.RedirectToRoute(RouteData); Response.End() but that always took me to the wrong place (some completely wrong route).
Updated:
Thanks to BuildStarted for leading me to this answer:
public class UserAccountController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
if ((string) filterContext.RouteData.Values["username"] != "your")
return;
var routeValues = new RouteValueDictionary(filterContext.RouteData.Values);
routeValues["username"] = UserSession.Current.User.Username;
filterContext.Result = new RedirectToRouteResult(routeValues);
}
}
You can use the FilterAttribute with IActionFilter to accomplish what you want.
public class UserFilterAttribute : FilterAttribute, IActionFilter {
public void OnActionExecuted(ActionExecutedContext filterContext) {
}
public void OnActionExecuting(ActionExecutingContext filterContext) {
var username = filterContext.RouteData.Values["username"];
var realUserName = ""; //load from database
filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new { controller = "Users", action = "Index", username = realUserName }));
}
}
Then on your ActionResult in your controller you could apply [UserFilter] to the action.
[UserFilter]
public ActionResult UnknownUserHandler() {
return View();
}
This should get you the results you're looking for. Any questions please post :)