ControllerBase class has Challenge method, that returns an object of the ChallengeResult class.
CookieAuthenticationOptions class has AutomaticChallenge property.
I believe ChallengeResult has something to do with external logins. But how does it actually work? Where does the term "Challenge" come from? What does lay inside this.
A ChallengeResult is an ActionResult that when executed, challenges the given authentication schemes' handler. Or if none is specified, the default challenge scheme's handler. Source code for ChallengeResult
So for example, you can do:
return Challenge(JwtBearerDefaults.AuthenticationScheme); //Can specify multiple schemes + parameters
This will challenge the JWT Bearer authentication handler.
In this handler's case, it sets the response status code to 401 to tell the caller they need authentication to do that action.
AutomaticChallenge (in ASP.NET Core 1.x) is the setting that says this is the default challenge handler. It means it will be called if no authentication scheme is specifically named.
In 2.x, this was changed such that you now specify the default challenge scheme or the higher-level default scheme.
services.AddAuthentication(o =>
{
o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; //Default for everything
// o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; //Default specifically for challenges
})
A challenge is basically a way of saying "I don't know who this user is, please verify their identity". So if the authentication handler triggered is e.g. the Facebook authentication handler, it will react to the challenge by issuing a redirect to the Facebook authentication page. A local account authentication handler might issue a redirect to the local sign-in page.
In the case of JWT Bearer authentication, the handler cannot do anything other than respond with a 401 status code and leave it up to the caller to authenticate themselves properly.
You can see this in action in OAuthHandler (HandleChallengeAsync), which Facebook auth uses (and Microsoft and Google authentication).
You typically return a Challenge when you don't know who the user is, and a Forbid if you know who they are, but they are not allowed to do the action they tried to do.
Related
I have this controller
[Route("Authentication")]
public class AuthenticationController : Controller
{
and this action
[HttpGet("SignOut")]
public async Task<IActionResult> SignOut([FromQuery] string sid)
{
await ControllerContext.HttpContext.SignOutAsync(Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectDefaults.AuthenticationScheme);
await ControllerContext.HttpContext.SignOutAsync(Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationDefaults.AuthenticationScheme);
return View();
This works as expected.
But when I configure a SignedOutCallbackPath for my OpenId authentication that has the same route, it doesn't work anymore. The constructor of my controller is not called, the action is not hit and the result in the browser is a blank page (code 200) with html/head/body, but all empty, that doesn't match any template or view.
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.Cookie.HttpOnly = true;
})
.AddOpenIdConnect(options =>
{
options.SignedOutCallbackPath = "/Authentication/SignOut";
Is the SignedOutCallbackPath not supposed to be a view of my own?
The callback paths in the OpenID Connect authentication scheme are internal paths that are used for the authentication flow of the OpenID Connect protocol. There are three of those:
CallbackPath – The path the authentication provider posts back when authenticating.
SignedOutCallbackPath – The path the authentication provider posts back after signing out.
RemoteSignOutPath – The path the authentication provider posts back after signing out remotely by a third-party application.
As you can see from my explanation, these are all URLs that the authentication provider uses: They are part of the authentication flow and not to be directly used by your users. You also don’t need to worry about handling those things. The OpenID Connect authentication handler will automatically respond to these requests when the authentication middleware runs.
This means that when you change a callback path to some path that is a route of one of your controller actions, then the authentication middleware will handle that request before your controller gets involved. This is by design, so that you do not need to worry about these routes as they are mostly internal.
You just have the option to change those paths if you cannot or do not want to use the default values.
Now, there are two possible things I can think of that you could have meant to change instead:
SignedOutRedirectUri: This is the URL the user gets redirected to after the sign-out process is completed. This basically allows you to send the user to some view with e.g. a message “you were successfully signed out”, to show that the sign-out is done.
You can also set this as part of the AuthenticationProperties that you can pass to the SignOutAsync.
CookieAuthenticationOptions.LogoutPath: This is the URL that is configured to the actual URL that users can go to to sign out of the application. This does not really have that much of an effect though.
Otherwise, it’s really up to you to send users to your /Authentication/SignOut URL. You can put a button into your layout that goes there for example, to offer users a sign out functionality at all times.
Your Action is expecting parameter which is not passed by your callback, the parameter is seemingly not used within the action either, so you can either omit the parameter or make it optional
[HttpGet("SignOut")]
public async Task<IActionResult> SignOut()
{
await ControllerContext.HttpContext.SignOutAsync(Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectDefaults.AuthenticationScheme);
await ControllerContext.HttpContext.SignOutAsync(Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationDefaults.AuthenticationScheme);
return View();
}
For Azure AD I found this post logout redirection behavior:
It doens't work for root accounts of the tenant, that is my personal account, which created Azure
subscription.
But it works for new accounts I created inside of my
subscription.
In several places in a standard ASP.Net MVC Identity 2.0 Owin implementation you'll see rememberBrowser, like:
await signInManager.SignInAsync(user, isPersistent: isPersistent, rememberBrowser: false);
If you do set rememberBrowser to true, I've noticed that I can kill the browser, kill IIS Express, delete the user the browser was logged in as, even restart my machine, and the browser is still treated as logged-in. Not so great, considering a deleted user being treated as authorized/logged-in is going to cause all sorts of issues in code behind the [Authorize] attribute that expects to have a valid user to work with.
So what is it exactly that rememberBrowser is doing, and is there any risk that someone could just fake rememberBrowser in their cookies to bypass OWIN login? It seems the point of [Authorize] is to guarantee no one but logged-in users access a given Controller Action, and rememberBrowser seems to be a hole in that guarantee.
Bonus question: Is there a way to disable rememberBrowser so that even if a forged cookie did come in, it would be rejected?
I think rememberBrowser is relevant only in Two-factor authentication. So if you set it to true, the browser will acquire TwoFactorRememberBrowser cookie which allow the user to skip 2FA authentication (if enabled) during the login process.
Is there a way to disable rememberBrowser so that even if a forged
cookie did come in, it would be rejected?
The cookie created from rememberBrowser is used in conjunction with the authentication cookie. It will only allow the user to skip 2FA, therefore it is useless without being authenticated first.
The answer by #Hezye is correct, but I'll elaborate on this a bit more.
Here is the code that creates an identity for "rememberBrowser" CreateTwoFactorRememberBrowserIdentity (https://aspnetidentity.codeplex.com/SourceControl/latest#src/Microsoft.AspNet.Identity.Owin/Extensions/AuthenticationManagerExtensions.cs line 215):
public static ClaimsIdentity CreateTwoFactorRememberBrowserIdentity(this IAuthenticationManager manager,
string userId)
{
if (manager == null)
{
throw new ArgumentNullException("manager");
}
var rememberBrowserIdentity = new ClaimsIdentity(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
rememberBrowserIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userId));
return rememberBrowserIdentity;
}
So this identity is with type of "TwoFactorRememberBrowserCookie" and with only claim of user ID.
Looking on the source code of SignInManager that uses this code: (https://aspnetidentity.codeplex.com/SourceControl/latest#src/Microsoft.AspNet.Identity.Owin/SignInManager.cs line 106) :
if (rememberBrowser)
{
var rememberBrowserIdentity = AuthenticationManager.CreateTwoFactorRememberBrowserIdentity(ConvertIdToString(user.Id));
AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = isPersistent }, userIdentity, rememberBrowserIdentity);
}
Here IAuthenticationManager is used to sign-in 2 identities: one for the actual user, another for "rememberBrowser". And I believe this will produce 2 cookies - one user authentication cookie, another remembering the browser.
In SignInManager when using SignInOrTwoFactor the code (line 218) checks if "RememberBrowser" identity is already set in the cookies.
OWIN cookies are protected by encryption, encryption is borrowed from DpapiDataProtector (documentation). I'm no expert in cryptography so can't comment on the strength of cryptography. I'm just saying that "rememberBrowser" cookie is encrypted the same way as the main authentication cookie.
Regarding your exercise where you restarted your IIS, machine, etc. Have you removed the cookies from the browser? Because if you have not, Identity (or rather OWIN) will treat browser as logged-in, even if the original user record is removed from the database. Though user will not be logged-in for long as there is code in the default template MVC that checks with the database for the user record and logs out if user record have been changed.
As for disabling "rememberBrowser" - always pass false to that argument. And the second cookie will not be set.
my MVC app has common ajax methods (in web api and regular controller). I'd like to authorize these calls based on which area (view) of my app the call is coming from. The problem I am facing is how to verify the origin of the ajax call.
I realize that this is not easily possible since ajax calls are easy to spoof, but since I have full control of how the view gets rendered (full page source) perhaps there is a way to embed anti-forgery type tokens that could later be verified to a Url Referrer.
Authentication is already handled and I can safely verify the identity of the call, the only problem is verifying which URL (MVC route) the call came from. More specifically, preventing the user from being able to spoof the origin of the ajax call.
I tried creating a custom authorization header and passing it between view render and ajax calls, and that works, but still easy to spoof (since a user could sniff the headers from another part of the site and re-use those). In the end I am not sure how to safely verify that the header has not been spoofed. The only thing that comes to mind is encoding some info about the original context inside the token, and validating it somehow against incoming call context (the one that's passing the token in ajax call).
I see that MVC has AntiForgery token capabilities, but I am not sure if that can solve my problem. If so I'd like to know how it could be used to verify that /api/common/update was called from /home/index vs /user/setup (both of these calls are valid).
Again, i'd like a way to verify which page an ajax call is coming from, and user identity is not the issue.
update
as per #Sarathy recommended I tried implementing anti-forgery token. As far as I can tell this works by adding a hidden field with token on each page, and comparing it to a token set in a cookie. Here is my implementation of custom action filter attribute that does token validation:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var req = filterContext.RequestContext.HttpContext.Request;
var fToken = req.Headers["X-Request-Verification-Token"];
var cookie = req.Cookies[AntiForgeryConfig.CookieName];
var cToken = cookie != null
? cookie.Value
: "null";
log.Info("filter \ntoken:{0} \ncookie:{1}", fToken, cToken);
AntiForgery.Validate(cToken, fToken);
base.OnActionExecuting(filterContext);
}
then my anti forgery additional data provider looks like this:
public class MyAntiForgeryProvider : IAntiForgeryAdditionalDataProvider
{
public string GetAdditionalData(System.Web.HttpContextBase context)
{
var ad = string.Format("{0}-{1}",context.Request.Url, new Random().Next(9999));
log.Info("antiforgery AntiForgeryProvider.GetAdditionalData Request.AdditionalData: {0}", ad);
log.Info("antiforgery AntiForgeryProvider.GetAdditionalData Request.UrlReferrer: {0}", context.Request.UrlReferrer);
return ad;
}
public bool ValidateAdditionalData(System.Web.HttpContextBase context, string additionalData)
{
log.Info("antiforgery AntiForgeryProvider.ValidateAdditionalData Request.Url: {0}", context.Request.Url);
log.Info("antiforgery AntiForgeryProvider.ValidateAdditionalData additionalData: {0}", additionalData);
return true;
}
this works, in that i can see correct pages logged in the provider, and anti forgery breaks w/out the tokens.
however, unless i did something wrong, this seems trivial to spoof. for example
if i go to pageA and copy the token form pageB (just the form token, not even the cookie token), this still succeeds, and in my logs i see pageB while executing ajax method from pageA
confirmed that this is pretty easy to spoof.
I am using csrf to generate ajax tokens like this:
public static string MyForgeryToken(this HtmlHelper htmlHelper)
{
var c = htmlHelper.ViewContext.RequestContext.HttpContext.Request.Cookies[AntiForgeryConfig.CookieName];
string cookieToken, formToken;
AntiForgery.GetTokens(c != null ? c.Value : null, out cookieToken, out formToken);
return formToken;
}
I then pass the form token back with each ajax call and have a custom actionfilterattribute where I read/validate it along with cookie token
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var req = filterContext.RequestContext.HttpContext.Request;
var fToken = req.Headers[GlobalConstants.AntiForgeKey];
var cookie = req.Cookies[AntiForgeryConfig.CookieName];
var cToken = cookie != null
? cookie.Value
: "null";
log.Info("MyAntiForgeryAttribute.OnActionExecuting. \ntoken:{0} \ncookie:{1}", fToken, cToken);
AntiForgery.Validate(cToken, fToken);
this all works (changing anything about the token throws correct exception), then in my IAntiForgeryAdditionalDataProvider I can see what it thinks it's processing.
as soon as i override the csrf token from another view, it thinks it's that view. I don't even have to tamper with the UrlReferrer to break this :/
one way this could work if i could force the cookie to be different on every page load
I am assuming you can use IAntiForgeryAdditionalDataProvider for this.
public class CustomDataProvider : IAntiForgeryAdditionalDataProvider
{
public string GetAdditionalData(HttpContextBase context)
{
// Return the current request url or build a route or create a hash from a set of items from the current context.
return context.Request.Url.ToString();
}
public bool ValidateAdditionalData(HttpContextBase context, string additionalData)
{
// Check whether the allowed list contains additional data or delegate the validation to a separate component.
return false;
}
}
Register the provider in App_Start like below.
AntiForgeryConfig.AdditionalDataProvider = new CustomDataProvider();
https://msdn.microsoft.com/en-us/library/system.web.helpers.iantiforgeryadditionaldataprovider(v=vs.111).aspx
Hope this helps in your scenario.
You mentioned in your question that you're looking for Anti-forgery token capabilities.
Hence, I think what you're asking about is an anti-CSRF solution (CSRF=cross site request forgery).
One way to do this is to render a true random number (a one-time token) into your page, then passing it on each request, which can be done by adding a key/value pair to the request header and then checked at the backend (i.e. inside your controller). This is a challenge-response approach.
As you mentioned, in the server-side code you can use
var fToken = req.Headers["X-Request-Verification-Token"];
to get it from the requesting page.
To pass it along from each client AJAX request of the page, you can use
var tokenValue = '6427083747'; // replace this by rendered random token
$(document).ajaxSend(function (event, jqxhr, settings) {
jqxhr.setRequestHeader('X-Request-Verification-Token', tokenValue);
});
or you can set it for each request by using
var tokenValue = '2347893735'; // replace this by rendered random token
$.ajax({
url: 'foo/bar',
headers: { 'X-Request-Verification-Token': tokenValue }
});
Note that tokenValue needs to contain the random number which was rendered by the web server when the web page was sent to the client.
I would not use cookies for this, because cookies don't protect you against CSRF - you need to ensure that the page, which is requesting is the same as the page which was rendered (and hence created by the web server). A page being on a different tab in the same browser window could use the cookie as well.
Details can be found on the OWASP project page, in the OWASP CSRF prevention cheat sheet.
My quick interim solution was to use custom tokens created on each page load (guid which i keep track of in my token cache), which are passed as headers in all ajax calls. Additionally i create a original url hash and combine it into the custom auth token.
in my ajax methods I then extract the hash and compare it with UrlReferrer hash to ensure that hasn't been tampered with.
since the custom token is always different it's less obvious to guess what's going on as token appears to be different on every page load. however this is not secure because with enough effort the url hash can be uncovered. The exposure is somewhat limited because user identity is not the problem so worst case is a given user would gain write access to another section of the site but only as himself. My site is internal and i am auditing every move so any temper attempts would be caught quickly.
I am using both jQuery and angular so appending tokens with all requests like this:
var __key = '#Html.GetHeaderKey()' //helper method to get key from http header
//jQuery
$.ajaxSetup({
beforeSend: function (xhr, settings) {
xhr.setRequestHeader('X-Nothing-To-See-Here', __key); // totally inconspicuous
})
//angular
app.config(['$httpProvider', function ($httpProvider) {
$httpProvider.defaults.headers.common['X-Nothing-To-See-Here'] = __key;
});
update
the downside of this approach is that custom tokens need to be persisted across a web farm or app restarts. Based on #Sarathy's idea I am trying to side step this by leveraging MVC anti forgery framework. Basically add/remove my "salt" and let the framework manage the actual token validation. That way it's a bit less to manage for me. Will post more details once i verify that this is working.
So this is going to be one of those "you're doing it wrong" answers that I don't like, and so I apologize up front. In any case, from the question and comments, I'm going to propose you approach the problem differently. Instead of thinking about where did the request come from, think about what is the request trying to do. You need to determine if the user can do that.
My guess as to why this is hard in your case is I think you have made your api interface too generic. From your example api "api/common/update" I'm guessing you have a generic update api that can update anything, and you want to protect updating data X from a page that is only supposed to access data Y. If I'm off base there then ignore me. :)
So my answer would be: don't do that. Change your api around so it starts with the data you want to work with: api/dataX api/dataY. Then use user roles to protect those api methods appropriately. Behind the scenes you can still have a common update routine if you like that and it works for you, but keep the api interface more concrete.
If you really don't want to have an api for each table, and if its appropriate for you situation, perhaps you can at least have an api for protected/admin tables and a separate api for the standard tables. A lot of "if"s, but maybe this would work for your situation.
In addition, if your user can update some dataX but not other dataX, then you will have to do some sort of checking against your data, ideally against some root object and whether your user is authorized to see/use that root object.
So to summarize, avoid an overly generic api interface. By being more concrete you can use the existing security tools to help you.
And good luck!
I'm implementing a custom API on top of SBT. I'm doing this so I can manipulate and transform the response from connections before returning it to the user.
The basic auth with username and password is working. But I can't seem to find out how I can add the JSESSIONID header to ClientService (ClientService.Args) on a request before its sent.
Any idea how this can be done ?
Since REST is stateless, in theory you should not be needing JSESSIONID cookie, which is used for preserving state of loggedin user. However if you do wish to add that you should be doing it in below way :
In the specific endpoint class ( like BasicEndpoint.java for Basic Authentication ), look for the initialize method, it would be registering an interceptor, BasicInterceptor in this case.
In process method of BasicInterceptor you can add the cookie like
request.setHeader("Cookie", "JESSION string goes here");
This code intercepts all outgoing requests and would add the cookie header.
Hope this helps.
when you make the request, the JSESSIONID cookie should already be in the response when you do the BASICAUTH.
Also JSESSIONID is not guaranteed to be the unique identifier for a WAS appserver.
Here's the situation...
Site 1) ASP.NET MVC application for customers to login, view and pay bills, etc. This site is using cookieless ASP sessions to allow multiple sessions to run in tabbed browsers.
Site 2) Corporate web site with standard static content. This will have a small form at the top of each page with a username/password form that will post to the MVC application.
When posting to the MVC app, a new session is being generated and the site is returning a 302 redirect with the session ID in the URL (as expected). The controller has two Login methods, one for handling a GET, one for handling a POST. Because of the redirect, it's hitting the GET method and it loses the form values.
By changing the method on the corporate site to do a form GET rather than a POST, the username and password are preserved in the query string after the redirect and I could handle the requests that way, but I would rather do a POST and not pass that data around in the URL.
My gut says that implementing some sort of custom HttpHandler would allow me to do this, but I'm not sure where I'd be able to tie into the session creation. A breakpoint at Session_Start in global.asax shows that the session ID has already been crated and the redirect already happened at that point.
I believe I found where the redirect was happening as well as a possible solution to it. The ISessionIDManager interface has a SaveSessionID method with an "out bool redirected" parameter on it. It appears that when a new session is created, the default session manager rewrites the URL inside this method and does a redirect. I played around with implementing my own ISessionIDManager and I was able to suppress the redirect, but there's no way (that I could tell) of inserting the session ID into the URL for the cookieless session without doing the redirect. Server.Transfer is not allowed at that point in the request lifecycle. I tried that but received an "Error Executing Child Request" message.
Later on, I found this article on Enabling POST in cookieless ASP.NET Applications. Basically by tying in to the Application_EndRequest event, you can detect the redirect, clear the response and fake a form post to the new URL that contains the session ID.
void MvcApplication_EndRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
if (application.Request.Form["CorpLogin"] == "1"
&& application.Response.RedirectLocation != null
&& application.Response.IsRequestBeingRedirected)
{
StringBuilder build = new StringBuilder();
build.Append("<html>\n<body>\n<form name='Redirect' method='post' action='");
build.Append(application.Response.RedirectLocation);
build.Append("' id='Redirect' >");
foreach (string key in application.Request.Form)
{
if (key != "CorpLogin")
build.Append(string.Format("\n<input type='hidden' name='{0}' value = '{1}'>", (string)key, application.Request.Form[(string)key]));
}
build.Append("\n<noscript><h2>Object moved <input type='submit' value='here'></h2></noscript>");
build.Append(#"</form>
<script language='javascript'>
<!--
document.Redirect.submit();
// -->
</script>
");
build.Append("</body></html>");
application.Response.Clear();
application.Response.ClearHeaders();
application.Response.Output.Flush();
application.Response.Write(build.ToString());
}
}
This isn't an ideal solution IMHO but it might be workable enough to meet the requirements.
When the MVC site issues the redirect are you handling that yourself with a controller or is it being routed automatically by your membership provider?
If you can intercept the request before the redirect you could place the username and password in the TempData variable and process it after the redirect.
You're looking for a Server.Transfer equivalent in MVC (see Simon's answer.)
It's like Request.Redirect but simply transfers the .NET processing to another page. It also allows for maintaining the form variables even on POST between transfers (in this case a RouteValueDictionary).
EDIT: (response to your comment #1)
Here is an example of Forms Authentication with MVC. Without knowing the internals of your authorization mechanism, I'm not entirely sure. At any point of the "DoLogin" process, you would be able to do a transfer instead of a redirect.