I was trying to make a guard for some action so it is not accessible unless the request comes from a certain host. Here is the sample code.
public ActionResult test()
{
if (Request.UrlReferrer == null || Request.UrlReferrer.Host != "mydomain.com") { return Content("Blocked!"); }
else { return Content("Authorized!"); }
}
Everything seems to work well until I went to mydomain.com "typed the link in the addressbar" , opened the browser console and typed
window.location.href = "https://domainholdingthatacion.whatever/ActionRoute/test"; //trying to get unauthorized access
It worked! It enters the else branch. I need your input because I have no idea if I am using it wrong as Request.UrlReferrer is not meant to be used for that or It is inherently vulnerable.
UrlReferrer is not safe to use for authorization. Any browser or client can decide to set the referrer url to whatever they want. Additionally some browsers block refuse respecting this header (though that's mostly only true between different domains) for privacy reasons.
There's the Authorize annotation specifically for this.
Authorize attribute in ASP.NET MVC
https://www.red-gate.com/simple-talk/dotnet/asp-net/thoughts-on-asp-net-mvc-authorization-and-security/
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-3.1
Related
I have written a webservice that basically inserts data into the central database and is expected to be consumed by multiple clients from multiple systems.
It works but the problem is that it can be accessed by anyone hence making it vulnerable to be used by anybody i.e. anybody can spam by entering data or anything.
One way is to check for the Session variable but how would I know the name of the session variable of the client consuming the system or may be he's not authenticating that way?
So what should I do to make it secure?
[WebMethod(EnableSession= true)]
public int InsertEngineeringData(string FunctionalLocation, string EqptType, string WINFileNo, string ComponentTagNo)
{
try
{
if (Session["User"] != null)
{
}
int EngineeringDataID = 0;
EngineeringDataDAL EngineeringDataDAL = new Vail_PlantWebApi.EngineeringDataDAL();
EngineeringDataID = EngineeringDataDAL.InsertEngineeringData(FunctionalLocation, EqptType, WINFileNo, ComponentTagNo);
return EngineeringDataID;
}
catch (Exception ex)
{
throw ex;
}
}
If it is an asmx webservice, then use the link Crocoder posted or another quick way if it works is you can try the [Authorize] attribute although I'm not sure if that will work with an inline webmethod you're using, I've only seen it used in WebAPI. Authorize attribute in ASP.NET MVC
A more robust way that would definitely work is you add a column to the Users table called 'CurrentSessionID' and another one that says 'LastLoginDateStamp' For each login request if you have a valid user you update their session there and a datestamp. Then when the user hits the api, you compare the session and make sure it hasn't exceeded what you decide is a valid threshold for the last login, maybe 24 hours for example.
There would be a lot more work to do after that, but that's the basic idea.
I have an Angular app that integrates with IdentityServer4 with implicit flow and the angular-oauth2-oidc library.
Everything seems to work fine, I can log in; and access token is available.
If I click the logout button, I handle it like:
logout() {
this.oauthService.logOut();
}
...and I'm redirected to Identity Server, where it asks me if I really want to log out.
My question is whether I can bypass that prompt? I mean, I want to log out completely if the button is clicked and redirected back to the Angular site, without the need to confirm it?
How can this be achieved?
EDIT: as mentioned in the other answers, it should work if you pass id_token_hint. So I did:
logout() {
this.oauthService.customQueryParams = {
'id_token_hint': this.oauthService.getIdToken()
};
this.oauthService.logOut();
}
But it doesn't make any difference.
There were two issues I needed to fix in order to make this work.
In IdentityServer, AccountOptions class, I had to set this property to true instead of false:
public static bool AutomaticRedirectAfterSignOut = true;
Next, In IdentityServer client configuration, I had to define the post logout redirect uri:
PostLogoutRedirectUris = new List<string> {"http://...."}
That did it. I did not have to change anything in the Angular client.
I'm trying to implement HTTPS on selected pages of my site. Using the attribute RequireHttps works but causes problems testing as we don't have a cert installed locally.
The solution I'm looking for will need to ignore localhost and ignore one test server while working on our second test server where we do have a cert in place.
Some further background on this. The aim is to move the site gradually to https. It's an ecommerce site so obviously portions are already secure and I know that for many reasons moving the entire site to secure is a good thing. I also know that once you move from Page A to Page B where B is secure then it won't go back to HTTP when you move back to A, that's fine.
I want to move the site in stages just in case there are problems with things like mismatched content, site maps, SEO, google ranking etc.
Some of the various solutions I have tried - I've implemented a class derived from the RequireHttps attribute as follows:
public class CustomRequireHttps : RequireHttpsAttribute
{
protected override void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.Url != null && (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)
&& !String.Equals(filterContext.HttpContext.Request.HttpMethod, "HEAD", StringComparison.OrdinalIgnoreCase)
&& !filterContext.HttpContext.Request.Url.Host.Contains("localhost")
&& !filterContext.HttpContext.Request.Url.Host.Contains("testing")))
{
base.HandleNonHttpsRequest(filterContext);
}
}
}
And have applied this attribute to one page but it hasn't worked as intended, it either applies HTTPS to all pages on the site or doesn't work at all.
I have also tried this solution which works but only on localhost and not on the two test servers:
#if !DEBUG
[RequireHttps]
#endif
Then I tried overriding the OnAuthorizartion method like so:
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (filterContext.HttpContext != null && filterContext.HttpContext.Request.IsLocal)
{
return;
}
base.OnAuthorization(filterContext);
}
It worked locally but once I got it onto the server with the test cert suddenly every page is HTTPS which I do not understand as I've only used this derived attribute on one page.
So, what I'm looking to achieve is to implement HTTPS on a select number of pages on my site. This HTTPS request needs to be ignored on localhost and the first test server but, it needs to NOT to be ignored on the second test server which has a cert.
So far it either doesn't work at all or is on every page on the site.
However, and this is the kicker, If I use the RequireHttps attribute it works perfectly on the second test server but causes problems on all servers without a cert. By 'works perfectly' I mean it implements HTTPS only on the pages where I've used that attribute and does not suddenly switch all pages to secure.
Any ideas what I'm doing wrong here?
There can be a lot going on, for example when your links are local, when a switch is made to HTTPS, all pages are HTTPS (not applying require HTTPS doesn't switch back to HTTP). From a security standpoint, you should serve all pages from HTTPS when you need it for a subset of pages (otherwise, you might share secure cookies / login tokens over unencrypted HTTP). So probably your attribute is applied, and all subsequent requests are served over SSL.
Secondly, testing on localhost request uri will serve the page over HTTP on your second server. My opinion to solve this problem is to create a switch in your web.config if the pages should be served over HTTPS. Check this switch in your global filterConfig:
public static class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
var useSsl = Convert.ToBoolean(ConfigurationManager.AppSettings["useSsl"]);
if (useSsl )
{
filters.Add(new RequireHttpsAttribute());
}
}
}
The objective is to add a maintenance batch on the same url of the administration of an Azure MVC site. The url should be something like:
https://admin.mysite.com/Batch?pass=HKE671
I decided to override OnActionExecuting and to capture the information I need in the url to trigger the maintenance method. I am not familiar with MVC projects, and this may not sound very conventional...
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
EmailUtility.SendSupportEmail("DEBUG - On result executing", ""); //I receive this email
int y = 14;
if (Request.Url.Query.Length > y)
{
string url = Request.Url.Query.Remove(0, y); // I remove ?ReturnUrl=%2f
if (url.StartsWith("Batch"))
{
mySite.Admin.Web.Controllers.TenantController controller = new mySite.Admin.Web.Controllers.TenantController();
EmailUtility.SendSupportEmail("DEBUG - starts maintenance", ""); // I don't receive this one
controller.maintenance(HttpUtility.ParseQueryString(url).Get("pass"));
};
}
base.OnActionExecuting(filterContext);
}
This code works as well as I need on local, the maintenance method is called and does the job. But when deployed on Azure, this modification throws a 404 error. Interestingly, I send two debug emails : I don’t receive the second one "DEBUG - starts maintenance", therefore my 404 error comes from the instantiation of a controller in OnActionExecuting.
First, I would like to understand why is the behavior different between my development machine and Azure?
Then, how can I make it work? Thanks,
EDITED on Jan 4:
I made a little progress, but this issue is still unsolved.
- About the difference between my dev machine and Azure: there are a few redirections on this site: https, 404 and non-existent domain. I assumed it was due to a 404 error. Encapsulating the code with a try/catch didn't sent me any error, so I am guessing that I can suppress the 404 from the hypothesis.
- I tried the code above on OnAuthorization without having more success.
- I noticed that the first email DEBUG - On result executing is in fact sent at the first test only. It is not sent the second time I run my test. This doesn't make any sense to me, because the session should be checked every time.
Conclusion for today: it seems to be more a routing/redirection problem.
Why don't you do:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
try {
//your code here
} catch (Exception ex){
EmailUtility.SendSupportEmail("Execution failed: " + ex.Message , "");
}
}
If your code is throwing an exception, this should give you a better understanding on why it is failing.
But more important, a more robust way of checking for the correct URL would be:
if (filterContext.HttpContext.Request.Url.AbsolutePath.EndsWith("Batch")){
}
As far as i can tell your string variable "url" will not ever start with "Batch", because Request.Url.Query only conains the part after the "?" thus making your check always return false. Why this IS working on localhost is hard to tell, but in my opinion it shouldn't.
Actually, to work in all cases your check should be:
if (filterContext.HttpContext.Request.Url.AbsolutePath.ToLower().EndsWith("batch") || filterContext.HttpContext.Request.Url.AbsolutePath.ToLower().EndsWith("batch/")){
}
After your edit:
Ok, so what's happening is that you're requesting the action "Batch" in Home Controller, but because you're not authenticated you are redirected to LogOn in Account Controller. You should be seeing a login page, unless there's no associated LogOn view in your Views folder, or no AccountController at all, in both cases an exception is thrown.
Is the Batch method supposed to check for authentication? If not, remove the [Authorize] annotation from this method in HomeController and you should be fine.
You still have to adjust the if-check though, because it actually only evaluates to true on the LogOn page, and false if you actually get to the Batch page.
Shame on me, the problem was somewhere else!... The code above works well, but the routing of this site rejected an incomplete url. In this case, it had to contain the project name: https://admin.mysite.com/Admin/Batch?pass=HKE671 Then all is back to normal!
So the answer is: the behavior is different on my local computer because the local host of my development machine doesn't route projects of the solution the same way Azure does.
Many thanks for your help!
Problem
Long story short, how can you prevent unauthorized ChildActions to return a 401 code and return an empty result instead.
Context
My application uses NTLM to authenticate on the network. I also want to handle anonymous identification, for non NTLM capable devices or if someone goes through some proxy.
One the the web application, there is controller/actions that absolutely require to be logged in to be viewable and some other that are viewable as anonymous. For the ones that authentication is mandatory where I initially used the following:
[Authorize()]
public class SomeController : BaseController
Which works as expected and returns a 401 Status Code (Unauthorized).
The problem starts on the page that allows anonymous. Because those pages have parts that uses RenderAction to render pieces that requires authentication.
On local developpement server on front end everything looks fine, but with IIS any page that have any small piece that requires authentication it returns 401 page. So I created a custom Authorize attribute:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public bool Ignore401ChildAction { get; set; }
public CustomAuthorizeAttribute()
: base() {
this.Ignore401ChildAction = true;
}
protected override void HandleUnauthorizedRequest( AuthorizationContext filterContext ) {
base.HandleUnauthorizedRequest( filterContext );
if( this.Ignore401ChildAction && filterContext.IsChildAction ) {
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}
}
}
And then on frontend it seems to produce correct behavior with and without IIS. However in the backend, even if the page renders completly it returns a 401
So I added the following to my CustomAttribute instead of TrySkipIisCustomErrors
filterContext.HttpContext.Response.StatusCode = 200;
filterContext.Result = new EmptyResult();
In anonymous everything is fine, but when NTLM is enabled server doesn't request authentication when the controller doesnt have that CustomAuthorize attribute. It seems like its only requesting the crendetials when their mandatory instead of when they're available.
Thank you,
EDIT
After lot of searching, digging and fiddling, realized that true problem is that Anonymous identification will take precedence over Windows/NTLM. If Anonymous and Windows authentication are enabled, Windows will only execute if anonymous authentication fails.
SOLUTION(not perfect and kind of hackish but it works)
Add the following function to your BaseController then call from the views that do not mandatory require authentication, or simply call it from your _Layout view.
NOTE: Even though page will render correctly as anonymous, a 401 status code will still be returned.
[CustomAuthorize]
public ActionResult Auth() {
return new EmptyResult();
}