Sustainsys / Saml2

Saml2 Authentication services for ASP.NET
Other
952 stars 600 forks source link

AuthenticateAsync Not Implemented #1248

Closed TheArchitectIO closed 2 years ago

TheArchitectIO commented 3 years ago

Attempting to Use AspNetCore2 plugin with cookies.

I do not need the overhead of Identity and that is why its not in use.

I use the [Authorize] Decorator on my Controller action and that is all.


        [Authorize]
        public IActionResult Index()

Information needed

  1. What nuget packages are you using
  2. What is the expected behaviour: To Send me to SLO Page
  3. What happens instead. In the case of an exception, this includes the exception typ, complete exception message (personal information may be redacted) and a stack trace:
System.NotImplementedException: The method or operation is not implemented.
   at Sustainsys.Saml2.AspNetCore2.Saml2Handler.AuthenticateAsync()
   at Microsoft.AspNetCore.Authentication.AuthenticationService.AuthenticateAsync(HttpContext context, String scheme)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

Additional info

Please include

Super Basic Startup


public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication()
                  .AddSaml2(options =>
                  {
                      options.SPOptions.EntityId = new EntityId("https://localhost:5001/Saml2");
                      options.IdentityProviders.Add(
                          new IdentityProvider(
                              new EntityId("https://sso-xxxxxxxx.sso.duosecurity.com/saml2/sp/xxxxxxx/metadata"), options.SPOptions)
                          {
                              LoadMetadata = true
                          });

                  });

            services.AddControllersWithViews();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }
            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthentication();

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

@TheArchitectIO Is there any update about this issue ?, I got same error trying to verify user is signed on as Saml2 scheme before to perform a Signout action.

var result = await HttpContext.AuthenticateAsync(Saml2Defaults.Scheme);
if (result?.Principal != null) { await HttpContext.SignOutAsync(Saml2Defaults.Scheme); }

nuget v2.8 netcore v2.2

after some research found this post from Anders Abel "The Saml2 handler cannot be used as an authencation scheme, it is a challenge scheme."

justintoth commented 3 years ago

@IMrBean @TheArchitectIO Did either of you get past this error? I'm running into the same exception when calling HttpContext.AuthenticateAsync.

IMrBean commented 3 years ago

Well to bypass this nightmare I take as reference the work off @hmacat https://github.com/hmacat/Saml2WebAPIAndAngularSpaExample/blob/master/ExampleWebApi/Startup.cs he built an example off a webapi using SAML2 authentication to set up authorization over api resources emitting a JWT after SAML2 login was successfully performed. Bassically what I do was to put the cookie off the saml2scheme in an "External" scheme and leaving Saml2Scheme as the default challenge scheme. ` services.AddAuthentication(sharedOptions => { sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; sharedOptions.DefaultSignInScheme = IdentityConstants.ExternalScheme; //"Identity.External" sharedOptions.DefaultChallengeScheme = Sustainsys.Saml2.AspNetCore2.Saml2Defaults.Scheme; sharedOptions.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;
});

        services.AddAuthentication()
           .AddCookie(ApplicationSamlConstants.External)
           .AddSaml2(options =>
           {
               options.SPOptions.EntityId = new EntityId(authenticationConfigSection.GetValue<string>("Wtrealm"));
               options.SPOptions.ReturnUrl = new Uri(authenticationConfigSection.GetValue<string>("audienceUri"));
               options.SPOptions.AuthenticateRequestSigningBehavior = SigningBehavior.Never;
               options.SPOptions.Saml2PSecurityTokenHandler = new Saml2PSecurityTokenHandler();
               options.IdentityProviders.Add(
                  new IdentityProvider(
                    new EntityId(authenticationConfigSection.GetValue<string>("authorityName")), options.SPOptions)
                  {
                      MetadataLocation = authenticationConfigSection.GetValue<string>("MetadataAddress"),
                      AllowUnsolicitedAuthnResponse = true,
                      LoadMetadata = true,
                      Binding = Saml2BindingType.HttpRedirect
                      , SingleLogoutServiceBinding = Saml2BindingType.HttpRedirect
                      ,DisableOutboundLogoutRequests=false
                  });
               options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
               options.SPOptions.ServiceCertificates.Add(certificate);
               options.SignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme; 

So in that way aftter callback is called I can retrive the claims values off authenticathion stored in CookieAuthenticationDefaults.AuthenticationScheme in order to use them to authenticate on IdentityConstants.ExternalScheme; //"Identity.External" var authenticateResult = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);

if (!authenticateResult.Succeeded) {
return Unauthorized(); }

var claimsIdentity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme); var username = authenticateResult.Principal.Claims.FirstOrDefault(c=>c.Type.Equals(ClaimTypes.NameIdentifier)).Value.ToString();
var claims = new List(); claims = claimsIdentity.Claims.ToList();

  try
        {
            var principal = new ClaimsPrincipal(claimsIdentity);

            HttpContext.User = new ClaimsPrincipal(claimsIdentity);
            User.AddIdentity(claimsIdentity);         
            Thread.CurrentPrincipal= new ClaimsPrincipal(claimsIdentity);

            await HttpContext.SignInAsync(
                                       IdentityConstants.ExternalScheme,
                                    principal, new AuthenticationProperties() { IsPersistent = true });             
        }
        catch (Exception ex)
        {
            _logger.LogError(ex.StackTrace);
        }
      `  

Now you are signed on your custon ExternalScheme and ApplicationCookiesScheme, so SamlScheme is a challenge scheme not an authenticathion one, there is the main reason because claims doesnt persist over Saml2Scheme.

to perform logout action, I call
` await HttpContext.SignOutAsync(Saml2Defaults.Scheme, new AuthenticationProperties { RedirectUri = "/" });

var location = HttpContext.Response.Headers["Location"]; // SignOutAsync return the redirect logoutSaml2 url and after you got the saml2logut request (location variable) you need to sign out the user from the AplicationCookiesScheme and from the "External" Scheme and after that just send the redirect action to the Idp with the saml2logout request await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

return Redirect(location);

`

justintoth commented 3 years ago

@IMrBean Thanks for sharing your code, I really appreciate it! I wasn't able to get it to work... Regardless of which cookie I set in the Startup configuration (Identity Server's external cookie or the one you have above), only a cookie named "Saml2.{relayStateId}" is set and this happens when challenge is called, not after the IDP returns back to the challenge callback. Thus, calling HttpContext.SignInAsync returns a failed result, as there is no external cookie to act on.

After burning a week trying to make this work, I resorted to reading in the SamlResponse value that is POST'd as a base64 string, decoding that into XML, loading it into an XmlDocument, and then validating that document manually. This isn't nearly as sexy as being able to just call HttpContext.SignInAsync and having it magically validate the response and return the claims for me, however I don't know what else to do so...

AndersAbel commented 2 years ago

You should not call Authenticate on the Saml2 scheme. Call it on the cookie scheme, which is the one preserving the session.