Closed Tratcher closed 6 years ago
Wonderful....! Yay...!
Hi, I've tried to use WsFederation with ADFS server 2016. Here my configuration:
services.AddAuthentication() .AddWsFederation(options => { options.Wtrealm = "https://localhost:44312"; options.MetadataAddress = "https://adfsserver2016/FederationMetadata/2007-06/FederationMetadata.xml"
I get this exception.
Exception.txt
This is my metadata file FederationMetadata.txt
Anyone can help me? Thanks
I've got the same issue like @gtresoldi with an ADFS on Windows Server 2012 R2
Hi, I've tried to use WsFederation with ADFS server 2016. Here my configuration:
services.AddAuthentication() .AddWsFederation(options => { options.Wtrealm = "https://localhost:44312"; options.MetadataAddress = "https://adfsserver2016/FederationMetadata/2007-06/FederationMetadata.xml" I get this exception. Exception.txt
This is my metadata file FederationMetadata.txt
Anyone can help me? Thanks
Should we file a seperate issue for this problem or is there any other solution?
Thanks & best regards, Christoph
@gtresoldi @Compufreak345 I've filed https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/786 for your parser issue.
@Tratcher FYI. same parser issue as above.
Great news :)
@Tratcher Looks like I am getting the same parser issue too.
Any timeline for wsignoutcleanup1.0 support?
@Zoxive this one? https://github.com/aspnet/Security/issues/1425. We're considering it but there's no specific timeline. The current focus is porting over existing functionality from Katana.
@Tratcher Kinda, when our single signon provider wants to sign out all the sub applications each application gets a SignoutCleanup action
For now im doing something like this inside the WsFederationHandler HandleRemoteAuthenticateAsync
// Handle SignoutCleanup
if (Request.Query.TryGetValue("wa", out var wa))
{
if (wa == WsFederationConstants.WsFederationActions.SignOutCleanup)
{
await _authenticationService.SignOutAsync(Request.HttpContext, Options.SignInScheme, null);
return HandleRequestResult.Handle();
}
}
You could do something similar with middleware etc, but we already have a custom WsFederation fork to support dotnetcore. (soon as all features are met we can remove our custom proj)
@Zoxive how is that different from #1425? That sounds like what we'd implement. OIDC has something similar.
Is there any guidance on how we could share FedAuth cookies between existing ASP.NET full framework applications and ASP.NET Core apps? As far as I can see this just enables an ASP.NET Core application to authenticate against a WSFed STS. We are looking for a way to re-use existing FedAuth cookies so that we can move part of our applications to ASP.NET Core without having to rewrite existing code.
@jmezach It depends on what components your ASP.NET apps were using. If it was the Microsoft.Owin WsFederation provider then there is a way to share cookies. https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/compatibility/cookie-sharing
I get this error when I start the application. it is not able to complete the configuration from Federation Metadata. if I enter the metadata url on the browser I am able to see the xml, but only issue I see is that there is a certificate error, which I override on the browser. would that be an issue causing this?
my config:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
})
.AddWsFederation(options =>
{
options.Wtrealm = @"http://localhost:3230/";// Configuration["wsfed:realm"];
options.MetadataAddress = @"https://DevIDPServer/sts/Metadata.xml";// Configuration["wsfed:metadata"];
})
.AddCookie();
}
app.UseAuthentication();
----------------------Errors-----------------------------
System.InvalidOperationException: IDX10803: Unable to obtain configuration from: 'https://DevIDPServer/sts/Metadata.xml'. ---> Microsoft.IdentityModel.Xml.XmlReadException: IDX13004: Security token type role descriptor is expected.
at Microsoft.IdentityModel.Protocols.WsFederation.WsFederationMetadataSerializer.ReadEntityDescriptor(XmlReader reader)
at Microsoft.IdentityModel.Protocols.WsFederation.WsFederationMetadataSerializer.ReadMetadata(XmlReader reader)
at Microsoft.IdentityModel.Protocols.WsFederation.WsFederationConfigurationRetriever.<GetAsync>d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.<GetConfigurationAsync>d__24.MoveNext()
--- End of inner exception stack trace ---
at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.<GetConfigurationAsync>d__24.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Authentication.WsFederation.WsFederationHandler.<HandleChallengeAsync>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.<ChallengeAsync>d__53.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Authentication.AuthenticationService.<ChallengeAsync>d__11.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.ChallengeResult.<ExecuteResultAsync>d__14.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeResultAsync>d__19.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeFilterPipelineAsync>d__17.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeAsync>d__15.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Builder.RouterMiddleware.<Invoke>d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.<Invoke>d__7.MoveNext()
@arc2018 yes https cert errors are expected to fail the metadata download. However, that's a strange inner exception. I've ask @brentschmaltz about it on your other thread: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/804
To ignore cert errors you need to hook into the backchannel like this:
options.BackchannelHttpHandler = new HttpClientHandler() { ServerCertificateCustomValidationCallback = (_, __, ___, ___) => true };
@Tratcher thank you for your response. I added the BackchannelHttpHandler, but that did not make any difference, I get the same error. this is the new code: `public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddAuthentication(sharedOptions => { sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme; }) .AddWsFederation(options => { options.Wtrealm = @"http://localhost:3230/";// Configuration["wsfed:realm"]; options.MetadataAddress = @"https://DevIDPServer/sts/Metadata.xml";// Configuration["wsfed:metadata"]; options.BackchannelHttpHandler = new HttpClientHandler() { ServerCertificateCustomValidationCallback = (req, cert, er, t) => true }; }) .AddCookie();
}
` I created a test full framework 4.5 asp.net web app pointing to the same IDP and it works without any issue on the same machine. here is my web config on the 4.5 application. We are using SecureAuth as our identity provider.
`
</system.identityModel>
Please reopen you AzureAd bug then. They can investigate the exception.
@Tratcher thank you. I reopened it.
I'm getting a similar issue as @arc2018. Is it possible to bypass the metadata XML file retrieval and configure this thing manually?
Yes you can set the options.Configuration property, but building it by hand is non trivial. If you manage please share a sample.
@Tratcher @lzandman Not to bad for us. This is what we use.
// Created our own custom WsFederationSettings dto
public class WsFederationSettings
{
public string Issuer { get; set; }
public string TokenEndpoint { get; set; }
public IEnumerable<string> SigningKeys { get; set; }
public string Realm { get; set; }
}
// Load from section "WsFederation" in our appsettings.json file
var settings = Configuration.GetSection("WsFederation").Get<WsFederationSettings>();
var config = new WsFederationConfiguration
{
Issuer = settings.Issuer,
TokenEndpoint = settings.TokenEndpoint,
};
foreach (var signKey in settings.SigningKeys)
{
var cert = new X509Certificate2(Convert.FromBase64String(signKey));
var key = new X509SecurityKey(cert);
config.SigningKeys.Add(key);
}
o.Configuration = config;
o.Wtrealm = settings.Realm;
o.TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = settings.Realm
};
///rest of your options
Thanks @Tratcher @Zoxive. Will try!
Using a nightly build I got things partially working. After succesful authentication in ADFS the browser is redirected to ".../signin-wsfed". This returns a 302 response, where I see the ".AspNetCore.Correlation.WsFederation..." cookie is being removed and a new cookie ".AspNetCore.Cookies" is set. Then the browser is redirected to the initial controller endpoint (a web API with Authorize attribute set) that started the authentication process. However, my code is never called. Instead another authentication process is triggered, sending the browser back to ADFS. The repeats itself six times, ending with a "MSIS7042: The same client browser session has made '6' requests in the last '0' seconds." error.
So it appears the request to my Web API endpoint isn't seen as a valid authenticated request. The only cookie it receives is the ".AspNetCore.Cookies" cookie. Is that OK? Does that cookie contain all authentication/session data required?
@lzandman Unless the WsFed authentication handler does something completely different (which I don’t expect it to do), the authentication stack works as follows in ASP.NET Core:
/signin-wsfed
So the WsFed authentication handler only runs for the authentication process, but the identity is persisted using the cookie authentication handler. So what you are seeing is correct: There will be only the cookie from the cookie authentication handler.
If you end up with an authentication loop, then something should be off with your configuration. Maybe your authorization policy is set improperly. Could you show debug logs from the application starting at the time where you hit your secured endpoint?
@poke I hadn't set an explicit policy. I just wanted the caller of my controller to be authenticated. So I used the Authorize attribute without specifying a policy.
I then tried to implement authorization based on the beginning of this article (the RequireAuthenticatedUser policy). That that also doesn't seem to work. Still the same loop.
The debug output doesn't seem to include anything helpful. Is there a way to get more debug info on the authentication/authorization process?
@poke Fixed it! Turns out the problem was that ordering seems to matter in the Configure() method in Startup.cs. My code originally was like this:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
loggerFactory.AddDebug();
app.UseMvc();
app.UseAuthentication();
}
But the UseMvc() call needs to be at the end (or at least below the UseAuthentication() call.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
loggerFactory.AddDebug();
app.UseAuthentication();
app.UseMvc();
}
Now it seems everything is working fine!
@lzandman Yes, the UseAuthentication
should be very early in your Configure
to make sure that the authentication middleware runs very early. Having it after MVC would mean that it will never run for MVC routes (since the MVC middleware terminates the request), so the user will never be authenticated there. Glad you could resolve this :)
Got another question. In our regular .Net web apps that use WS-Federation we specify a certificate thumbprint. The certificate itself is not installed in the store on the machine. The metadata file or the manual method of configuring this new WS-Federation package (as presented by @Zoxive code sample above) both require the actual X509Certficate2 data to be present. Am I missing something here? Is it possible to configure this using only the certificate thumbprint?
I did find some code to obtain a certificate from the Store by its thumbprint, but since I do not have the certificate installed locally, I cannot use that method.
@lzandman can you share me the nightly build download link?
@arc2018 I got it from this website. You can also add their NuGet feed to Visual Studio's Package Manager (be sure to check preview builds):
https://www.myget.org/F/azureadwebstacknightly/api/v3/index.json
@lzandman you need the certificate. The thumbprint would need additional information pointing to a cert store.
Another question: In our regular Asp.Net MVC apps we've implemented custom sliding session logic that works by hooking into the FederatedAuthentication.SessionAuthenticationModule.SessionSecurityTokenReceived event. Based on some logic either nothing happens, a new session security token is created or the user gets signed out. This SessionSecurityTokenReceived event is triggered for every request.
I'm trying to do something similar in our AspnetCore app using this experimental ws-fed package. I've hooked into the WSFederationOptions Events (MessageReceived, SecurityTokenReceived, SecurityTokenValidated, etc). But these events don't get called for every request. They are only called when an ADFS roundtrip was made (at start and after token lifetime has passed; i.e. when "/signin-wsfed" is called.)
Does anybody know where i should hook into for our custom sliding session logic?
@lzandman As per my explanation above, the WSFed authorization handler won’t run if the cookie authentication scheme is able to reconstruct the claims identity from the stored cookies. So hooking into WSFed here will not work since it simply won’t run at all. You would have to hook into the cookie authentication scheme here.
I’m not sure if there’s a good way to hook into it though, I haven’t tried looking for it yet. I personally use a custom authorization handler to implement a custom session that exists parallel to the actual authentication process. But hooking into the cookie authentication scheme might be a pretty good way to do it, if it allows doing so.
Update: using this Microsoft document I found out the OnValidatePrincipal event seems like the proper location to put our custom stuff. However, when our custom logic decides a new cookie should be created, I call the HttpContext.SignInAsync() extension method. This results in the following exception:
InvalidOperationException: No IAuthenticationSignInHandler is configured to handle sign in for the scheme: WsFederation
Anyone have any idea what I'm missing?
@lzandman You're calling SignIn with the wrong scheme, you should be using the cookie auth scheme.
@Tratcher Yes, that was the problem. Originally I had put that code inside a WsFederation event handler. There I figured I had to use the WsFederation scheme. Now that I'm working from a cookie auth event handler, I had to use that scheme. Thanks :-)
So, it seems I've got everything working. Except for signing out. I use the following code:
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
The browser is redirected to the ADFS server, to the following URL:
https://myadfsserver.com/adfs/ls/?wtrealm=https%3A%2F%2Flocalhost%3A44304%2F&wa=wsignin1.0&wreply=https%3A%2F%2Flocalhost%3A44304%2Fsignin-wsfed&wctx=...
Then it's sent to the "/signin-wsfed" url and it's authenticated again. Then it is redirected to the Web API endpoint and allowed access. Any ideas? I had expected it to get redirected to the ADFS using a wsignout action.
SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
only removes the local cookie. If you also want a remote sign-out then you should add a second call to sign out for the WsFederation scheme.
@Tratcher That sounds plausible. But do you have any sample code? I tried this:
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
await context.HttpContext.SignOutAsync(WsFederationDefaults.AuthenticationScheme);
But that didn't do anything...
You're right, that's beyond the capabilities of that event. The problem is that the request continues to execute normally (e.g. it proceeds to process /foo/bar). To cause a remote signout you'd have to call signout like above And terminate the request. For that level of control you're going to need to move the logic to your own middleware.
@Tratcher I assume the short-circuiting behavior for authorization doesn’t work there because we only have access to the HTTP context instead of the authorization filter context? A real shame that we cannot terminate the request early there then. I have something very similar implemented with custom MVC filters but moving it into authentication events would have made a lot of sense (to decouple it from MVC in my case).
It's a control flow problem, the event does not have a flag that lets you terminate the request.
@Tratcher So when writing custom middleware, how would I get access to certain values that are available inside authentication events? E.g. I need the values of context.Properties.IssuedUtc and context.Properties.ExpiresUtc.
HttpContext.AuthenticateAsync()
returns a result object with both user and properties.
@Tratcher what happens when you call HttpContext.AuthenticateAsync() inside MiddleWare. What does the calls graph look like?
@brentschmaltz HttpContext.AuthenticateAsync()
gets the AuthenticationService
from DI and calls AuthenticationService.AuthenticateAsync()
. That then gets the handler for the default authentication scheme and calls AuthenticateAsync
on that handler.
This is basically the same thing that happens when the MVC AuthorizeFilter
runs.
And btw. calling AuthenticateAsync
early in a middleware will not make subsequent calls do the same thing again. The result will be cached internally, so all that happens with calling it in custom middleware is that it runs a bit earlier.
@Tratcher Thanks! I've got it working now. The only issue I'm seeing now is that there's a wsignoutcleanup1.0 request on /signin-wsfed that returns a 500 status. I think @Zoxive messages above imply that this functionality hasn't yet been implemented, right?
@Zoxive You mention that you've implemented custom wsignoutcleanup1.0 handing inside WsFederationHandler HandleRemoteAuthenticateAsync. How did you do that? Is that an event I can hook into?
Another potential issue: I've placed custom cookie authentication events inside a custom class that inherits from CookieAuthenticationEvents and specify that class using the EventsType property of CookieAuthenticationOptions as specified in this Microsoft document.
I tried to apply the same pattern to the WsFederationEvents class, but noticed it doesn't contain any virtual members that I can override in my own custom class. It only inherits one overrideable virtual member, TicketReceived, from its base class RemoteAuthenticationEvents. So it seems this pattern isn't applicable and the WsFederationOptions.EventsType property mechanism cannot be used. Is this a bug?
WsFederation preview support is now available for ASP.NET Core 2.0.0. The Microsoft.AspNetCore.Authentication.WsFederation 2.0.0-preview1 package is available at https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.WsFederation/. This is a standalone preview that targets netstandard2.0 and should work with existing ASP.NET Core 2.0.0 applications (.NET Core 2.0 or .NET 4.6.1). A non-preview ASP.NET Core 2.0.0 compatible package will follow once we’ve addressed your feedback.
The code is available at https://github.com/aspnet/security/tree/rel/2.0.0-ws-preview1 and issues can be filed at https://github.com/aspnet/security/issues. Please give us a 👍 from the reactions menu on this post if you have successfully used this component and are ready for the final release.
This component is a port from Microsoft.Owin.Security.WsFederation and uses many of the same mechanics. It has also been updated to integrate with ASP.NET Core 2.0’s authentication model. See the samples below.
Aside from updating the usage pattern to match ASP.NET Core, there are also some functional changes to be aware of. A. This component no longer checks every form post request for sign-in messages by default. Sign-in callbacks are restricted to the "/signin-wsfed" path by default. The CallbackPath can be changed to the application root “/” used by some auth providers if you also enable SkipUnrecognizedRequests to allow sharing that request path with other components. B. This component no longer allows unsolicited logins by default. That WsFederation protocol feature is susceptible to XSRF attacks. See the AllowUnsolicitedLogins option to opt into that feature if your application requires it.
Samples:
For applications only using WsFederation (similar to using OpenIdConnect):
For applications using WsFederation with Identity: