I'm trying to create my own RequireHttps attribute because I don't want to use the RequireHttpsAttribute from Mvc. I just want to reject non secure connections instead of forcing them to resend them using SSL. I copied a function from Microsoft web page, but for some reason, the attribute rejects both connections, http and https.
This is my code, anyone has any idea of how to fix it or another way to approach the solution?
[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Method, Inherited = true,
AllowMultiple = true)]
public class RequireHttpsAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext.Request.RequestUri.Scheme != Uri.UriSchemeHttps)
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
{
ReasonPhrase = "HTTPS Required"
};
}
else
{
base.OnAuthorization(actionContext);
}
}
}
actionContext.Request.Method != HttpMethod.Options
This part of the conditional will reject any request that does not use the OPTIONS HTTP method. So what this conditional translates to is that it will reject any requests except OPTIONS requests made over HTTPS. This would include any GET, POST, etc.
Try leaving off the second part of the conditional:
if (actionContext.Request.RequestUri.Scheme != Uri.UriSchemeHttps)
Related
I have been trying to recreate an Ajax version of the ValidateAntiForgeryToken - there are many blog posts on how to do this for previous versions of MVC, but with the latest MVC 6, none of the code is relevant. The core principle that I am going after, though, is to have the validation look at the Cookie and the Header for the __RequestVerificationToken, instead of comparing the Cookie to a form value. I am using MVC 6.0.0-rc1-final, dnx451 framework, and all of the Microsoft.Extensions libraries are 1.0.0-rc1-final.
My initial thought was to just inherit ValidateAntiForgeryTokenAttribute, but looking at the source code, I would need to return my own implementation of an an Authorization Filter to get it to look at the header.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ValidateAjaxAntiForgeryTokenAttribute : Attribute, IFilterFactory, IFilterMetadata, IOrderedFilter
{
public int Order { get; set; }
public bool IsReusable => true;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return serviceProvider.GetRequiredService<ValidateAjaxAntiforgeryTokenAuthorizationFilter>();
}
}
As such, I then made my own version of ValidateAntiforgeryTokenAuthorizationFilter
public class ValidateAjaxAntiforgeryTokenAuthorizationFilter : IAsyncAuthorizationFilter, IAntiforgeryPolicy
{
private readonly IAntiforgery _antiforgery;
private readonly ILogger _logger;
public ValidateAjaxAntiforgeryTokenAuthorizationFilter(IAntiforgery antiforgery, ILoggerFactory loggerFactory)
{
if (antiforgery == null)
{
throw new ArgumentNullException(nameof(antiforgery));
}
_antiforgery = antiforgery;
_logger = loggerFactory.CreateLogger<ValidateAjaxAntiforgeryTokenAuthorizationFilter>();
}
public async Task OnAuthorizationAsync(AuthorizationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (IsClosestAntiforgeryPolicy(context.Filters) && ShouldValidate(context))
{
try
{
await _antiforgery.ValidateRequestAsync(context.HttpContext);
}
catch (AjaxAntiforgeryValidationException exception)
{
_logger.LogInformation(1, string.Concat("Ajax Antiforgery token validation failed. ", exception.Message));
context.Result = new BadRequestResult();
}
}
}
protected virtual bool ShouldValidate(AuthorizationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
return true;
}
private bool IsClosestAntiforgeryPolicy(IList<IFilterMetadata> filters)
{
// Determine if this instance is the 'effective' antiforgery policy.
for (var i = filters.Count - 1; i >= 0; i--)
{
var filter = filters[i];
if (filter is IAntiforgeryPolicy)
{
return object.ReferenceEquals(this, filter);
}
}
Debug.Fail("The current instance should be in the list of filters.");
return false;
}
}
However, I cannot find the proper Nuget package and namespace that contains IAntiforgeryPolicy. While I found the interface on GitHub - what package do I find it in?
My next attempt was to instead go after the IAntiforgery injection, and replace the DefaultAntiforgery with my own AjaxAntiforgery.
public class AjaxAntiforgery : DefaultAntiforgery
{
private readonly AntiforgeryOptions _options;
private readonly IAntiforgeryTokenGenerator _tokenGenerator;
private readonly IAntiforgeryTokenSerializer _tokenSerializer;
private readonly IAntiforgeryTokenStore _tokenStore;
private readonly ILogger<AjaxAntiforgery> _logger;
public AjaxAntiforgery(
IOptions<AntiforgeryOptions> antiforgeryOptionsAccessor,
IAntiforgeryTokenGenerator tokenGenerator,
IAntiforgeryTokenSerializer tokenSerializer,
IAntiforgeryTokenStore tokenStore,
ILoggerFactory loggerFactory)
{
_options = antiforgeryOptionsAccessor.Value;
_tokenGenerator = tokenGenerator;
_tokenSerializer = tokenSerializer;
_tokenStore = tokenStore;
_logger = loggerFactory.CreateLogger<AjaxAntiforgery>();
}
}
I got this far before I stalled out because there is no generic method on ILoggerFactory for CreateLogger<T>(). The source code for DefaultAntiforgery has Microsoft.Extensions.Options, but I cannot find that namespace in any Nuget package. Microsoft.Extensions.OptionsModel exists, but that just brings in the IOptions<out TOptions> interface.
To follow all of this up, once I do get the Authorization Filter to work, or I get a new implementation of IAntiforgery, where or how do I register it with the dependency injection to use it - and only for the actions that I will be accepting Ajax requests?
I had similar issue. I don't know if any changes are coming regarding this in .NET but, at the time, I added the following lines to ConfigureServices method in Startup.cs, before the line services.AddMvc(), in order to validate the AntiForgeryToken sent via Ajax:
services.AddAntiforgery(options =>
{
options.CookieName = "yourChosenCookieName";
options.HeaderName = "RequestVerificationToken";
});
The AJAX call would be something like the following:
var token = $('input[type=hidden][name=__RequestVerificationToken]', document).val();
var request = $.ajax({
data: { 'yourField': 'yourValue' },
...
headers: { 'RequestVerificationToken': token }
});
Then, just use the native attribute [ValidadeAntiForgeryToken] in your Actions.
I've been wrestling with a similar situation, interfacing angular POSTs with MVC6, and came up with the following.
There are two problems that need to be addressed: getting the security token into MVC's antiforgery validation subsystem, and translating angular's JSON-formatted postback data into an MVC model.
I handle the first step via some custom middleware inserted in Startup.Configure(). The middleware class is pretty simple:
public static class UseAngularXSRFExtension
{
public const string XSRFFieldName = "X-XSRF-TOKEN";
public static IApplicationBuilder UseAngularXSRF( this IApplicationBuilder builder )
{
return builder.Use( next => context =>
{
switch( context.Request.Method.ToLower() )
{
case "post":
case "put":
case "delete":
if( context.Request.Headers.ContainsKey( XSRFFieldName ) )
{
var formFields = new Dictionary<string, StringValues>()
{
{ XSRFFieldName, context.Request.Headers[XSRFFieldName] }
};
// this assumes that any POST, PUT or DELETE having a header
// which includes XSRFFieldName is coming from angular, so
// overwriting context.Request.Form is okay (since it's not
// being parsed by MVC's internals anyway)
context.Request.Form = new FormCollection( formFields );
}
break;
}
return next( context );
} );
}
}
You insert this into the pipeline with the following line inside the Startup.Configure() method:
app.UseAngularXSRF();
I did this right before the call to app.UseMVC().
Note that this extension transfers the XSRF header on any POST, PUT or DELETE where it exists, and it does so by overwriting the existing form field collection. That fits my design pattern -- the only time the XSRF header will be in a request is if it's coming from some angular code I've written -- but it may not fit yours.
I also think you need to configure the antiforgery subsystem to use the correct name for the XSRF field name (I'm not sure what the default is). You can do this by inserting the following line into Startup.ConfigureServices():
services.ConfigureAntiforgery( options => options.FormFieldName = UseAngularXSRFExtension.XSRFFieldName );
I inserted this right before the line services.AddAntiforgery().
There are several ways of getting the XSRF token into the request stream. What I do is add the following to the view:
...top of view...
#inject Microsoft.AspNet.Antiforgery.IAntiforgery af
...rest of view...
...inside the angular function...
var postHeaders = {
'X-XSRF-TOKEN': '#(af.GetTokens(this.Context).FormToken)',
'Content-Type': 'application/json; charset=utf-8',
};
$http.post( '/Dataset/DeleteDataset', JSON.stringify({ 'siteID': siteID }),
{
headers: postHeaders,
})
...rest of view...
The second part -- translating the JSON data -- is handled by decorating the model class on your action method with [FromBody]:
// the [FromBody] attribute on the model -- and a class model, rather than a
// single integer model -- are necessary so that MVC can parse the JSON-formatted
// text POSTed by angular
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult DeleteDataset( [FromBody] DeleteSiteViewModel model )
{
}
[FromBody] only works on class instances. Even though in my case all I'm interested in is a single integer, I still had to dummy up a class, which only contains a single integer property.
Hope this helps.
Using a anti forgery token in a Ajax call is possible but if you are trying to secure a Api I really would suggest using a Access Token instead.
If you are relying on a identity token stored in a cookie as authentication for your Api, you will need to write code to compensate for when your cookie authentication times out, and your Ajax post is getting redirected to a login screen. This is especially important for SPAs and Angular apps.
Using a Access Token implementation instead, will allow you to refresh you access token (using a refresh token), to have long running sessions and also stop cookie thiefs from accessing your Apis.. and it will also stop XSRF :)
A access token purpose is to secure resources, like Web Apis.
I have the following code:
CookieHeaderValue cookie = Request.Headers.GetCookies("session").FirstOrDefault();
var isAuthenticated = _userService.IsAuthenticated(cookie);
if (!isAuthenticated)
return Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "");
I'd like this code to execute as soon as any part of my api is called. I havn't found any good solutions or ways to do this so i thought i would ask here instead.
(what I do now is execute the code in every get/post/put/delete which is horrible).
The best place to solve this would be an authorization filter attribute. See Authentication Filters in ASP.NET Web API 2.
The subject is too broad to repeat here in its entirety, but it comes down to creating an attribute:
public class CookieAuthenticationFilterAttribute : Attribute, IAuthenticationFilter
{
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
// your cookie code
}
}
And applying it to the controller or action methods:
[YourCookieAuthentication]
But be sure to read the link.
You can use an ActionFilter or AuthorizationFilter for this purpose. These are attribute classes that you can use on specific controllers/actions or globally. So you don't need to repeat the code for every action.
See this link for details. It shows the general authentication/authorization flow in ASP.NET Web API and how you can customize it.
So i found the best solution for my problem was the following code:
public class CookieFilterAttribute : AuthorizeAttribute
{
[Inject]
public IUserService UserService { get; set; }
protected override bool IsAuthorized(HttpActionContext actionContext)
{
CookieHeaderValue cookie = actionContext.Request.Headers.GetCookies("session").FirstOrDefault();
var isAuthenticated = UserService.IsAuthenticated(cookie);
return isAuthenticated;
}
}
How can I make IsAuthorized return my custom object while function returns false?
In my WebAPI project I have a class like;
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(System.Web.Http.Controllers.HttpActionContext actionContext)
{
StandardWebAPIResponse invalidUserResponse = new StandardWebAPIResponse()
{
code = (int) Constants.ErrorCodes.InvalidCredentials,
data = "InvalidCredentials.",
StatusCode = HttpStatusCode.Unauthorized
};
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized,
invalidUserResponse);
// if I set this to true I am getting 401 with my custom object
// otherwise it gives me default error message
// {"Message":"Authorization has been denied for this request."}
return false;
}
}
For some reason when I return false from IsAuthorized function, it does not return my custom invalidUserResponse object. But if I return true it returns it.
How can I resolve this issue?
I know this question has been answered but I feel like it is slightly wrong. I don't think that a message for unauthorized request should be handled by OnAuthorization but should be handled by HandleUnauthorizedRequest. I'm not sure if it will cause any major problems putting it in OnAuthorization, but presumably the reason you were getting the message when true and not false is because the base class writes over your response in HandleUnauthorizedRequest.
It is a subtle thing but the OnAuthorization directs to the HandleUnauthorizedRequest for a reason. It is mainly a separation of responsibilities thing, but if you ever want to do more than just sending an error message, like log bad request your OnAuthorization method will probably get crowded. You should used the methods given to you for clarity sake if nothing else.
Yes I agree. You would need to implement custom filter derived from AuthorizeAttribute.
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
base.HandleUnauthorizedRequest(actionContext);
actionContext.Response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.Unauthorized,
Content = new ObjectContent(typeof(ErrorMessage),
new ErrorMessage()
{
StatusCode = (int)HttpStatusCode.Unauthorized,
Message = Constants.UnauthorisedErrorMessage,
ErrorCode = Constants.UnauthorisedErrorCode
}, new JsonMediaTypeFormatter())
};
}
You should override the OnAuthorization method, that use the IsAuthorized and flushs the response, or force a flush at your method. Makes more sense fill the response where the filter manipulates It.
I've implemented the following action attribute in my MVC solution.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeADAttribute : AuthorizeAttribute
{
public string[] Groups { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (base.AuthorizeCore(httpContext))
{
/* Return true immediately if the authorization is not
locked down to any particular AD group */
if (Groups == null)
return true;
foreach (var group in Groups)
if (httpContext.User.IsInRole(group))
return true;
}
return false;
}
}
And invoked it like this:
public const string Admin = "MY_DOMAIN\\Admins";
public const string Users = "MY_DOMAIN\\Users";
public const string AddUser = "MY_DOMAIN\\AddUser";
[AuthorizeAD(Groups = new string[] { Admin, Users })]
public ActionResult GridData(...)
{ ... }
[AuthorizeAD(Groups = new string[] { Admin, Users, AddUser })]
public ActionResult Add(...)
{ ... }
It seemed like it was working fine so far (locally without a problem), until someone noticed (on another question I posted), that I've been receiving 401 errors on the deployed instance.
I think my AuthorizeADAttribute need to be reworked, unless anyone has an idea of what the issue could be on the host environment. The idea is that a user must be in the admin or user group on the active directory to access the site, and if he/she is assigned to the user role, they need to belong to one other group as well, eg: Add, Delete, Update, etc...
So far I'm pretty much stumped :/
It seemed like it was working fine so far (locally without a problem),
until someone noticed (on another question I posted), that I've been
receiving 401 errors on the deployed instance
That's perfectly normal and it is how NTLM authentication works. It's a challenge-response authentication protocol meaning that the server challenges the client by sending a 401 page to which the client responds, ... So the 401s you are seeing are parts of the challenge that the server sent to the client to authenticate himself. You see that in the end the client successfully responded to the challenge and was authenticated with a 200 success.
I don't think that you should be reworking anything with your custom authorize attribute. It's just that you probably don't need it as you could achieve similar functionality with the default Authorize attribute:
[Authorize(Roles = "MY_DOMAIN\\Admins,MY_DOMAIN\\Users" })]
public ActionResult GridData(...)
I've read thru many of the questions on ASP.NET MVC [RequireHttps] - but can't find the answer to this question:
How do you make the [RequireHttps] attribute switch the url to https if it was not https to start with?
I have this code:
public ActionResult DoSomething()
{
return View("AnotherAction");
}
[RequireHttps]
public ActionResult AnotherAction()
{
return View();
}
But I get an error saying: "The requested resource can only be accessed via SSL."
The MVC futures project has a similar attribute [RequireSsl(Redirect = true)]. But that is outdated now ... What is the equivalent in MVC 2?
When someone types in the URL http://example.com/home/dosomething OR the url http://example.com/home/anotheraction, I need them to be automatically redirected to the url https://example.com/home/anotheraction
EDIT this is the sequence of events:
The URL http://example.com/home/dosomething is called from another website. They redirect their users to this url (with a response.redirect or similar).
DoSomething() then tries to return AnotherAction(), but fails with the error message "The requested resource can only be accessed via SSL."
The RequiresHttps attribute does automatically attempt to redirect to https://your-url. I verified this behavior on a site I have that uses that attribute, and also looking at the code in Reflector:
protected virtual void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
if (!string.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(MvcResources.RequireHttpsAttribute_MustUseSsl);
}
string url = "https://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
Are you sure you have your site set up to accept secure connections? What happens if you try to browse to https://your-url directly?
[mvc 4] short answer:
protected void Application_BeginRequest(Object source, EventArgs e)
{
if (!Context.Request.IsSecureConnection)
{
Response.Redirect(Request.Url.AbsoluteUri.Replace("http://", "https://"));
}
}
longer answer:
to move from http to https you cant send a redirect to https after the first packet,
therefor you need to catch the packet using Application_BeginRequest,
from the Global.asax add the function and it will override the default,
the code should be something like so (Global.asax on the class level):
protected void Application_BeginRequest(Object source, EventArgs e)
{
if (!Context.Request.IsSecureConnection &&
!Request.Url.Host.Contains("localhost") &&
Request.Url.AbsolutePath.Contains("SGAccount/Login"))
{
Response.Redirect(Request.Url.AbsoluteUri.Replace("http://", "https://"));
}
}
i strongly suggest putting a breakpoints and inspecting the Request.Url object for any url related need.
or visit the msdn page confused about request.url absoluteuri vs originalstring?
so am i you can go to dotnetperls for examples.
this function enables you to develop on localhost and deploying your code as is.
now for every page you want to make a https redirect you need to specify it in the if condition.
to move from https to http you can use regular Response.Redirect like so:
if (Request.Url.Scheme.Contains("https"))
{
Response.Redirect(string.Format("http://{0}", Request.Url.Authority), true);
}
notice this also support working on the same code when developing on local host by not interrupting the original course of things pre the https addition.
also i recommend thinking about implementing some return url convention (if not already implemented) in that case you should go something like so:
if (Request.Url.Scheme.Contains("https"))
{
Response.Redirect(string.Format("http://{0}{1}", Request.Url.Authority, returnUrl), true);
}
this will redirect to the requested page post login.
naturally you should protect every page that shows user data, register, login and more.
Http HEAD requests do not appear to be redirected. When reviewing our error logs we see lots of this message, googling lands here, but after looking more at the details they have a few interesting "features"
Request_method: HEAD
User Agent: curl/7.35.0
In other words all of the failed attempts were not customer facing...
(100% credit to comment from #arserbin3 for making me realize they were all HEAD requests)
MVC4 does now redirect
but not how you would expect.
http://www.example.com:8080/alpha/bravo/charlie?q=quux
will be redirect the client's browser to
https://www.example.com/alpha/bravo/charlie?q=quux
Notice the lack of a port number.
http://aspnetwebstack.codeplex.com/SourceControl/latest#test/System.Web.Mvc.Test/Test/RequireHttpsAttributeTest.cs
code test
[Fact]
public void OnAuthorizationRedirectsIfRequestIsNotSecureAndMethodIsGet()
confirms this is the desired behaviour.
If you would like to write a custom attribute that does include the PORT ... you can base your code on:
http://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Mvc/RequireHttpsAttribute.cs
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class RequireHttpsAttribute : FilterAttribute, IAuthorizationFilter
{
public RequireHttpsAttribute()
: this(permanent: false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RequireHttpsAttribute"/> class.
/// </summary>
/// <param name="permanent">Whether the redirect to HTTPS should be a permanent redirect.</param>
public RequireHttpsAttribute(bool permanent)
{
this.Permanent = permanent;
}
/// <summary>
/// Gets a value indicating whether the redirect to HTTPS should be a permanent redirect.
/// </summary>
public bool Permanent { get; private set; }
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, this.Permanent);
}
}
To supplement the answer already given, this is the code from the MVC 5 implementation of HandleNonHttpsRequest
protected virtual void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
// only redirect for GET requests, otherwise the browser might not propagate the verb and request
// body correctly.
...
}