IDX21323 OpenIdConnectProtocolValidationContext.Nonce was null, OpenIdConnectProtocolValidatedIdToken.Payload.Nonce was not null - c#

I'm attempting to authenticate for Azure AD and Graph for an Intranet (Based off Orchard CMS), this functions as expected on my local machine, however, when accessing what will be the production site (already set up with ssl on our internal dns), I get the above error at times, it's relatively inconsistent, others in my department while accessing usually get this error.
My Authentication Controller is as follows:
public void LogOn()
{
if (!Request.IsAuthenticated)
{
// Signal OWIN to send an authorization request to Azure.
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
public void LogOff()
{
if (Request.IsAuthenticated)
{
ClaimsPrincipal _currentUser = (System.Web.HttpContext.Current.User as ClaimsPrincipal);
// Get the user's token cache and clear it.
string userObjectId = _currentUser.Claims.First(x => x.Type.Equals(ClaimTypes.NameIdentifier)).Value;
SessionTokenCache tokenCache = new SessionTokenCache(userObjectId, HttpContext);
HttpContext.GetOwinContext().Authentication.SignOut(OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
}
SDKHelper.SignOutClient();
HttpContext.GetOwinContext().Authentication.SignOut(
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
}
My openid options are configured as follows:
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
var openIdOptions = new OpenIdConnectAuthenticationOptions
{
ClientId = Settings.ClientId,
Authority = "https://login.microsoftonline.com/common/v2.0",
PostLogoutRedirectUri = Settings.LogoutRedirectUri,
RedirectUri = Settings.LogoutRedirectUri,
Scope = "openid email profile offline_access " + Settings.Scopes,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async (context) =>
{
var claim = ClaimsPrincipal.Current;
var code = context.Code;
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new SessionTokenCache(signedInUserID,
context.OwinContext.Environment["System.Web.HttpContextBase"] as HttpContextBase).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(
Settings.ClientId,
Settings.LogoutRedirectUri,
new ClientCredential(Settings.AppKey),
userTokenCache,
null);
AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, Settings.SplitScopes.ToArray());
},
AuthenticationFailed = (context) =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
}
};
var cookieOptions = new CookieAuthenticationOptions();
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(cookieOptions);
app.UseOpenIdConnectAuthentication(openIdOptions);
The url for redirection is kept consistent both at apps.dev.microsoft.com and in our localized web config.

In my case, this was a very weird problem because it didn't happen in for everyone, only few clients and devs have this problem.
If you are having this problem in chrome only (or a browser that have the same engine) you could try setting this flag on chrome to disabled.
What happens here is that chrome have this different security rule that " If a cookie without SameSite restrictions is set without the Secure attribute, it will be rejected". So you can disable this rule and it will work.
OR, you can set the Secure attribute too, but I don't know how to do that ;(

How to solve IDX21323
The problem is solved with this lines of codes, the reason of the error was that ASP.NET don't has the sessión info created yet. The function "authFailed.OwinContext.Authentication.Challenge()" fill the header with the info that needs for the authentication.
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions()
{
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthenticationFailed = AuthenticationFailedNotification<OpenIdConnect.OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> authFailed =>
{
if (authFailed.Exception.Message.Contains("IDX21323"))
{
authFailed.HandleResponse();
authFailed.OwinContext.Authentication.Challenge();
}
await Task.FromResult(true);
}
}
});

Check the URL mentioned in the AD App Registrations --> Settings --> Reply URL's. if for example that url is https://localhost:44348/
Go to MVC Project --> Properties (Right Click and Properties) --> Web Section --> Start URL and Project URL should also be https://localhost:44348/
This has resolved the issue for me. other option is to dynamically set the Redirect URL after AD authentication in Startup.Auth

See System.Web response cookie integration issues by Chris Ross (AKA Tratcher on github). The OWIN cookie manager and the original cookie management built into ASP.NET Framework can clash in an unhelpful way, and there is no universal solution to this. However, in setting up OIDC authentication I found this suggested work-around from that link worked for me:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
// ...
CookieManager = new SystemWebCookieManager()
});
And:
OpenIdConnectAuthenticationOptions.CookieManager = new SystemWebCookieManager();
This causes OWIN to use the ASP.NET Framework cookie jar/store and avoid the clash. I imagine there will be side effects to his, so tread carefully! Read the link for a full explanation.

With it being inconsistent, it makes me believe the error you are seeing is caused by what people call "Katana bug #197".
Luckily, there is a workaround with a nuget package called Kentor.OwinCookieSaver.
After installing the nuget package add app.UseKentorOwinCookieSaver(); before app.UseCookieAuthentication(cookieOptions);.
For more info, checkout the Kentor.OwinCookieSaver repo on GitHub.

Also check this link:
https://learn.microsoft.com/en-us/aspnet/samesite/owin-samesite
For me it didn't work at first but the solution was to use https. I used Visual Studio IIS Express which hosts a website default using http. In test it worked because of https.

I've got the same error in production environment while locally it worked for all development team. I've tried Kentor.OwinCookieSaver solution suggested by Michael Flanagan but it did not help. After digging a little bit I discovered that authentication itself completed successfully and OwinContext contains user identity and claims, but AuthenticationFailed event handler is raised with IDX21323 exception. So I decided to use the following workaround - I updated AuthenticationFailed event handler:
// skip IDX21323 exception
if (context.Exception.Message.Contains("IDX21323"))
{
context.SkipToNextMiddleware();
} else {
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
}
return Task.FromResult(0);
This way system will not throw IDX21323 exception but continues auth process and allows users to login and use the system.
I know this not a solution, but at least users can now login until I find a better way to solve this issue.

My start and project URL's were different than the Redirect URI in Azure. I made all these match and no longer get IDX2132 error.

I needed to switch my local project to use HTTPS. I changed IISExpress to https:// and port to :443__ in the project settings.

Azure problems?
Make sure you are using the right domain name. I was debugging some firewall problems on Azure and I was using my-subdomain.azurewebsites.net to see if the site itself was up, and once it was, I forgot to change the domain name back to my-subdomain.mydomainname.org.

In my case, we were standing up new non-prod environments with very similar configs but one environment was throwing this error. It turned out that it was an issue with IIS configuration. After installing the HTTP Redirection role on the server and restarting, everything worked fine.
This can be configured by
Opening up the Server Manager
Click "Add roles and features" below "Configure this local server"
Continue through setup pages to get to server roles. "Before You Begin" -> "Installation Type" -> "Server Selection" -> "Server Roles"
Under Roles expand "Web Server (IIS)" -> "Web Server" -> "Common HTTP Features"
Check "HTTP Redirection"
Complete installation and restart server.
This is probably a niche answer as I'm guessing it's related to using URL rewrite/redirect rules specified in a web.config, but it took me forever to track down the issue. Hope it helps someone!

Related

Authentication against local AD in the Angular application

I've been developing an Angular app with .NET Core backend (services). The task is to enable an integrated authentication, i.e. make it work with the local user seamlessly, so I login to my (connected to a local AD) machine once and the web application lets me in without the necessity to login a second time. We've been working with Identity Server 4 and intended to implement this scenario using it.
There is a little documentation on the official website concerning the Windows Authentication (e.g. against Active directory): http://docs.identityserver.io/en/latest/topics/windows.html but it doesn't explain much. As per my info, to make this scenario work the browser utilizes either Kerberos or NTLM. Neither of them is mentioned in the IS4 docs. I'm lacking the understanding of how the local credentials are getting picked up and how IS4 'knows' the user belongs to AD? How I can make sure only the users from a specific domain have access to my app?
I found some working stuff here https://github.com/damienbod/AspNetCoreWindowsAuth but questions remain the same. Even though I was able to get to the app with my local account I don't understand the flow.
I expect the user utilizing the app in the local network to log-in to the app without entering the login/password (once he's already logged in to the Windows). Is this something achievable?
Identity Server is intended to serve as an Identity Provider, if you need to talk with your AD you should see the Federation Gateway architecture they propose using the IAuthenticationSchemeProvider. Where Identity Server acts as an endpoint and talks with your AD.
This is the link:
http://docs.identityserver.io/en/latest/topics/federation_gateway.html
You have the control to programmatically reach your AD and pass the correct credentials to get the authentication. That step should be done in your Identity Server. After you get authenticated you should get redirected to your application again.
About your last question, the answer is yes, if you have your website hosted on an intranet and you have the access to your AD, you don't need to capture your credentials as user input, you can programmatically reach the AD as I said.
Bellow is the code I use to connect with my active directory
On the ExternalController class, you get when you use IdentityServer, you have this:(I don't remember at the top of my head how much I changed from the original code, but you should get the idea)
/// <summary>
/// initiate roundtrip to external authentication provider
/// </summary>
[HttpGet]
public async Task<IActionResult> Challenge(string provider, string returnUrl)
{
if (string.IsNullOrEmpty(returnUrl)) returnUrl = "~/";
// validate returnUrl - either it is a valid OIDC URL or back to a local page
if (Url.IsLocalUrl(returnUrl) == false && _interaction.IsValidReturnUrl(returnUrl) == false)
{
// user might have clicked on a malicious link - should be logged
throw new Exception("invalid return URL");
}
if (AccountOptions.WindowsAuthenticationSchemeName == provider)
{
// windows authentication needs special handling
return await ProcessWindowsLoginAsync(returnUrl);
}
else
{
// start challenge and roundtrip the return URL and scheme
var props = new AuthenticationProperties
{
RedirectUri = Url.Action(nameof(Callback)),
Items =
{
{ "returnUrl", returnUrl },
{ "scheme", provider },
}
};
return Challenge(props, provider);
}
}
private async Task<IActionResult> ProcessWindowsLoginAsync(string returnUrl)
{
// see if windows auth has already been requested and succeeded
var result = await HttpContext.AuthenticateAsync(AccountOptions.WindowsAuthenticationSchemeName);
if (result?.Principal is WindowsPrincipal wp)
{
// we will issue the external cookie and then redirect the
// user back to the external callback, in essence, testing windows
// auth the same as any other external authentication mechanism
var props = new AuthenticationProperties()
{
RedirectUri = Url.Action("Callback"),
Items =
{
{ "returnUrl", returnUrl },
{ "scheme", AccountOptions.WindowsAuthenticationSchemeName },
}
};
var id = new ClaimsIdentity(AccountOptions.WindowsAuthenticationSchemeName);
id.AddClaim(new Claim(JwtClaimTypes.Subject, wp.Identity.Name));
id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name));
// add the groups as claims -- be careful if the number of groups is too large
if (AccountOptions.IncludeWindowsGroups)
{
var wi = wp.Identity as WindowsIdentity;
var groups = wi.Groups.Translate(typeof(NTAccount));
var roles = groups.Select(x => new Claim(JwtClaimTypes.Role, x.Value));
id.AddClaims(roles);
}
await HttpContext.SignInAsync(
IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme,
new ClaimsPrincipal(id),
props);
return Redirect(props.RedirectUri);
}
else
{
// trigger windows auth
// since windows auth don't support the redirect uri,
// this URL is re-triggered when we call challenge
return Challenge(AccountOptions.WindowsAuthenticationSchemeName);
}
}
If you want to use Azure AD, I would recommend you to read this article:
https://damienbod.com/2019/05/17/updating-microsoft-account-logins-in-asp-net-core-with-openid-connect-and-azure-active-directory/
Not sure if it's what you want, but I would use the Active Directory Federation Services to configure an OAuth2 endpoint and obtain the user token in the .Net Core Web App.
Isn't NTLM authentication support limited on non Microsoft browsers?
OAuth2 have the advantage of using only standard technologies.
One way to do it is to have 2 instances of the app deployed.
The first one is configured to use Windows Authentication and the other one uses IS4.
ex:
yoursite.internal.com
yoursite.com
Your local DNS should redirect traffic internally from yoursite.com to yoursite.internal.com
yoursite.internal.com will be the one configured to use AD authentication. You should have a flag in your appsettings.json to indicate if this instance is a AD auth or IS4 auth.
The downside of this solution is that you have to deploy 2 instances

How can I set a domain hint for a multi-tenant Azure AD request?

When using Azure AD for authentication using OpenID Connect, it is possible to include a domain_hint parameter in the authentication request to redirect the user to their branded Azure login page (similar to using whr - see the difference between http://login.microsoftonline.com/ and http://login.microsoftonline.com?whr=microsoft.com, for example).
If providing services for a single tenant, it is easy enough to set this up using something like:
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
RedirectToIdentityProvider = (context) =>
{
context.ProtocolMessage.DomainHint = "mydomain.com";
return Task.FromResult(0);
},
}
})
However, I am working on a multi-tenant application which asks for the user's email, and then depending on configuration settings, will either allow the user to log on using a local password, or will redirect them to Azure for authentication. This is effected by a call to:
context.HttpContext.GetOwinContext().Authentication.Challenge(properties, "AzureAd");
Since I do not know the DomainHint at app startup (and it will change depending on the user's organisation), is there a way to pass the DomainHint parameter as part of the Challenge method's properties or similar?
Soon after I asked the question I started down a similar route to the answer posted by #vibronet, and for those who come after the solution is posted here.
Part of the properties object (of type AuthenticationProperties) in the call to Challenge() above is a Dictionary<string, string> object, called Dictionary (as luck would have it). Before the Challenge() call, the domain hint was set on this:
properties.Dictionary["DomainHint"] = domainHint;
As #vibronet suggested, it is then possible to access the pipeline through changes to the app.UserOpenIdConnectAuthentication() call in Startup. This call now looks like:
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
...
Notifications = new OpenIdConnectAuthenticationNotifications
{
...
RedirectToIdentityProvider = notification =>
{
var dict = notification.OwinContext.Authentication.AuthenticationResponseChallenge.Properties.Dictionary;
if (dict.ContainsKey("DomainHint"))
{
notification.ProtocolMessage.DomainHint = dict["DomainHint"];
}
return Task.FromResult(0);
}
}
}
);
Hope this helps.
Sure! Please see http://www.cloudidentity.com/blog/2017/03/20/tweak-sign-in-messages-with-the-asp-net-owin-middleware/ for a way of passing context from your app to the sign in message building pipeline. You can easily adapt the example (which signals a binary consent option) to specifying domain hint, login hint or any combination you need.

Too many OpenID.nonce cookies cause "Bad Request"

I have already gone through links here, here and here which are related to issue I am having.
I have Silverlight application using IdentiServer3 for authentication and I started having this issue just now when I implemented log out functionality. Note that the issue has nothing to do with Silverlight because login and logout functionality is actually implemented on the server side which is a classic ASP.Net Web form. (.NET 4.5.1)
The application never had logout functionality, so user just used to close the browser so we never encountered this issue before. We have now logout.aspx page and Silverlight application have link to this page.
Logout.aspx page
public partial class Logout : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (Request.IsAuthenticated)
{
Session.Clear();
Request.GetOwinContext().Authentication.SignOut();
}
Response.Redirect("/");
}
}
Default.aspx page. This is starting page
public partial class Default : Page
{
protected void Page_Load(object sender, EventArgs e)
{
// Send an OpenID Connect sign-in request.
if (!System.Web.HttpContext.Current.Request.IsAuthenticated)
{
HttpContext.Current.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
}
OWIN startup class where OpenID connection is configured
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
LoginPath = new Microsoft.Owin.PathString("/Default.aspx")
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = ConfigurationManager.AppSettings["Authority"],
Scope = "openid profile",
ClientId = ConfigurationManager.AppSettings["ClientId"],
RedirectUri = ConfigurationManager.AppSettings["RedirectUri"],
ResponseType = "id_token",
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = (context) =>
{
var id = context.AuthenticationTicket.Identity;
// create new identity
var newIdentity = new ClaimsIdentity(id.AuthenticationType);
// we want to keep username and subjectid
var sub = id.FindFirst(ClaimTypes.NameIdentifier);
var username = id.FindFirst("preferred_username");
newIdentity.AddClaim(username);
newIdentity.AddClaim(sub);
// keep the id_token for logout
newIdentity.AddClaim(new Claim("id_token", context.ProtocolMessage.IdToken));
context.AuthenticationTicket = new AuthenticationTicket(
newIdentity,
context.AuthenticationTicket.Properties);
return Task.FromResult(0);
},
RedirectToIdentityProvider = (context) =>
{
if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = context.OwinContext.Authentication.User.FindFirst("id_token").Value;
context.ProtocolMessage.IdTokenHint = idTokenHint;
}
return Task.FromResult(0);
},
}
Steps to reproduce the issue:
I type web site URL which redirects me to identityserver3 login
page.
I enter credentials and hit login.
After successful login I
get redirected to the web site and There I click log out.
I get logged
out successfully. Fiddler shows the following calls
https://idsvr.mydomain.com/identity/connect/endsession?id_token_hint=XXXXXXXXXXXXXX
https://idsvr.mydomain.com/identity/logout?id=616dd9a4e4c6a55b0bb27faceb4df8dd
https://idsvr.mydomain.com/identity/connect/endsessioncallback?sid=xxxxxx
I land up on https://idsvr.mydomain.com/identity/logout?id=xxxxxxx page as expected.
Now I close the browser ( this step is important)
Now type web site URL again which redirects me to identity server login page. ( like Step 1)
I enter credentials and hit login.
After successful login, IdentityServer makes POST to the web site and web site creates AuthenticationTicket. However, here HttpContext.Current.Request.IsAuthenticated is false so Default.aspx page redirects to IdentityServer. I am already logged in so Indetityserver redirects to client web site and loops continue.
Fiddler shows several round trips from identityServer to web site’s default.aspx page. Each roundtrip keeps adding OpenIdConnect.nonce.OpenIdConnect cookie and ultimately i get bad request error because of max request size.
So as suggested in above links I downgraded Microsoft.Owin.Security.OpenIdConnect to 3.0.0 in Client Application.
However, I still get stuck in continuous loop. The only difference is now it does not add new OpenIdConnect.nonce.OpenIdConnect cookie for each round trip. Fiddler shows only one cookie for each round trip. However HttpContext.Current.Request.IsAuthenticated is still false. So I get stuck in continuous loop.
I had a similar issue with my asp.net mvc application . After some Research i found that there is a bug in Microsoft's Owin implementation for System.Web. The one that is being used when running Owin applications on IIS. Which is what probably 99% of us do, if we're using the new Owin-based authentication handling with ASP.NET MVC5.
The bug makes cookies set by Owin mysteriously disappear on some occasions.
This middleware is a fix for that bug. Simple add it before any cookie handling middleware and it will preserve the authentication cookies.
app.UseKentorOwinCookieSaver();
Here is the link in detail
https://github.com/KentorIT/owin-cookie-saver
What solved the problem for me was using AdamDotNet's Custom OpenIdConnectAuthenticationHandler to delete old nonce cookies.
https://stackoverflow.com/a/51671887/4058784

How to add multiple endpoints to adfs

I have a lot of web applications on the same web server (II7):
let's say mydomain/app1, mydomain/app2, ... and so on.
I'm trying to add an ADFS authentication through OWIN.
Here's what I've done:
[assembly: OwinStartup(typeof(MyNamespace.Startup))]
namespace MyNamespace
{
public class Startup
{
private static string realm = ConfigurationManager.AppSettings["ida:Wtrealm"];
private static string adfsMetadata = ConfigurationManager.AppSettings["ida:ADFSMetadata"];
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
app.Use((context, next) =>
{
SignIn(context);
return next.Invoke();
});
app.UseStageMarker(PipelineStage.Authenticate);
}
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
Wtrealm = realm,
MetadataAddress = adfsMetadata
});
}
public void SignIn(IOwinContext context)
{
if (context.Authentication.User == null)
{
context.Authentication.Challenge(
WsFederationAuthenticationDefaults.AuthenticationType);
}
}
}
}
When a user access mydomain/app1, I want him to be authenticated through ADFS and then redirected to mydomain/app1. And same thing for a user accessing mydomain/app2.
But I wish to add only one relying party trust in ADFS (because there's a lot of applications and all are using same claim rules).
I've tried different configurations, but I can't do what I want:
if the RP endpoint is mydomain/app1/, authentication is ok but all requests (even from mydomain/app2 are redirected to app1), obviously
if the RP endpoint is only mydomain/, I get a 405.0 http error - Method Not Allowed after redirection (I take care of the trailing slash).
For information, I saw this question on stackoverflow:
URL redirection from ADFS server
But it doesn't really answer my problem because I don't understand sentence "(...) WIF will process the response at URL_1, and then take care of redirecting the user to URL_2" in Andrew Lavers's comment.
How can I add multiple endpoints to one RP trust ?
Or how can I redirect users to the original URL ? (considering all applications are on the same domain).
Thanks in advance for any help.
You should be able to set the wreply parameter based on the application that triggers the authentication flow. Something like this:
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
Wtrealm = realm,
MetadataAddress = adfsMetadata,
Notifications = new WsFederationAuthenticationNotifications
{
RedirectToIdentityProvider = context =>
{
context.ProtocolMessage.Wreply = <construct reply URL from context.Request>;
return Task.FromResult(0);
}
}
});
The issue here is that even by doing this the ADFS server does not need to comply with the given Wreply parameter. By default behaviour ADFS always re-directs to the Wtrealm after successful login.
In our case we wanted to authenticate via ADFS with 2 test servers, 1 production server and enable the login also for developers (localhost). Because of the re-direction issue each of the servers need their own Relying party trust .
The ideal solution here would be that RP trust is created separately for each server running the application and also for https://localhost:44300 (Visual Studio default SSL port) so that developers can also authenticate. For allowing https://localhost:44300 there is probably some security conserns to the preferred option would be to set up development ADFS for example on Azure VM.

C# Owin login results in identity=null on production system

I've got an asp.net MVC 5 web project which is running fine on my development system. But for some reason, the login using Microsoft Owin with Facebook stops working as soon as I deploy the solution on my production system.
The callback always retrieves ....error=access_denied as parameter and I tracked it back to the fact that owin returns null for my identity. Any clue whats going on here?
UPDATE
I implemented log4net in my Owin code and was able to dive deeper:
Response status code does not indicate success: 400 (Bad Request).
Stack trace:
at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
at Microsoft.Owin.Security.Facebook.FacebookAuthenticationHandler<AuthenticateCoreAsync>d__0.MoveNext()
Please not that I have already modified the facebook app to match the production urls, responses etc.
private void ConfigureAuth(IAppBuilder app)
{
var cookieOptions = new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
};
app.UseCookieAuthentication(cookieOptions);
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); // Passive
app.SetDefaultSignInAsAuthenticationType(cookieOptions.AuthenticationType);
app.UseFacebookAuthentication(new FacebookAuthenticationOptions
{
AppSecret = ConfigurationManager.AppSettings["FacebookAppSecret"],
AppId = ConfigurationManager.AppSettings["FacebookAppId"],
Provider = new FacebookAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new Claim(
IdentityUtility.ExtendedClaimTypes.IdentityProvider,
"Facebook"));
return Task.FromResult(0);
},
OnReturnEndpoint = (context) =>
{
if(context.Identity == null)
throw new Exception(context.Response.StatusCode.ToString());
return Task.FromResult(0);
}
}
});
}
Regards,
Martin
Maybe you already solved it! Anyway... I'll post this answer just in case that any other lost soul (?) comes here looking for some other alternatives or workarounds.
I was fighting with a similar issue for a couple of days! In order to save time I would recommend you to try the following first:
Read this github thread about a workaround that OWIN needs when you are working with web cookies manager (Correlation cookie, external cookie and the SystemWebCookieManager):
https://github.com/aspnet/AspNetKatana/issues/331
Also... (long version) when you're working behind a load balancer or some proxy that makes a reverse with the traffic your server could be doing an overwrite over the requests schemes and finally calling facebook endpoints (/oauth) using http protocol (That's why it works fine on localhost or any development environment, because it runs without a LB at front). Since Facebook requires https in order to exchange tokens and complete the sing in process you will receive an ?error=access_denied response (it's so frustrating +_+).
(short version):
You can assert that some request (before the reverse) was using https and overwrite the context.request attribute to restore it. And you can do it using a middleware (OWIN) to check the 'x-forwarded-proto' request header from the original request.
Just apply the OWIN middleware before declaring external cookies as default sign-in authentication type (SetDefaultSignInAsAuthenticationType). The order is important here because the middleware works as a pipeline! (more info here -> https://learn.microsoft.com/en-us/aspnet/aspnet/overview/owin-and-katana/owin-middleware-in-the-iis-integrated-pipeline)
Example code snippet:
// This is the middleware fixing the request scheme
app.Use((context, next) => {
if (context.Request.Headers["x-forwarded-proto"] == "https")
{
context.Request.Scheme = "https";
}
return next();
});
// The following lines are related with the step 1 issue!
app.SetDefaultSignInAsAuthenticationType(DefaultAuthenticationTypes.ExternalCookie);
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = DefaultAuthenticationTypes.ExternalCookie,
AuthenticationMode = AuthenticationMode.Passive,
CookieName = CookiePrefix + DefaultAuthenticationTypes.ExternalCookie,
ExpireTimeSpan = TimeSpan.FromMinutes(5),
CookieManager = new SystemWebCookieManager()
});
...
More info about x-forwarded-proto header here -> https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto . The key point here is:
The X-Forwarded-Proto (XFP) header is a de-facto standard header for
identifying the protocol (HTTP or HTTPS) that a client used to connect
to your proxy or load balancer.

Categories