aspnet / Security

[Archived] Middleware for security and authorization of web apps. Project moved to https://github.com/aspnet/AspNetCore
Apache License 2.0
1.27k stars 600 forks source link

Ws-Federation remote signout not working ASP.NET Core 2.1 #1882

Closed GoFightNguyen closed 5 years ago

GoFightNguyen commented 5 years ago

Context

ASP.NET Core 2.1

The app is configured to use cookie authentication through WS-Federation. It is remote authentication. In the Startup.ConfigureServices I have this configuration

services
.AddAuthentication(o =>
{
    o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    o.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    o.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
})
.AddWsFederation(o =>
{
    o.Wtrealm = "urn:sp.ProspectPortal";
    o.MetadataAddress = "https://localhost:44311/nsp_IdP_metadata_wsfed_prospect.xml";
    o.Events.OnRedirectToIdentityProvider = ctx =>
    {
        ctx.ProtocolMessage.Wct = DateTimeOffset.UtcNow.ToString();
        return Task.CompletedTask;
    };
})
.AddCookie();

The default page a user is routed to is the Index Razor Page. This Razor Page contains an OnPostAsync method, which is invoked by a simple form containing a button for logout.

public async Task OnPostAsync()
{
    await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    await HttpContext.SignOutAsync(WsFederationDefaults.AuthenticationScheme);           
}

Problem

Signout is not working. Through debugging, I verified the OnPostAsync method is executing. I see no http traffic to the remote authenticator for logout.

Expectation

I expected at least one of the HttpSignOutAsync method invocations to invoke the remote logout.

Tratcher commented 5 years ago

if you put a breakpoint in OnRedirectToIdentityProvider, does it get invoked when you call SignOutAsync? https://github.com/aspnet/Security/blob/4dd9c6deefba64aa7721430e7d1dc72d6e002416/src/Microsoft.AspNetCore.Authentication.WsFederation/WsFederationHandler.cs#L364-L373

Is OnPostAsync overwriting the response with some kind of default 200 response?

GoFightNguyen commented 5 years ago

OnRedirectToIdentityProvider is being invoked.

Using a Fiddler trace, I do not see the OnPostAsync overwriting the response with a 200 response.

Tratcher commented 5 years ago

Can you share the fiddler trace?

GoFightNguyen commented 5 years ago

My employer isn't ok with me sharing the fiddler trace.

When I invoke the OnPostAsync method, there are no HTTP requests going to the remote authenticator despite OnRedirectToIdentityProvider being invoked.

At the end of the OnPostAsync method, I added a call to the OnGet method. The response from the post request is now a 302 redirect to the remote Authenticator for signout. Any ideas why this triggers the call?

Tratcher commented 5 years ago

Can you at least share the response headers as seen by the client? Is it a 200 response with a Location header set?

GoFightNguyen commented 5 years ago

The response was a 500. I discovered it was an issue with my Razor Page. Here is the cshtml

<div>
    <h2>Claims</h2>
    <table class="table-condensed table-bordered">
        <thead>
            <tr>
                <th>@Html.DisplayNameFor(model => model.Claims[0].Type)</th>
                <th>@Html.DisplayNameFor(model => model.Claims[0].Value)</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var item in Model.Claims)
            {
                <tr>
                    <td>@Html.DisplayFor(modelItem => item.Type)</td>
                    <td>@Html.DisplayFor(modelItem => item.Value)</td>
                </tr>
            }
        </tbody>
    </table>
</div>

The problem was the OnPostAsync method was not initializing the PageModel.Claims property...it was only being set on the OnGet method. Thus, the response would create a 500 response because Model.Claims was null.

After resolving this issue, I see the 302 redirect to the remote authenticator for signout.

Thanks