Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Idp-initiated SSO w/ AspNetCore2 Handler #1030

Closed
sai450 opened this issue Oct 13, 2018 · 17 comments
Closed

Idp-initiated SSO w/ AspNetCore2 Handler #1030

sai450 opened this issue Oct 13, 2018 · 17 comments

Comments

@sai450
Copy link

sai450 commented Oct 13, 2018

Idp-Initiated SSO (Unsolicited SSO) Auth does not seem to work when using AspNetCore2 Handler

I used SSOCircle as the identity provider and AspNetCore2 sample to test. Apart from the default settings, I have set ReturnUrl to "/account/externallogin?handler=callback" and set AllowUnsolicitedAuthnResponse to true

Observation:
When the execution reaches OnGetCallbackAsync handler (ExternalLogin.cshtml.cs) via Idp Initiated SSO, call to the following line of code returns null (where as the same line of code successfully retrieves ExternalLoginInfo with UserPrincipal and Claims for SP Initiated Logins).

var info = await _signInManager.GetExternalLoginInfoAsync();

Prior to reaching ExternalLogin Callback handler, Logs indicate "Successfully processed SAML response" and "Identity.External signed in", however the callback handler fails to retrieve ExternalLoginInfo.

Library/Framework Versions:
TargetFramework: net472
Sustainsys.Saml2.AspNetCore2: 2.0.0

I believe this could be a bug (Unless I'm missing something). Any help is appreciated.

@AndersAbel AndersAbel added the bug label Oct 15, 2018
@AndersAbel
Copy link
Member

This needs debugging.

@AndersAbel
Copy link
Member

This is due to how SignInManager.GetExternalLoginInfoAsync works. It expects and looks for a provider in the relayData:

https://github.com/aspnet/Identity/blob/2c9e1940204837bcd425e8f4488bf2564434b357/src/Identity/SignInManager.cs#L612

With an Idp-initiated sign on, there is no relayData. So you need to make your own implementation of GetExternalLoginInfoAsync that covers for that.

@sai450
Copy link
Author

sai450 commented Oct 26, 2018

Thank you, Anders for looking into this. I'll try to implement custom logic for GetExternalLoginInfoAsync like you suggested.

@AndersAbel
Copy link
Member

@sai450 Well, I just had to implement it myself for a customer :) It's mostly a matter of copying the existing code and removing the provider check. Then you somehow have to find out the provider key - can be hard coded if Saml2 is the only external provider.

@mickey-stringer
Copy link

mickey-stringer commented Apr 20, 2020

@AndersAbel can you expand on that?
I assume you mean to remove this, but can you confirm:
if (providerKey == null || provider == null) { return null; }

And then what value did you use to hardcode the provider key?

Edit: I was able to figure this out. Posting the steps I took for reference.
As Anders said, create a custom implementation of the SignInManger class. Make sure to change the namespace and the class name, then register it in Startup:
services.AddTransient<CustomSignInManager<IdentityUser>>();

Remove || !items.ContainsKey(LoginProviderKey) (commented out below) at line 593:

public virtual async Task<ExternalLoginInfo> GetExternalLoginInfoAsync(string expectedXsrf = null)
        {
            var auth = await Context.AuthenticateAsync(IdentityConstants.ExternalScheme);
            var items = auth?.Properties?.Items;
            if (auth?.Principal == null || items == null) // || !items.ContainsKey(LoginProviderKey)) -- Items does not contain LoginProviderKey in Idp-initiated flow
            {
                return null;
            } 

Then hardcode (or figure out a better way to substitute) the value of the provider variable where it tries to retrieve the LoginProviderKey at line 612:

var provider = "Saml2"; //items[LoginProviderKey] as string; - LoginProviderKey isn't present in Idp-initiated flow

At this point, authentication should work. New logins will still be prompted to register with an email, - and confirm that email - but they are authenticated.

@AndersAbel
Copy link
Member

@mickey-stringer Thanks for taking the time to post the how-to!

@mickey-stringer
Copy link

@AndersAbel thank you for pointing me in the right direction!

@Jrssnyder
Copy link

@mickey-stringer and @AndersAbel
I have a similar issue with IdP-initiated login. For my setup, please see https://stackoverflow.com/questions/63853661/authenticateresult-succeeded-is-false-with-okta-and-sustainsys-saml2.

As you can see from the code samples on SO, I am not using a sign-in manager, but am directly calling:
await HttpContext.AuthenticateAsync(ApplicationSamlConstants.External);
But for IdP-initiated, it has no information. (It is now working for SP-initiated, though).
AuthenticateResult.Succeeded is false, and AuthenticateResult.None is true.

Can either of you offer any advice?
Thanks so much for any assistance you can offer.

@Jrssnyder
Copy link

Update, just FYI:
When I run the solution in Visual Studio, it works, using the test IdP provider, i.e., https://stubidp.sustainsys.com/.
And when I deploy the solution to my local IIS instance on my desktop, it still works, using the test IdP provider
But when I deploy that same exact code to our production server (a VM), it fails as described above.
Any thoughts on what to look for or how to debug further?

@Jrssnyder
Copy link

Jrssnyder commented Sep 15, 2020

I have captured the following debugging information (from the production VM):
2020-09-15 16:01:40.574 -05:00 [DBG] Received unsolicited Saml Response Microsoft.IdentityModel.Tokens.Saml2.Saml2Id which is allowed for idp http://www.okta.com/exk1jic9zn7QommF00h8
2020-09-15 16:01:40.590 -05:00 [DBG] Signature validation passed for Saml Response Microsoft.IdentityModel.Tokens.Saml2.Saml2Id
2020-09-15 16:01:40.652 -05:00 [DBG] Extracted SAML assertion id16338952065129118260692652
2020-09-15 16:01:40.652 -05:00 [INF] Successfully processed SAML response Microsoft.IdentityModel.Tokens.Saml2.Saml2Id and authenticated bankoetest@sfi.cloud
2020-09-15 16:01:41.433 -05:00 [ERR] SAML Authentication Failure:
authenticateResult.Failure (Exception object) is null;
No information was returned for the authentication scheme;
authenticateResult.Principal is null;
authenticateResult.Properties is null.
authenticateResult.Ticket is null.

@Narshe1412
Copy link

Would it be possible to have both implementations at the same time?
On my identityserver we allow OIDC, Microsoft Accounts, and regular SAML with the Sustainsys package. One of our users asked to check if we can support Idp initiated. I wouldn't like to cripple the support for all the other users while trying to support this specific case.

Thanks for the input!

@AndersAbel
Copy link
Member

@Narshe1412 Yes. The setting to allow Idp initiated is per Idp.

@Narshe1412
Copy link

Apologies, I meant both implementations of the Signing Manager :)
PS: Thanks for the quick response.

@mickey-stringer
Copy link

@Narshe1412 you'd still need to use a custom SignInManager in order to handle the IDP-initiated flow, you just need to make it more resilient/flexible so that your SP-initiated methods are still supported. Basically, you'll treat IDP-initiated flow as a fallback. Use duck typing to determine if the auth attempt is IDP-initiated (e.g. items is null), and then apply the modified code.

@IAMHK90
Copy link

IAMHK90 commented Aug 25, 2023

var provider = "Saml2"; //items[LoginProviderKey] as string; - LoginProviderKey isn't present in Idp-initiated flow

I see that provider value was hard coded, but in my case I do have mutiple Idp-initiated logins. @mickey-stringer Is there a way to obtain the provider key dynamically, depending on the external provider?

@mickey-stringer
Copy link

@IAMHK90 It's been a while since I've worked with this package, but since you register different IDPs based on their metadata and signing keys and such, there is a way to access which provider a login is coming from. I just don't recall exactly how/where to do that, and I no longer have access to the repository where I implemented it.
I do know I paused the debugger on every single step of the login process to see the available claims and figure it out, so you should at least be able to get somewhere by doing that!

Sorry I can't be of more help. Good luck!

@sai450
Copy link
Author

sai450 commented Sep 4, 2023

@IAMHK90 - It varies by implementation and requirements but one way to figure out the provider it is to define a unique ReturnUrl under SP Options for each unique IDP (in our case the ReturnUrl is naturally dynamic and varies per client due to a multi-tenant setup). Then, in the custom SignInManager, you can figure out the LoginProviderKey value based on the requested path. (you would still need to debug line by line like @mickey-stringer mentioned to find the setup that best works for you)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants