How to add multiple endpoints to adfs - c#

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.

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

IDX21323 OpenIdConnectProtocolValidationContext.Nonce was null, OpenIdConnectProtocolValidatedIdToken.Payload.Nonce was not null

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!

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.

How to make ASP.NET create authenticated session with Owin OpenId Connect library?

After searching through lots of examples online I'm struggling with what seems to be a fairly simple requirement.
I'm trying to extend an existing ASP.NET Application that uses Form Authentication today so that it can use OpenID Connect for authentication as well as some role information coming from the Identity Provider. In particular I'm integrating with an existing hosted Identity Provider that I do not have control over.
I'm using ASP.NET MVC with the Owin components for OpenIdConnect. Namely,
Microsoft.Owin.Security
Microsoft.Owin.Security.Cookies
Microsoft.Owin.Security.OpenIdConnect
I am successfully able to:
In a web browser -- navigate to a controller method that is secured with the [Authorize] attribute
The Owin components properly redirect me to the Identity Provider where I can authenticate and then and I'm redirected back to my app (NOTE: my Identity Provider requires that a redirect_uri be passed in, so I'm currently setting that as part of the OpenIdConnectAuthenticationOptions startup configuration.)
When the redirect back to my app happens, I'm able to see the access_token and the id_token as part of the query string. Additionally, I've been able to use the access_token to call into the user info endpoint and properly derive information about the user using that token.
So far so good! HOWEVER.
What I'm failing to grasp and what most Owin examples I've seen don't seem to explain: what, if any, extra configuration is required to get ASP.NET to actually create an authenticated session in my application based on the redirect from the Identity Provider back to my application.
The general feeling I get from the documentation is that I should NOT have to do extra configuration within the Owin libraries -- that once I've configured the system to use cookie authentication and the OpenId Connect libraries -- that it should just work. However, this doesn't seem to be as easy as it looks. I'm guessing I'm missing something.
Some specific considerations/observations:
Many examples I've found don't require the RedirectUri to be set in the OpenIdConnectAuthenticationOptions, but my Identity Provider requires this parameter to be set each time.
Very few examples that I've found explain whether the controller method that fires as a result of the RedirectUri being hit should be secured with [Authorize] or left anonymous. In my testing, if I mark it as [Authorize] I get into an infinite redirect loop. If I leave it anonymous, I'm able to see the tokens in the request info but the ASP.NET Session is never created. For example, Request.IsAuthenticated is always false.
As a test I've set breakpoints inside several of the OpenIdConnectAuthenticationNotifications() events and currently I'm only seeing my code break into the RedirectToIdentityProvider event, and NONE of the others seem to hit -- which leads me to believe I'm not configuring this right.
Per suggestions I've found, I've set the authentication node this way in the web.config, but it doesn't seem to make a difference if I exclude this node.
<system.web>
<authentication mode="None" />
</system.web>
To summarize:
Do I need to specifically write code to handle the returning redirect from the Identity Provider to manually set up the ASP.NET Session (cookie etc.) for the given user? and
If so, should this code go in the controller method that is called as a result of RedirectUri being hit, or should the code go into one of the "Notifications" events available within OpenIdConnectAuthenticationNotifications()?
Lastly, if I'm NOT supposed to be setting up the Authenticated session manually after redirect from the Identity Provider (if it's supposed to work automatically), any suggestions for common mistakes on this configuration?
For completeness:
My Owin pipeline Startup Configuration method:
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
//no problems on these as far as I can tell
ClientId = "client_id_string",
ClientSecret = "client_secret_string",
Authority = "url_to_identity_provider",
Scope = "email name etc",
//I'm properly redirected to this URL but not sure
//if I should need to create the session manually
RedirectUri = "http://mymachine/mymvcapp/authorize",
//this causes the redirection to come with the access_token,
//which is valid
ResponseType = "token",
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications()
{
RedirectToIdentityProvider = (context) =>
{
//I'm able to break into this method
return Task.FromResult(0);
},
MessageReceived = (context) =>
{
//doesn't seem to run this line
return Task.FromResult(0);
},
SecurityTokenReceived = (context) =>
{
//doesn't seem to run this line
return Task.FromResult(0);
},
SecurityTokenValidated = (context) =>
{
//doesn't seem to run this line
return Task.FromResult(0);
},
AuthorizationCodeReceived = (context) =>
{
//doesn't seem to run this line
return Task.FromResult(0);
},
AuthenticationFailed = (context) =>
{
//doesn't seem to run this line
return Task.FromResult(0);
},
},
});
}
My secured method that properly initiates the login flow:
[Authorize]
public class HomeController : Controller
{
//I'm sent to the login flow the first time this is hit
public ActionResult Index()
{
return View();
}
}
My method at the RedirectUri that does get called but does indicate that the ASP.NET authenticated session was created:
public class AuthorizeController : Controller
{
// [Authorize] -- currently this Authorize attribute is turned off
//so the method is anonymous.
//If I turn that back on, I get infininte redirect loops to
//the Identity Provider
public ActionResult Index()
{
//the incoming request to this controller method from the
//identity provider DOES include valid access_token and id_token
//(which can be used against the user info endpoint) but does not
//create a valid ASP.NET session for my web app
//Request.IsAuthenticated is always false
//should there be a manual creation of the ASP.NET
//session/cookie information in this controller method?
//note: to me it would make most sense if this attribute was not
//anonymous since it's unlikely that the Request would ever appear
//as IsAuthenticated == true, but if you read the entire question
//it will be clear why I'm trying this method with anonymous access
return View();
}
}
As you found out, you can't put an [Authorize] attribute on the method the external server uses to notify you the user was authorized - the session isn't authorized yet, you're just being notified that it should be.
Fortunately, creating that session is not difficult:
How can I manually create a authentication cookie instead of the default method?
(I'm pretty sure you have to do this yourself with the basic Microsoft Owin stuff - and you always can do it yourself if you want.)

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