Unable to complete Azure ACS Authentication in MVC Applications - asp.net-mvc-3

I have an MVC 3 Application that I am trying to integrate with Azure-hosted ACS Identity Providers. I have been following the Tutorials but they do not appear to be working for me when using ASP.NET MVC.
Essentially, when I hit the View which I've flagged with [Authorize] the user is redirected to the Azure-hosted Login page with the list of Identity Providers. I choose a provider (in this case Live) and log in. At this point this all works as I expect. After I successfully authenticate it appears (visually) that I'm not redirected back to my application, instead I'm returned to the Identity Providers page. When watching this in Fiddler, it appears it actually returns but then starts the cycle all over again (HTTP Status Code 302).
Can someone explain what may be causing this?
Within the Azure portal, I have the following Urls configured for my relying party application
Realm: http: //localhost:7777/
Return Url: http: //localhost:7777/ (I also tried http: //localhost:7777/Home/About)
In all other cases I have the default settings
The urls match what is in the Web.config (including the trailing slash)
There is only one controller with the following:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[Authorize]
public ActionResult About()
{
Response.Write("Claims Received from ACS:");
ClaimsIdentity ci = Thread.CurrentPrincipal.Identity as ClaimsIdentity; foreach (Claim c in ci.Claims)
{
Response.Write("Type: " + c.ClaimType + "- Value: " + c.Value + "");
}
return View();
}
}
Note: This is a brand new project created to work through this integration. All the packages and related SDKs are all up-to-date.

I would like to know whether you mean is when you log in and return to your web application, it thinks you’re not logged in, and redirects you to the identity provider’s sign in page again.
Please check if you’ve configured the authorization logic correctly. For example, if you use role based authorization, it is needed to configure ACS to create a rule that returns a role. You can also use custom authorization instead of the Authorization attribute. In your custom authorization code, you can check if the required claims are present. The claims can be role or anything else (user name, age, etc.). Custom authorization is usually more agile than the Authorization attribute.
Best Regards,
Ming Xu.

Please make sure you haven’t modified the sample code. Since it is an official ACS SDK sample, a lot of people have tried it and it will work.
Also in your original post, you mentioned you’ve configured ASP.NET authorization:
<authorization>
<deny users="?" />
</authorization>
Please remove this (as indicated in the document), if you don’t want to use ASP.NET authorization(you want to use WIF).
Best Regards,
Ming Xu.

Related

MVC Prompt Query Parameter

I want to override Azure AD SSO login for a MVC web application. I don't want to log other applications out in the process, but require login for security purposes. I am using OAuth2.0 with OIDC via Owin for authentication.
I am trying to use the prompt=login query parameter, which should theoretically do the trick. I found a Github reference to this being recently made available in Core but cannot trace how to do it in MVC5.2
Is it possible to do it in the Application Builder? I tried adding .WithExtraQueryParameters ("prompt=login") to the ConfidentialClientApplicationBuilder when getting the access code. No luck.
Is there another workaround if the code doesn't come out-of-the-box?
EDIT: PublicClientApplication allows .WithPrompt(Prompt.Login) while ConfidentialClientApplication does not (also does not allow AcquireTokenInteractive) This is a web app so it needs the confidential builder. Tested using the Public builder and it logs in successfully. But I get an ActiveX instantiation error 'not in a single-threaded apartment'(?) Strange, unless that is how the token is being delivered perhaps. I also tested by changing to multitenant in Azure and by toggling Public client on and off.
Any ideas?
You could use ForceLogin to add paramter to ConfidentialClientApplicationBuilder.
ForceLogin: enables the application developer to have the user prompted for credentials by the service even if this would not be needed. This can be useful if Acquiring a token fails, to let the user re-sign-in. This is done by sending prompt=login to the identity provider. Again, we've seen it used in some security focused applications where the organization governance demands that the user re-logs-in each time they access specific parts of an application.
So, use the code as below:
result = await app.AcquireTokenInteractive(scopes)
.WithPrompt(Prompt.ForceLogin)
.ExecuteAsync();
The Modifier .WithExtraQueryParameters will not help you. You need to use .WithPrompt instead.Please refer article.
Example:
await PublicClientApplication
.AcquireTokenInteractive(scopes, null)
.WithAccount(CurrentUser)
.WithPrompt(Prompt.ForceLogin) // use login for your case
.ExecuteAsync();
I eventually resolved this as follows:
Under Notifications in the ConfigureAuth(IAppBuilder app) add a reference to a new task:
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
Then add the task:
private Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification
<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
// Forces the user to login.
if (notification.ProtocolMessage.Prompt == null)
{
notification.ProtocolMessage.Prompt = "login";
}
return Task.FromResult(0);
}
Hope that helps the next person with this issue.

IdentityServer (OpenId Connect) authentication on asp.net webform

i'm trying to use IdentityServer3 to authenticate users on an asp.net webform application with owin pipeline (no mvc)
All the examples suggest to configure the application like a mvc application, but in this way the application doesn't perform a redirection to the IdentityServer Login page when i try to access to a protected resource of the webform application
this is my client (webform) configuration
[Startup.cs]
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationMode = AuthenticationMode.Active,
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie
//LoginPath = new PathString ("/Account/Login") //<--enabling this path property redirect me to a local login page but not to the external IdentityServer login page
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost:44300/identity", //<<--url of the identityServer
ClientId = "webform",
ClientSecret = "ciccio",
Scope = "openid profile roles",
RedirectUri = "https://localhost:44302/", //<-- url of the client (to come back ofter the login)
ResponseType = "id_token",
SignInAsAuthenticationType = "Cookies"
});
i'm sure i forgot something
It looks, you missed app.UseStageMarker(PipelineStage.Authenticate); at the bottom of your Startup.cs.
The reason is that (according to the documentation):
Owin middleware components (OMCs) run at the latest stage, which by default is PreHandlerExecute. The stage markers are used to make them to run earlier.
The complete sample including cookie mapping and support for logout is in IdSrv repo
posting this just in case someone else's still looking a solution for the same problem like we did recently, finding this question without an answer
as workaround i tried these solution suggested in this article but they DON'T WORK
Login page on different domain
both solutions in the example page force a "brutal" redirection to the IdentityServer, but doing so, the IdentityServer doesn't show you the login page because the performed request is not in the correct form
the "signin=xxxxxx" parameter attached to the querystring, needed to legitimate the login request is not present.
i've tried to use an mvc client as well, in this case every requests made to a protected resource (with Authorize attribute) is redirected to the IdentityServer in the correct way (the url where the user should be redirected to log-in page is in this format https://localhost:44300/identity/login?signin=4f7ee6677aec2d2aca6ebc40e4d13720) with the "signin" parameter attached to the querystring
this behaviour doesn't happen in a webform application (at least with the same configuration used in a mvc application)
I'm sure that something is missing, something like a "httpModule" that intercepts the "http 401 not authorized response" and composes a correctr url to redirect towards the login page of the IdentityServer
forgive me for my english

ASP.net Web API RESTful web service + Basic authentication

I'm implementing a RESTful web service using ASP.Net Web Api. I have concluded to use Basic authentication + SSL to do the authentication part. What is the best/correct way to implement that?
My first attempt was to do it manually, parsing the Authorization header, decoding and verifying the user against my database. It works, but I wonder if I am missing something.
I've seen some solutions using user roles and principals. While I'm not sure what these actually do, I'm almost sure I will not be needing these, since in my database I define my own users and their roles.
Also what I haven't yet completely understand, is if the consumers of the service must sent the credentials with each request or they are somehow cached. Should my service do something in order for this to happen, or it's completely up to the consumer to handle this?
And a last question about clients making requests with javascript. Would there be any "cross domain request" problems if they try to use the service?
Jamie Kurtze provides a good explanation of using Basic Authentication here ASP.NET Web API REST Security Basics
From my understanding, if you want your requests to be stateless then each request will require the Authentication field to be set
Jamie Kurtze wraps the necessary code in a class derived from DelegateHandler, while Rick Strahl checks if the call is valid using a Filter. You can read more at his blog post on this topic at A WebAPI Basic Authentication Authorization Filter
Use basic authentication for the initial (sign in) request by adding a [BasicHttpAuthorize] attribute to the appropriate controllers/methods. Specify the Users and Roles with the attribute if desired. Define BasicHttpAuthorizeAttribute as a specialized AuthorizeAttribute like this:
public class BasicHttpAuthorizeAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
if (Thread.CurrentPrincipal.Identity.Name.Length == 0) { // If an identity has not already been established by other means:
AuthenticationHeaderValue auth = actionContext.Request.Headers.Authorization;
if (string.Compare(auth.Scheme, "Basic", StringComparison.OrdinalIgnoreCase) == 0) {
string credentials = UTF8Encoding.UTF8.GetString(Convert.FromBase64String(auth.Parameter));
int separatorIndex = credentials.IndexOf(':');
if (separatorIndex >= 0) {
string userName = credentials.Substring(0, separatorIndex);
string password = credentials.Substring(separatorIndex + 1);
if (Membership.ValidateUser(userName, password))
Thread.CurrentPrincipal = actionContext.ControllerContext.RequestContext.Principal = new GenericPrincipal(new GenericIdentity(userName, "Basic"), System.Web.Security.Roles.Provider.GetRolesForUser(userName));
}
}
}
return base.IsAuthorized(actionContext);
}
}
Have the initial response include an API key for the user. Use the API key for subsequent calls. That way, the client's authentication remains valid even if the user changes username or password. However, when changing password, give the user an option to "disconnect clients", which you implement by deleting the API key on the server.
Have a look here for a good basic authentication implementation
http://leastprivilege.com/2013/04/22/web-api-security-basic-authentication-with-thinktecture-identitymodel-authenticationhandler/
there is more to read about it at:
https://github.com/thinktecture/Thinktecture.IdentityModel.45/wiki

How to fix browser back button issue after SAML SSO from Shibboleth

Have a SAML2 Service Provider web application build on .NET MVC (3). We are using ComponentSpace's SAML2 library to do the authentication, not the Shibboleth SP. Here is the relevant controller logic:
public class SignOnController : Controller
{
// constructor-inject _services
[HttpPost]
public ActionResult SendAuthnRequest(string userName, string returnUrl)
{
// this users ComponentSpace internally to push user to IdP
_services.SamlServiceProvider.SendAuthnRequest(args);
return new EmptyResult();
}
[HttpPost]
public ActionResult ReceiveAuthnResponse()
{
var samlResponse = _services.SamleServiceProvider
.ReceiveSamlResponse(args);
// ...
return Redirect(samlResponse.RelayResourceUrl ?? defaultUrl);
}
}
Ultimately the user lands on either the relay url or the default login page. However when they click a link, then click the back button, the browser goes back to an error page on the Shibboleth IdP server. After that, the browser forward and back buttons ultimately become useless.
Am I doing something wrong in either of the above methods? Should I be returning a View() instead of an EmptyResult() when sending? Is there some way to reset the browser history to prevent repeated postbacks back to the IdP? Is this something that I can configure in ComponentSpace's implementation?
So I understand that you hit back button during authentication in IdP i WBSSO SAML2 profile? I don't know components which you enumerated but in Shibboleth case it always ends with error page. I may be wrong but if you consider the nature of SP->IdP communication I believe there is no other option, unless you reimplement something.
The SAML protocols messages used in SAML SSO as sent via the browser. Clicking the browser forward and backward navigation buttons can be problematic when using SAML SSO, as you have seen.
There is no simple solution to this problem. You could look at incorporating Javascript to disable the navigation buttons but that most likely will cause other issues.
I think the best and simplest solution is to display a generic error message if this occurs. This is something you need to do in your application.
Our MvcExampleIdentityProvider and MvcExampleServiceProvider applications that we ship don't have issues in most cases. They use the SAML high-level API as opposed to the SAML low-level API used in the question but this shouldn't make a difference. In our examples we return an EmptyResult as well.
We faced the same problem and worked around it with JavaScript history API:
https://www.thecssninja.com/javascript/stealing-history-api
It's not nice though since essentially the browsing history is manipulated. But it works efficiently and prevents the error page from showing up. Back/forward buttons inside your web app continue to work correctly but there's no way for the user to get back to any previous page which is not your web app. That was a compromise we could live with.
We use this script. It checks if the main url is called from an SSO redirect. If so, it skips the SSO page when the user clicks the back button. If not, the script does nothing because the back button is working as expected.
(function(window, location) {
var previous = document.referrer.toLowerCase();
var issso = previous.indexOf('account/sso');
if(issso >= 0){
history.replaceState(null, document.title, location.pathname+"#!/skipsso");
history.pushState(null, document.title, location.pathname);
window.addEventListener("popstate", function() {
if(location.hash === "#!/skipsso") {
setTimeout(function(){
history.go(-2);
},0);
}
}, false);
}
}(window, location));

ADFS v2.0 Error : MSIS7042: The same client browser session has made '6' requests in the last '1' seconds

Folks,
I've got an ASP.NET MVC application that I am attempting to secure using the Release Candidate version of ADFS v2.0 (Geneva). I have configured the application as a relying party trust, and I've used Fedutil.exe to modify the application's Web.config so that it has the information about the Geneva server and uses the Geneva server as its claims source.
However, when I try and hit the MVC app, it redirects to Geneva, which then (after warning me about self-signed certs) re-directs me to the MVC app again. After accepting both self-signed cert warnings, the two servers play ping-pong with each other in an infinite redirect loop until finally Geneva spews the following message:
The same client browser session has made '6' requests in the last '1' seconds. There could be a possible bad configuration. Contact your administrator for details.
There are no errors in the event logs on the MVC side or on Geneva except for an event containing the above message. If someone could give me some information on how to try and debug, diagnose, and hopefully fix this problem, I would be eternally grateful.
Again, the Geneva box is the ADFS v2.0 Release Candidate and the ASP.NET MVC site was built using the latest (late '09) version of the Windows Identity Foundation SDK with Web.config modified using FedUtil.exe from the WIF SDK.
So you will all get a kick out of this... I tried this same application from Firefox and ... IT WORKS. I get prompted for my domain credentials, the ADFS v2 server re-directs me ONCE and then I end up on the home page of my application, complete with my account name and personalized greeting.
So now the real issue is this: Why the hell is IE8 getting caught in an infinite redirect loop and Firefox ISN'T ??
After even further testing, I've been able to get this scenario working, out of the box, without modification of any of the default pipeline stuff from ADFS v2 (RC) or from WIF (RTW) on BOTH Safari AND Firefox. IE8 is the ONLY browser to exhibit any problem dealing with this authentication scenario. I've tried everything, including installing and trusting the self-signed certs, adding the sites to my local intranet zone and dropping security to low and even setting first AND third party cookies to always allow.
I had the same issue with ADFS 1.0
And to resolve it, I made sure that the URL had a trailing forward slash "/" that would always work in FireFox as well as IE
eg : https://somedomain.com/Application_2/
Turns out that the host name of the relying party had an underscore in it (khoffman_2). Apparently, the underscore is an illegal DNS character and ONLY IE will reject the information with the underscore in it.
I renamed my machine from khoffman_2 to khoffman2 and the ADFS v2/MVC relying party combination works flawlessly on Firefox, Safari, AND IE.
While this isn't your problem, we have had identical problems to what you described. Our solution was to:
Enabled Basic Authentication in IIS (this solved nothing but was required for the next 2 steps)
Disable Windows Authentication in IIS (this solved the problem for some IE browsers but not all)
Disable Anonymous Access in IIS (this solved the problem for the rest of the IE browsers)
Jaxidian's answer is close.
In my case I only had to:
Windows Authentication -> Disabled
Anonymous Auth -> Enabled
ASP.NET Impersonation -> Disabled
Forms Auth -> Disabled
Windows Auth -> Disabled
This loop can occur when a user is not authorized to access a page.
We had a custom authorization attribute on our MVC controller that checks to see if the user was in a role based on the claims provided if the setting for UseADFS was true in the config files. I thought this setting was set to true and was confounded that I kept getting the adfs loop when accessing the page because I was in the groups that were authorized to access the page.
The key to troubleshooting was to make a web page that displayed my adfs claims without necessarily requiring authentication.
#if (User.Identity.IsAuthenticated)
{
<div>UserName: #User.Identity.Name;</div>
var claimsIdentity = User.Identity as System.Security.Claims.ClaimsIdentity;
<table>
#foreach (var claim in claimsIdentity.Claims)
{
<tr><td>#claim.Type</td><td>#claim.Value</td></tr>
}
</table>
}
I noticed that I was getting logged into ADFS, and my claims were getting set, so ADFS was working. The actual issue was my config file had UserADFS="true" instead of UseADFS="true" which basically caused my custom authorization code to return false on authorization. Therefore, the page kept forwarding me back to adfs to authenticate again.
Anyways, if a user does not have the correct claims to access the page, then this adfs login loop can occur, as well.
Also, if you wrote a custom authorize attribute be sure to check out the following link which describes how to prevent the loop.
Redirect loop with .Net MVC Authorize attribute with ADFS Claims
Custom HandleUnauthorizedRequest handler code for AuthorizeAttribute from that link:
protected override void HandleUnauthorizedRequest System.Web.Mvc.AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
//One Strategy:
//filterContext.Result = new System.Web.Mvc.HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
//Another Strategy:
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(
new
{
controller = "u",
action = "LoginStatus",
errorMessage = "Error occurred during authorization or you do not have sufficient priviliges to view this page."
})
);
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}

Resources