How do I set up an ADFS integration?
- Page Owner: Not Set
- Last Reviewed: 2021-09-01
A client is using on premises Active Director Federated Services (ADFS). How can I authenticate to it?
Answer
Every setup is going to be slightly different but in general:
- You need the metadata XML at a URL the site can read it. This may mean hosting it yourself, or having it on their server if the web server can access it.
- Install the WsFederation nuget packages.
- Create the startup.cs file:
// If you're logging in exclusively with WsFederation, set it to default.
app.SetDefaultSignInAsAuthenticationType(WsFederationAuthenticationDefaults.AuthenticationType);
// You MUST also include cookie auth. Set the type to WsFederationAuthenticationDefaults.AuthenticationType
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType
});
// Configuration the federation integration
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions()
{
// URL to Metadata XML. This path must be readable by the server.
// Nothing in the XML is sensitive info, so host it elsewhere if necessary.
// You can even self-host it.
MetadataAddress = $"https://localhost/federation/metadata.xml",
// This must match exactly what is configured for WT Realm on the server.
Wtrealm = "https://www.example.com/",
Notifications = new WsFederationAuthenticationNotifications()
{
RedirectToIdentityProvider = (ctx) =>
{
// If the site is behind a load balancer, you may need to force HTTPS.
var forceHttps = ConfigurationManager.AppSettings["ida:forcehttps"];
var currentUrl = ctx.Request.Uri;
// You need to set the Reply URL based on what domain the user is on. This is to support multi-tenant
// sites.
if (forceHttps != null && bool.TryParse(forceHttps, out bool shouldForceHttps) && shouldForceHttps)
{
ctx.ProtocolMessage.Wreply = new UriBuilder(
"https",
currentUrl.Host,
443).ToString();
}
else
{
ctx.ProtocolMessage.Wreply = new UriBuilder(
currentUrl.Scheme,
currentUrl.Host,
currentUrl.Port).ToString();
}
//To avoid a redirect loop to the federation server send 403 when user is authenticated but does not have access
if (ctx.OwinContext.Response.StatusCode == 401 && ctx.OwinContext.Authentication.User.Identity.IsAuthenticated)
{
ctx.OwinContext.Response.StatusCode = 403;
ctx.HandleResponse();
}
if (ctx.OwinContext.Response.StatusCode == 401 && IsXhrRequest(ctx.OwinContext.Request))
{
ctx.HandleResponse();
}
return Task.FromResult(0);
},
SecurityTokenValidated = (ctx) =>
{
//Ignore scheme/host name in redirect Uri to make sure a redirect to HTTPS does not redirect back to HTTP
var redirectUri = new Uri(ctx.AuthenticationTicket.Properties.RedirectUri, UriKind.RelativeOrAbsolute);
if (redirectUri.IsAbsoluteUri)
{
ctx.AuthenticationTicket.Properties.RedirectUri = redirectUri.PathAndQuery;
}
//Sync user and the roles to EPiServer in the background
ServiceLocator.Current.GetInstance<ISynchronizingUserService>().SynchronizeAsync(ctx.AuthenticationTicket.Identity);
return Task.FromResult(0);
}
}
});
#if DEBUG
// Can be helpful for local debugging
Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
#endif
app.UseStageMarker(PipelineStage.Authenticate);
//Remap logout to a federated logout
app.Map(LogoutUrl, map =>
{
map.Run(ctx =>
{
ctx.Authentication.SignOut();
ctx.Response.Redirect("/");
return Task.FromResult(0);
});
});
// Tell antiforgery to use the name claim. If you don't use the name claim
// any forms that have anti-XSS tokens may fail.
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.Name;
Other caveats:
- Make sure that any domains that use this auth are configured in the Relying Parties on the ADFS server.
- If you see a 401 unauthenticated response when you first deploy this update, you may need to set
debug="true"in the web.config, save, then set it back todebug="false". This resets some cached files in ASP.NET and allows OWIN startup to work. This seems to happen when you first add OWIN to a project.
See the Fidelity project for a live example.