Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
  • 14:13
    AndersAbel closed #1365
  • 14:13
    AndersAbel commented #1365
  • 14:06
    AndersAbel closed #1356
  • 13:53
    prrami closed #1364
  • 13:53
    prrami commented #1364
  • 13:53
    prrami labeled #1365
  • 13:53
    prrami opened #1365
  • 13:48
    prrami opened #1364
  • Aug 11 21:36
    dependabot[bot] labeled #1363
  • Aug 11 21:36
    dependabot[bot] opened #1363
  • Aug 11 21:36

    dependabot[bot] on nuget

    Bump System.Security.Cryptograp… (compare)

  • Aug 11 21:25
    dependabot[bot] labeled #1362
  • Aug 11 21:25
    dependabot[bot] opened #1362
  • Aug 11 21:25

    dependabot[bot] on nuget

    Bump System.Security.Cryptograp… (compare)

  • Aug 11 21:24
    dependabot[bot] labeled #1361
  • Aug 11 21:24
    dependabot[bot] labeled #1360
  • Aug 11 21:24
    dependabot[bot] opened #1361
  • Aug 11 21:24
    dependabot[bot] opened #1360
  • Aug 11 21:24

    dependabot[bot] on nuget

    Bump System.Security.Cryptograp… (compare)

  • Aug 11 21:24

    dependabot[bot] on nuget

    Bump System.Security.Cryptograp… (compare)

Anders Abel
@AndersAbel
Looks like InResponseTo is used as some kind of correlation ID between the authentication request and the returned response?
The contents of InResponseTo when using Sustainsys.Saml2 is the Id of the outbound AuthnRequest, which is a random Id. You cannot control how it is generated.
I suggest that you generate your own correlation id and pass it with the AuthProps when doing the challenge for Saml2. That value will be returned after the authentication is complete.
It will not be transmitted as ID/InResponseTo though, so if your Idp depends on that it is worse
It is of course always possible to custom adaptions - if you want more dedicated help I do provide commercial consulting services.
Mike Lindegarde
@mlindegarde
Thanks for the response. I've already created an example of round tripping and ID using the AuthProps. Between that and using the notifications I can probably accomplish what they want. I was just hoping for something a bit cleaner. If I need something more tailored to our needs I'll reach out.
Thanks again.
Anders Abel
@AndersAbel
@mlindegarde I read what you wrote earlier here too now and understand a bit more: Easiest way to make the InResponseTo value available in the ExternalController.Callback is to implement AcsCommandResultCreated and add the InResponseTo as a claim to the identity. That way you don't have to mess with DI-dependent services in the notification.
Mike Lindegarde
@mlindegarde
@AndersAbel I really like that idea. Seems very promising. The question I have there is how do I access the identity from within AcsCommandResultCreated? Looking at the definitions of CommandResult and Saml2Response I don't see a way to get at the identity. I can get the claims useing Saml2Response.GetClaims but I don't think I can add claims that way.
Bah, I found it: commandResult.Principal.Claims
Mike Lindegarde
@mlindegarde
Well, I need to cast the Principal to a ClaimsIdenitty etc... I get it now.
Mike Lindegarde
@mlindegarde
What is the proper way to add multiple SAML IdPs?
I currently have this code:
builder.AddSaml2(
    provider.AuthenticationScheme,
    provider.DisplayName,
    options =>
    {
        options.SignInScheme = provider.SignInScheme;
        options.SPOptions.EntityId = new EntityId(provider.ServiceProviderEntityId);
        options.SPOptions.Logger = adapter;
        options.SPOptions.ServiceCertificates.Add(certificate);

        options.IdentityProviders.Add(
            new IdentityProvider(new EntityId(provider.IdentityProviderEntityId), options.SPOptions)
            {
                MetadataLocation = provider.MetadataLocation,
                LoadMetadata = provider.LoadMetadata,

            });
    });
It gets called for each of the 2 SAML providers I have.
Each provider works on it's own.
When I add the second one, the second one fails with this error: KeyNotFoundException: The given key 'Sustainsys.Saml2.Metadata.EntityId' was not present in the dictionary.
If I reverse the order, then the second one still fails.
So, if I have a list like: [idpA, idpB].. idpB will return the error message above.
If I reverse the list so it's: [idpB, idpA]... then idpA will return the error message.
I'm clearly not handling adding multiple SAML identity providers correctly. I don't know what I'm missing.
Anders Abel
@AndersAbel
@mlindegarde There are two ways to add multiple Saml2 Idps using the library. 1: Use one authentication scheme (call AddSaml2 once) and add multiple IdentityProviders to the IdentityProviders collection. 2. Use one authentication scheme per Idp (call AddSaml2 once for each Idp). In the latter case you need to set a unique ModulePath for each call.
That's the reason for the error you see: The first handler will catch all incoming responses and if the response is for an Idp that is managed by another handler, it will throw an error message.
Please also note that if you use multiple handlers, they will be logically different SPs, with different metadata. So you need to make sure that you send the right metadata/Urls to each Idp
Mike Lindegarde
@mlindegarde
@AndersAbel Is there an example that shows how to handle multiple IdentityProvidersin the IdentotyProviders collection? With this approach there is only one scheme (Saml2) and I do not see an obvious way to map the scheme to the correct identity provider.
Mike Lindegarde
@mlindegarde
@AndersAbel It looks like I can specify the identity provider by passing "idp" and the entity id for the idp into the Items collection of the AuthenticationProperties object. Is this the "correct" way to handle a single authentiation scheme with multiple identity providers?
var props = new AuthenticationProperties
{
    RedirectUri = Url.Action(nameof(Callback)),
    Items =
    {
        { "returnUrl", returnUrl },
        { "scheme", scheme },
        { "idp", idp }
    }
};
Anders Abel
@AndersAbel
@mlindegarde Yes, with one scheme having multiple Idps the AuthProps is the right way.
But in many cases when the number of Idps is low and known from the start it's better with one Auth Scheme per Idp. It works better with how the Asp.Net and Asp.Net Identity architecture is setup. The main reason to have many Idps with one scheme is if the Idp list is dynamically loaded, such as from a common federation metadata file.
Mike Lindegarde
@mlindegarde
@AndersAbel dynamic loading is exactly where I'm heading.
We will be adding new federation metadata files as new companies come on board and I would prefer not to take down the auth server to do so.
My next question will most likely be around the proper way to add / remove IDPs dynamically. I haven't had the time to see if there is an example of that somewhere on the internet.
Mike Lindegarde
@mlindegarde
@AndersAbel Can you point me in the right direction for dynamically adding and removing IDPs at run-time? As my company sells its product to other companies we will need to add the new SAML2 IDP (if they have one). I do not want to have to restart the server to add the new IDP.
Anders Abel
@AndersAbel
There are several methods. With Asp.Net Core you can add/remove schemes runtime. You can also keep a reference to the options object around and alter the IdentityProviders collection. A third variant is to completely bypass the IdentityProviders collection and use the notifications for lookup instead. Design of solutions like that is what I do as commercial support so please feel free to mail me if you want to discuss more in depth help.
Imene Boussour
@imene-boussour
Hi, I am trying to use IdP initiated flow, my app acts as an IdP. reviewing sustainsys.saml2.stubidp I don't see the use of metadata files when sending Saml2Response to SP and how is the x509 certificates used? can anyone point me to the right direction? Also, I am new to SAML, In our project we will only be using Idp initiated flow, do I need to generate metadata file form my IDP?
Imene Boussour
@imene-boussour
Where can I find the file https://stubidp.sustainsys.com/a34d5bbc-48d0-4c87-9037-03ad7599351f/Metadata in the stubidp project? is it autogenerated
Anders Abel
@AndersAbel
  1. The stub idp is made for testing. It never reads the SP metadata. Instead it relies on the received message and some guessing. This works fine for testing, but is totally unsafe for production use.
  2. Yes, you will need metadata for your Idp that your SP can consume (unless the SP can accept manually entered parameters)
  3. Idp initiated means you can actually start off the StubIdp and get a secure solution - if you never respond to an SP request there's no need to validate any SP requests.
  4. The stubidp/<GUID>/Metadata content is generated by the MetadataController in the StubIdp project each time it is accessed. It is not a file.
Imene Boussour
@imene-boussour
Thank you @AndersAbel, we actually never receive SP requests in our project, we store the SP metadata securely in our server and use it to send a SAML2Response to SP. What I'm not sure about is how to generate a metadata file to upload it into the SP?
Anders Abel
@AndersAbel
There is an example of the code to generate Idp metadata in the StubIdp project. If you want to go the open source way, you have to dig into that code (and copy it if you want, the license permits that). If you want more instructions or implementation help I'd be happy to supply that as a commercial consulting job.
Huggy56
@Huggy56
Hi, I am looking for an example project (SampleIdentityServer4AspNetIdentity) but in .NET framework 4.6.1. Is it exist ? Thanks a lot.
RoLY roLLs
@RoLYroLLs
Hello all. I'm looking to load Idp's from a DB. I found this issue Sustainsys/Saml2#964 and I'm unsure how to implement the SelectIdentityProvider and GetIdentityProvider notifications. Anyone can guide me in the right direction would be appreciated. Thank you.
Mike Lindegarde
@mlindegarde
Sure, I can point you in the right direction...
builder.AddSaml2(
    saml2Options =>
    {
        saml2Options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;

        saml2Options.SPOptions.EntityId = new EntityId(config.Saml2.ServiceProviderEntityId);
        saml2Options.SPOptions.ServiceCertificates.Add(certificate);

        saml2Options.Notifications.SelectIdentityProvider =
            (id, data) => GetProvider(identityProviderStore, id, data, saml2Options, logger);

        saml2Options.Notifications.GetIdentityProvider =
            (id, data, options) => GetProvider(identityProviderStore, id, data, options, logger);

        saml2Options.Notifications.AcsCommandResultCreated =
            (commandResult, response) =>
            {
                if (commandResult.Principal.Identity is ClaimsIdentity identity)
                    identity.AddClaim(new Claim("in_response_to", response.InResponseTo.Value));
            };
    });
The GetProvider method looks something like this...
private IdentityProvider GetProvider(IEegIdentityProviderStore identityProviderStore, EntityId id, IDictionary<string,string> data, IOptions options, ILogger logger)
{
    Saml2IdentityProvider provider = identityProviderStore.GetSamlProviderByEntityId(id.Id);

    if (provider == null)
        return null;

    idp =
        new IdentityProvider(new EntityId(provider.EntityId), options.SPOptions)
        {
            MetadataLocation = provider.Debug
                ? provider.DebugMetadataLocation
                : provider.MetadataLocation
        };

    logger.Verbose("Adding Identity Provider: {IdpName}", provider.DisplayName);

    if (provider.Debug)
        logger.Warning(
            "Provider {Provider} is in debug mode, using metadata URL: {MetadataUrl}",
            provider.DisplayName,
            provider.DebugMetadataLocation);

    options.IdentityProviders.Add(idp);
    return idp;
}
Mike Lindegarde
@mlindegarde
I hacked out some code that wasn't relevant to your question, I apologize if there are any errors in that code, but it should get you going.
@RoLYroLLs see above.
Anders Abel
@AndersAbel
@mlindegarde Thanks for sharing. Just a note: When using Idp metadata loading you should cache the IdentityProvider objects between the calls. If you don't, a metadata download will take place on every SAML2 operation.
Mike Lindegarde
@mlindegarde
@AndersAbel Thanks for the note. When I mentioned that I "hacked out some code", I was mostly referring to my caching code. You make a very good point. I should have mentioned that @RoLYroLLs will need to do some sort of caching.
I believe the above code would also result in adding the same IDP to the options.IdentityProviders collection multiple times. That would also be a problem.
RoLY roLLs
@RoLYroLLs

@mlindegarde and @AndersAbel thank you for your input. There are a couple methods there that I don't have a great understanding how they work, in either way here's what I've done adjusting your code to mine. FYI: this project is a legacy Asp.Net Web App (non-MVC).

in my Global.asax

    void Application_BeginRequest(object sender, EventArgs e) {
        Saml2Config.Initialize();
    }

In the Sam2Config file:

    public class Saml2Config {
        private static bool _alreadyInitialized;
        private static readonly object Lock = new object();
        private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
        public static void Initialize() {
            if (_alreadyInitialized) {
                return;
            }

            lock (Lock) {
                if (_alreadyInitialized) {
                    return;
                }

                spOptions = Options.FromConfiguration.SPOptions;

                // Get list of IdentityProviders
                var saml2IdentityProvidersRepository = new Saml2IdentityProvidersRepository();
                var saml2IdentityProviders = saml2IdentityProvidersRepository.FindByServiceProviderEntityId(spOptions.EntityId.Id, x => x.Saml2BindingType).OrderBy(x => x.OrderPreference);

                Options.FromConfiguration.Notifications.SelectIdentityProvider = (id, data) => GetProvider(saml2IdentityProviders, id, data, Options.FromConfiguration, _logger);
                Options.FromConfiguration.Notifications.GetIdentityProvider = (id, data, options) => GetProvider(saml2IdentityProviders, id, data, options, _logger);

                saml2IdentityProvidersRepository.Dispose();
                _alreadyInitialized = true;
            }
        }

        private static IdentityProvider GetProvider(IQueryable<Saml2IdentityProvider> identityProviderStore, EntityId id, IDictionary<string, string> data, IOptions options, ILogger logger) {
            var identityProvider = identityProviderStore.FirstOrDefault(x => x.IsActive && x.IdentityProviderEntityId.Equals(id.Id));

            if (identityProvider == null) {
                return null;
            }

            var idpEntityId = new EntityId(identityProvider.IdentityProviderEntityId);
            var bindingType = EnumHelper.NumToEnum<Sustainsys.Saml2.WebSso.Saml2BindingType>(identityProvider.Saml2BindingType.Value);

            var idp = new IdentityProvider(idpEntityId, options.SPOptions) {
                MetadataLocation = identityProvider.MetadataLocation,
                LoadMetadata = identityProvider.LoadMetadata,
                AllowUnsolicitedAuthnResponse = identityProvider.AllowUnsolicitedAuthnResponse,
                Binding = bindingType,
            };

            options.IdentityProviders.Add(idp);
            return idp;
        }