authentication.net-coreadfsws-federation

Authorize attribute using WsFederation never executes annotated controller action


I am using WsFederation middleware to access an ADFS server for authentication. ADFS is given a specific endpoint to call back at the end of the conversation between the middleware and ADFS. If I don't provide an actual endpoint in my code (some action that responds to the route = callback endpoint), I get a 404. If I do implement an action at that endpoint, I get nothing useful (e.g. 'User' not set) and - whatever my action does at the end with respect to a response goes straight back to the user's browser. At no point was the action I decorated with [Authorize] executed.


From startup:

   public void ConfigureServices(IServiceCollection services)
    {         
        services.AddControllersWithViews();

        // set up ADFS authentication
        services.AddAuthentication(sharedOptions => {
            sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
        }).AddWsFederation(options =>
        {
            options.MetadataAddress = "<adfs-server>/FederationMetadata/2007-06/FederationMetadata.xml";
            options.Wtrealm = "<my-apps-server>/authviaadfs/auth-callback";
        }).AddCookie("Cookies", o => { });
        
        // set up custom authorization
        services.AddAuthorization(options => { });
        services.AddControllersWithViews();
        services.AddRazorPages();
    }
  
   public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            //app.UseHsts();
        }
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthentication(); 
        app.UseAuthorization(); 

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

From MyController:

[Authorize]
public IActionResult MyProtectedPage()
{
    ... code that never, ever, executes when decorated with [Authorize]
}

[Route("/authviaadfs/auth-callback")]
public IActionResult AuthCallback()
{
    ... code that executes after I log in via ADFS 
    ... response that returns to the original caller of "MyProtectedPage"
}

Can anyone tell me what I'm doing wrong? I've followed the recipe from half-dozen different Googled websites that say "this is how you authenticate to ADFS" (all slightly different, but the gist is the same, including setting only the options for 'MetadataAddress' and 'WtRealm').


Solution

  • Okay, I figured it out - another example of "Googled examples that are useless in the real world" and "bad/missing documentation of how to do something".

    There is another "option" to set in addition to "MetadataAddress" and "Wtrealm" if you want to use an endpoint other than "/" - an option called "CallbackPath". This tells the middleware what route in your code to "swallow and process"; if you don't set it (as I didn't originally, following the recipes) then your middleware doesn't know which request to intercept. So as far as the sample code I provided in the question, after setting options.MetadataAddress and options.Wtrealm you would set the following:


    options.CallbackPath = "/authviaadfs/auth-callback";
    

    That tells the middleware to, inside the request pipeline, to intercept any calls to "/authviaadfs/auth-callback" and use the request and headers to finish the authentication and then, effectively, give control to your protected controller action. The middleware creates a correlation cookie that it sends along with the first redirect to your ADFS server, then uses that cookie in the "/authviaadfs/auth-callback" to match the return call from ADFS to the proper request context that was being authenticated.