Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.
This repository was archived by the owner on Dec 13, 2018. It is now read-only.

The Grand Auth Redesign of 2017 #1179

Closed

Activity

  1. self-assigned this
    on Apr 13, 2017
  2. added this to the 2.0.0-preview1 milestone on Apr 13, 2017
  3. RickBlouch commented on Apr 19, 2017

    @RickBlouch

    I have read through the history for much of the Auth 2 issues and have browsed the code in the hoak/auth2 branch trying to get an early understanding of the new system. I realize it's still a work in progress so I may be too early and if that's the case just let me know, but since the "Named/multi tenant options #35 " is marked as close I'm hoping you can point me in the right direction.

    Right now I am using something very similar to this post where the request pipeline is being forked for each tenant (based on hostname) and I am able to create tenant specific authentication middleware (among other tenant specific middleware setup) configurations based on tenant specific values retrieved from our database. More specifically we have scenarios where some tenants have one or two OIDC middleware configured and others may not have any.

    I can see in the new codebase that this will not work the same way with Auth 2 and that's fine, but I couldn't figure out based on reading the various issues and browsing the new codebase exactly how I will replicate the above functionality. Can you point me in the right direction?

  4. HaoK commented on Apr 19, 2017

    @HaoK
    MemberAuthor

    There won't be any way to fork things since there's only a single service collection, but you should be able to register each using the tenant specific name similar to how the cookie name is being set in that post you mention, if you don't know the tenants at startup, can add dynamically schemes via the ISchemeProvider.AddScheme https://github.com/aspnet/HttpAbstractions/blob/dev/src/Microsoft.AspNetCore.Authentication.Abstractions/IAuthenticationSchemeProvider.cs#L56:

       services.AddGoogleAuthentication("Google-<tennant>", options => { });
    

    You'll also have to challenge/authenticate using the new tenant specific names as opposed to just always "Google", but I imagine that shouldn't be too big of a change.

    Does that sound like it will work for your scenario?

  5. CoskunSunali commented on Apr 19, 2017

    @CoskunSunali

    I have a scenario where I implement the things exactly the way @RickBlouch does.

    The multiple-schemes solution does not sound like the best scenario - if I don't get it wrong.

    The way @RickBlouch and I implement the things makes each tenant able to access only the services specific to itself but the way you suggest by schemes solving the issue requires each tenant to have access to all schemes even if they don't relate to it.

    What is more, it requires (if I am not wrong) manually invoking the challenge/authenticate methods when needed as opposed to having a single scheme for each tenant's own pipeline using the same scheme name and having it automatically handled.

    @HaoK , I have not really looked through the changes you made but the main complaint about the auth configuration was that we had to configure some of the things too early in the app life cycle. For instance we still did not know which tenant was being requested (lazy initialization) but we had to configure the token endpoint using a specific URL instead of tenant specific services and token endpoints based on the tenant host names.

    We discussed the same thing with @PinpointTownes at aspnet-contrib/AspNet.Security.OpenIdConnect.Server#107 shortly. The issue does not provide much information but it should give you an idea. Otherwise I invite @PinpointTownes to this discussion.

    The following quotation from @PinpointTownes's comments describes the issue very well, in my opinion.

    Sadly, this won't work, as AuthenticationMiddleware calls IOptions<>.Options directly in its constructor (where you can't access the current request, since there's simply none 😄) and stores it as a singleton: https://github.com/aspnet/Security/blob/dev/src/Microsoft.AspNet.Authentication/AuthenticationMiddleware.cs#L26-L34

    To support multi-tenancy, AuthenticationMiddleware's inheritors would also have to update their constructor, to avoid accessing the options instance too early. I guess we'd need a ValidateOptions directly in AuthenticationMiddleware to delay options validation.

  6. HaoK commented on Apr 19, 2017

    @HaoK
    MemberAuthor

    So far we have only enabled dynamically adding/removing schemes since its is the first building block needed for multi-tenancy.

    To keep each tenant's ability to challenge "Google"/"Facebook" with the new stack, you should be able to accomplish this by replacing some combination of IAuthenticationService/IAuthenticationSchemeProvider/IAuthenticationHandlerProvider to resolve the appropriate tenant specific instance using the request.

    The main idea was to make auth service based rather than middleware, so there should be a lot more extensibility/flexibility now

  7. RickBlouch commented on Apr 19, 2017

    @RickBlouch

    There won't be any way to fork things since there's only a single service collection, but you should be able to register each using the tenant specific name similar to how the cookie name is being set in that post you mention, if you don't know the tenants at startup, can add dynamically schemes via the ISchemeProvider.AddScheme https://github.com/aspnet/HttpAbstractions/blob/dev/src/Microsoft.AspNetCore.Authentication.Abstractions/IAuthenticationSchemeProvider.cs#L56:

    services.AddGoogleAuthentication("Google-", options => { });

    Forking the pipeline was a pretty clean way to do things so I'm sad to see it go; this feels like a step backwards in that regard. From top to bottom, my pipeline could be unique per tenant which was great in terms of isolation. We deal with PCI and HIPAA so the more isolation the better. It's also not realistic to load all tenants at startup time, I can't imagine that would be the case for very many enterprise apps.

    Just to confirm what you are suggesting, in order to dynamically add these schemes on the fly, I'm guessing I will have to have a step in my tenant resolution middleware that makes a call to GetSchemeAsync and checks if a scheme exists with my tenant based scheme name "Google-*****". If not, call AddScheme.

    Does that sound like what you are anticipating? Will AddScheme be adding additional dependencies to my DI container with every call? At 1000-2000 tenants will that be a problem?

    You'll also have to challenge/authenticate using the new tenant specific names as opposed to just always "Google", but I imagine that shouldn't be too big of a change.

    I can inject an object that has tenant context so yes this can happen pretty easily, just something extra I need to remember to do that I didn't have to with a forked pipeline.

    await _httpContextAccessor.HttpContext.Authentication.SignInAsync("Google-<tenant>", new ClaimsPrincipal(id), authInfo.Properties);

    Does that sound like it will work for your scenario?

    I don't think I have a good enough understanding of the new way to make that decision yet. I'm going to see if I can evaluate IAuthenticationService/IAuthenticationSchemeProvider/IAuthenticationHandlerProvider next.

  8. HaoK commented on Apr 19, 2017

    @HaoK
    MemberAuthor

    That sounds right at this point, that said, we will likely spend some cycles to have a 'blessed' way that we encourage for multi-tenancy, so things should get better. Make sure to be involved in that issue/PR when we start on that work. cc @blowdart

  9. HaoK commented on Apr 19, 2017

    @HaoK
    MemberAuthor

    Initial wave of PRs pushed to security/MVC/Identity/MusicStore, tests all passed locally so hopefully no 💥

  10. kevinchalet commented on Apr 19, 2017

    @kevinchalet
    Contributor

    Otherwise I invite @PinpointTownes to this discussion.

    @CoskunSunali haha, thanks!

    Multi-tenancy was indeed mentioned a few times during the "Auth 2.0" design: @HaoK was in favor of a "multiple scheme handlers" approach while I was more inclined to think that it was not really scalable and that the best approach would be to have a single scheme handler with potentially an infinity of options, resolved dynamically (per request):
    #1113 (comment) #1113 (comment)

    While I haven't tested (yet), I believe the approach I mentioned in the ASOS topic should now be possible, since options validation and initialization are now delayed. That said, I don't think the experience is ideal (yet), because a few important things are still missing (e.g async support in the options stack).

    I'll start porting ASOS to ASP.NET Core 2.0 in the next few days, so I'll have a chance to test whether things work correctly, even for multi-tenant scenarios 😄

  11. RickBlouch commented on Apr 19, 2017

    @RickBlouch

    @PinpointTownes I would be interested to know your findings with the approach you mentioned in that link. I also believe a single scheme approach with options resolved per request is the cleanest option all around. Please let me know where I can follow your progress / results of your testing. I'm not in position right now to be able to play with the 2.0 bits unfortunately.

  12. HaoK commented on Apr 19, 2017

    @HaoK
    MemberAuthor

    @PinpointTownes I think it should be easier with the new 2.0 stack to implement single handler with infinity options resolved per request if you desire. Its also possible we end up there for the built in auth handlers as well

  13. 45 remaining items

  14. Tratcher commented on Dec 7, 2018

    @Tratcher
    Member

    (In general this should be way easier with the same extension methods that work at startup also working later in the process but for right now I just need to get this working.)

    He's right, this experience is very incomplete if we can't even produce working sample code for a real auth handler.

  15. HaoK commented on Dec 7, 2018

    @HaoK
    MemberAuthor

    To be fair, there is no experience, we only added the extensibility points to make it possible to implement multi-tennant scenarios completely on your own via wholesale replacement. We never targeted actually supporting it with our higher level apis, because its just not something most of the current code can easily support

  16. JohnGalt1717 commented on Dec 7, 2018

    @JohnGalt1717

    OK, well this should be a to-do to make this usable in the real world and expand out the example code so that it actually would work (as it is right now it doesn't)

    Now I've added this:

    oidcOptions.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(oidcOptions.MetadataAddress, new OpenIdConnectConfigurationRetriever(), new HttpDocumentRetriever(oidcOptions.Backchannel) { RequireHttps = oidcOptions.RequireHttpsMetadata } );

    Which is basically right out of the code example above.

    Which gets this error:

    IDX10000: The parameter 'httpClient' cannot be a 'null' or an empty object.
    Parameter name: httpClient

  17. JohnGalt1717 commented on Dec 7, 2018

    @JohnGalt1717

    And if I use the overload that doesn't pass the HttpDocumentRetriever I get the following error:

    {"Errors":[{"Description":"Object reference not set to an instance of an object.\r\n at Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.WriteNonceCookie(String nonce)\r\n at Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.HandleChallengeAsync(AuthenticationProperties properties)\r\n at Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.ChallengeAsync(AuthenticationProperties properties)\r\n at Microsoft.AspNetCore.Authentication.AuthenticationService.ChallengeAsync(HttpContext context, String scheme, AuthenticationProperties properties)\r\n at Microsoft.AspNetCore.Mvc.ChallengeResult.ExecuteResultAsync(ActionContext context)\r\n at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultAsync(IActionResult result)\r\n at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResultFilterAsyncTFilter,TFilterAsync\r\n at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResultExecutedContext context)\r\n at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)\r\n at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultFilters()\r\n at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()\r\n at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)\r\n at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)\r\n at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()\r\n at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()\r\n at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)\r\n at Microsoft.AspNetCore.ResponseCompression.ResponseCompressionMiddleware.Invoke(HttpContext context)\r\n at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.Invoke(HttpContext context)","ErrorType":"System.NullReferenceException","Property":"Microsoft.AspNetCore.Authentication.OpenIdConnect","Url": … }

    (and the other override that takes an httpclient also throws.)

    So one override will NEVER work. And the other 2 overrides fail with the above error message. And that's just openIdConnect. God knows what's going to happen when I try WSFederation for SAML2.

  18. Tratcher commented on Dec 7, 2018

    @Tratcher
    Member

    @JohnGalt1717 you're right, this approach is unusable.

    @HaoK we should take that sample down.

  19. HaoK commented on Dec 7, 2018

    @HaoK
    MemberAuthor

    The sample is demonstrating the ability to add and remove schemes per request, and how to plug in options for those. Its goal was not to be a working multitenant sample for the OIDC handler, there's a separate issue talking about multi-tenant, but not necessarily OIDC here dotnet/aspnetcore#4126

  20. JohnGalt1717 commented on Dec 7, 2018

    @JohnGalt1717

    @HaoK the sample doesn't work and never could work and doesn't actually demonstrate what you'd actually have to do to make it work.

    Thus it is worse than useless. I'd suggest the people responsible for this pr actually go and use that sole to create an azure openidconnect endpoint and make it work. Don't you can't .

  21. HaoK commented on Dec 7, 2018

    @HaoK
    MemberAuthor

    So there's a mismatch of expectations I think here, the existing auth handlers and related stack in Security (including OIDC/OAuth) are not built at all to make it easy to do multi-tenant. The only goal for the 2.0 auth changes was to preserve the ability to do it yourself via replacing most of the options + potentially the core authentication pieces as well.

    This sample was only meant to demonstrate how you can add and remove schemes and their options per request at the Abstractions layer (https://github.com/aspnet/AspNetCore/tree/master/src/Http/Authentication.Abstractions/src) as opposed to the Security layer (which is not really designed for this and would require a lot of work to enable)

  22. HaoK commented on Dec 7, 2018

    @HaoK
    MemberAuthor

    I'll add a paragraph in the readme stating that this sample isn't meant to be a solving the entire multi-tenant auth scenario (and point at the relevant other issues for those approaches).

  23. JohnGalt1717 commented on Dec 7, 2018

    @JohnGalt1717

    That's fine and all and yet I still can't make this work. Please advise what needs to happen in my code to make this actually work.

    And your sample again can never work because of bugs and because of incompleteness. And I suspect if you actually flushed out the sample you'd see how woefully inadiquate the work that's been done on this is.

  24. dazinator commented on Dec 8, 2018

    @dazinator

    Hello. I was wondering, instead of making Api changes, why not solve multitenancy by forking the middleware pipeline, and container / IServiceProvider, per tenant. That approach allows all api's to remain the same, as it solves the multitenancy problem at a higher level. For example: https://github.com/dazinator/Dotnettency/blob/develop/README.md
    Do you think forking middleware pipeline and containers is overkill? Or is there some other criticisms we should be aware of with this approach? Most multitenant systems also end up with other customisation per tenant which could touch all levels of the stack, from what middleware should run, to what implementation of a specific service to inject, to what views / pages should be rendered (i.e mvc / razor), to what files should be visible (one tenant shouldnt be allowed to access anothers), to what database should be used etc. It seems more degrading to the stack (to me!) to make all these services support named options in the name of multitenancy, than it does to solve it higher up in the stack!

  25. HaoK commented on Dec 8, 2018

    @HaoK
    MemberAuthor

    We aren't planning on addressing multitenancy for 3.0, we were just adding a more fleshed out multi-tenant auth sample that uses some of the Security stack, there was a PR open in AuthSamples and it will eventually get checked into once Mondo repo is in a good place again, its here:

    https://github.com/aspnet/AuthSamples/pull/44/files

  26. JohnGalt1717 commented on Dec 8, 2018

    @JohnGalt1717

    In the meantime, I'm still completely blocked because of at least one bug in .net core that needs to be fixed.

    Can someone give a snippet of a working openidconnect handler being added after the fact please?

  27. JohnGalt1717 commented on Dec 8, 2018

    @JohnGalt1717

    @HaoK that pr doesn't actually address the primary use case in multi tenancy. In the VAST majority of cases tenants will need sso with either openidconnect or saml2 (wsfederstion as Ms likes to call it)

    The samples should be focused on adding and removing those at runtime dynamically, not some custom authentication protocol that people would be insane to attempt given how complex sso is to do right.

  28. JohnGalt1717 commented on Dec 10, 2018

    @JohnGalt1717

    So I've gotten further with openIdConnect.

    I'm able to get it to go to the openIdconnect endpoint and the user logs in there and then I get this:

    Exception: Correlation failed.

    Show raw exception details
    Exception: An error was encountered while handling the remote login.
    Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler.HandleRequestAsync()
    Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
    Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware.InvokeCore(HttpContext context)
    Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
    Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

    OpenIdConnect setup:

    var dataProtector = DataProtection.CreateProtector(typeof(OpenIdConnectHandler).FullName, authMethod.Id.ToString(), "v1");
    
    								var oidcOptions = new OpenIdConnectOptions
    								{
    									MetadataAddress = authMethod.IdPUrl,
    									Authority = authMethod.Audience,
    									ClientId = authMethod.ClientId,
    									ClientSecret = authMethod.ClientSecret,
    									ResponseType = "code id_token",
    									CallbackPath = "/signin-oidc",
    									BackchannelTimeout = TimeSpan.FromMinutes(5),
    									Resource = authMethod.Type == AuthTypes.Azure ? authMethod.ClientId : Configuration.Issuer,
    									
    #if (DEBUG)
    									Prompt = "login",
    									RequireHttpsMetadata = false,
    #else
    									RequireHttpsMetadata = true,
    #endif
    									TokenValidationParameters = new TokenValidationParameters
    									{
    										ValidateIssuer = false,
    										ValidAudience = authMethod.ClientId
    									},
    									DataProtectionProvider = DataProtection,
    									StateDataFormat = new PropertiesDataFormat(dataProtector),
    									StringDataFormat = new SecureDataFormat<string>(new StringSerializer(), dataProtector)
    								};
    								oidcOptions.Scope.Clear();
    								oidcOptions.Scope.Add("openid");
    								oidcOptions.Scope.Add("profile");
    
    								oidcOptions.Backchannel = new HttpClient(oidcOptions.BackchannelHttpHandler ?? new HttpClientHandler());
    								oidcOptions.Backchannel.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft ASP.NET Core OpenIdConnect handler");
    								oidcOptions.Backchannel.Timeout = oidcOptions.BackchannelTimeout;
    								oidcOptions.Backchannel.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB
    
    								oidcOptions.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(oidcOptions.MetadataAddress, new OpenIdConnectConfigurationRetriever(), new HttpDocumentRetriever(oidcOptions.Backchannel) { RequireHttps = oidcOptions.RequireHttpsMetadata });
    
    

    Saml2 Setup: (Which gives Unsolicited logins are not allowed.)

    								var wsOptions = new WsFederationOptions
    								{
    									BackchannelTimeout = TimeSpan.FromMinutes(5),
    									MetadataAddress = authMethod.IdPUrl,
    									Wtrealm = authMethod.Audience,
    									RequireHttpsMetadata = false,
    									SaveTokens = true,
    									TokenValidationParameters = new TokenValidationParameters
    									{
    										ValidAudiences = new string[] { authMethod.Audience, $"spn:{authMethod.Audience}" },
    										ValidateIssuerSigningKey = string.IsNullOrEmpty(authMethod.Certificate),
    										IssuerSigningKey = string.IsNullOrEmpty(authMethod.Certificate) ? null : new X509SecurityKey(KeyVaultService.GetCertificateFromString(authMethod.Certificate))
    									},
    									DataProtectionProvider = DataProtection,
    								};
    
    								wsOptions.Backchannel = new HttpClient(wsOptions.BackchannelHttpHandler ?? new HttpClientHandler());
    								wsOptions.Backchannel.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft ASP.NET Core WsFederation handler");
    								wsOptions.Backchannel.Timeout = wsOptions.BackchannelTimeout;
    								wsOptions.Backchannel.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB
    
    								wsOptions.SignOutScheme = wsOptions.SignInScheme;
    
    								var dataProtector = wsOptions.DataProtectionProvider.CreateProtector(typeof(WsFederationHandler).FullName, authMethod.Id.ToString(), "v1");
    								wsOptions.StateDataFormat = new PropertiesDataFormat(dataProtector);
    
    								wsOptions.ConfigurationManager = new ConfigurationManager<WsFederationConfiguration>(wsOptions.MetadataAddress, new WsFederationConfigurationRetriever(), new HttpDocumentRetriever(wsOptions.Backchannel) { RequireHttps = wsOptions.RequireHttpsMetadata });
    								result = WsFederationOptionsCache.TryAdd(authMethod.Id.ToString(), wsOptions);
    
    

    Anyone have any suggestions on what I'm missing from my manual options? Seems like they're both related and once getting through this it should work.

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions