Aspnetcore: Unable to add OpenID Connect authentication scheme dynamically

Created on 13 Dec 2018  路  8Comments  路  Source: dotnet/aspnetcore

Describe the bug

I am unable to add an OpenID Connect authentication scheme "dynamically" (outside of Startup.cs). Based on the discussion here, it sounds like it should be doable by following the DynamicSchemes sample, however that is not the case. I found this discussion in which another person is having the same issue.

To Reproduce

I made a fork of DynamicSchemes with the OpenID Connect code added to it to show what's going on.

Steps to reproduce the behavior:

  1. Clone my DynamicSchemes fork
  2. Replace the "XXX" strings with an Authority, ClientID, and Client Secret for an OIDC provider of your choosing
  3. Run the sample
  4. Click the button under "Add OpenID Connect scheme" to add the scheme dynamically
  5. Click the "Sign in with OpenID Connect" button in the navbar to attempt to sign-in
  6. At this point you will get the following error: "InvalidOperationException: Provide Authority, MetadataAddress, Configuration, or ConfigurationManager to OpenIdConnectOptions"
  7. You can get around this by passing a ConfigurationManager to the OpenIdConnectOptions. Uncomment this line and fill in the "XXX" with the metadata address for your OIDC provider
  8. Run the sample again, you will get the following error: "NullReferenceException: Object reference not set to an instance of an object."

Expected behavior

Signing in to the dynamically added OIDC scheme should work without errors.

Screenshots

N/A

Additional context

Add any other context about the problem here.
Include the output of dotnet --info

PS C:\git\AspNetCore\src\AuthSamples> dotnet --info
.NET Core SDK (reflecting any global.json):
 Version:   3.0.100-preview-009812
 Commit:    e3abf6e935

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.17134
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\3.0.100-preview-009812\

Host (useful for support):
  Version: 3.0.0-preview-27122-01
  Commit:  00c5c8bc40

.NET Core SDKs installed:
  2.0.0 [C:\Program Files\dotnet\sdk]
  2.0.3 [C:\Program Files\dotnet\sdk]
  2.1.4 [C:\Program Files\dotnet\sdk]
  2.1.100 [C:\Program Files\dotnet\sdk]
  2.1.102 [C:\Program Files\dotnet\sdk]
  2.1.104 [C:\Program Files\dotnet\sdk]
  2.1.201 [C:\Program Files\dotnet\sdk]
  2.1.202 [C:\Program Files\dotnet\sdk]
  2.1.401 [C:\Program Files\dotnet\sdk]
  2.1.402 [C:\Program Files\dotnet\sdk]
  2.2.100 [C:\Program Files\dotnet\sdk]
  2.2.101 [C:\Program Files\dotnet\sdk]
  3.0.100-preview-009812 [C:\Program Files\dotnet\sdk]

.NET Core runtimes installed:
  Microsoft.AspNetCore.All 2.1.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.1.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.2.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.1.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.1.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.2.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 3.0.0-preview-18579-0056 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 2.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.0.3 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.0.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.0.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.0.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.3-servicing-26724-03 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.2.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 3.0.0-preview-27122-01 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.WindowsDesktop.App 3.0.0-alpha-27128-4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

To install additional .NET Core runtimes or SDKs:
  https://aka.ms/dotnet-download
area-security

Most helpful comment

I agree with John, the dynamically added scheme for OIDC independent of multi-tenant does not work in the sample provided (even for a single scheme at runtime). I'm working with IdentityServer4 which will resolve the idp through the arc_values if present. When removing the (working) AddOpenIdConnect from Startup and into a LoginController using the IAuthenicationSchemeProvider and even the IPostConfigureOptions an unhandled exception is chucked as the Options.ClientId is unable to be resolved. The IOptionsMonitorCache successfully adds the OpenIdConnectOptions with the named scheme.

Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectOptions.Validate()
Microsoft.AspNetCore.Authentication.RemoteAuthenticationOptions.Validate(string scheme)
Microsoft.AspNetCore.Authentication.AuthenticationHandler.InitializeAsync(AuthenticationScheme scheme, HttpContext context)
Microsoft.AspNetCore.Authentication.AuthenticationHandlerProvider.GetHandlerAsync(HttpContext context, string authenticationScheme)
IdentityServer4.Hosting.FederatedSignOut.FederatedSignoutAuthenticationHandlerProvider.GetHandlerAsync(HttpContext context, string authenticationScheme) in FederatedSignoutAuthenticationHandlerProvider.cs

All 8 comments

Try OpenIdConnectPostConfigureOptions, it helped me https://stackoverflow.com/a/49825151

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<OpenIdConnectPostConfigureOptions>();
...
}

Then Inject OpenIdConnectPostConfigureOptions in your AuthController
and use from AddOpenIdConnect method

var options = new OpenIdConnectOptions
                {
                    MetadataAddress =  "https://XXX.com/.well-known/openid-configuration",
                    CallbackPath = "/signin-oidc",
                    ClientId = "XXX",
            ClientSecret = "XXX"
                };
_openIdConnectPostConfigureOptions.PostConfigure("oidc", options);                
_optionsCache.TryAdd("oidc", options);

That got me a bit further. I added the changes to my fork in this commit.

However, it's still broken. When the OIDC provider redirects back to my app at https://localhost:44383/signin-oidc, I get a 404. I did some Googling and found this thread which suggests putting app.UseAuthentication() in Startup.cs. After doing that, I get a crash and an "Access violation" when adding the OIDC authentication scheme.

If you were able to see the OIDC provider's page and to be redirected back to the app then it means everything is OK.
The issue you've got is because you want to Authenticate the user using 3rd party identity provider, but current app is not able to Authenticate due to the wrong project template. Seems like that project with 'No Authencitation' initially. You need to add an 'Individual user accounts' authentication to the project
https://stackoverflow.com/questions/41471782/visual-studio-2017access-the-change-authentication-screen-on-a-already-created
or better to create a new project with 'Individual user accounts' authentication and implement the same in AccountController. There will be ExternalLoginCallback action in AccountController where you can get user info from the provider.

@HaoK - can you add your thoughts to this?

You can also take a look at https://github.com/aspnet/AspNetCore/pull/6363 which demonstrates how to provide a different set of options/schemes per tenant but the main thing to be aware of is that we don't really support using the built in providers (OIDC/Oauth/Google/Facebook) in a multi-tenant way, we left enough hooks for it to be possible if you replace large pieces of the stack, but the shared provider abstractions layer that we use to implement our providers (in what used to be the Security repo) was built in a way that did not consider multitenancy. So the example PR demonstrates how you can configure options generally in a multi-tenant way, but it isn't going to address any of the specific issues that come up in actual providers like OIDC

Per my comments here: Grand Auth Redesign this doesn't work properly and I gave examples.

We need either a complex example that actually works and doesn't throw errors or this needs to be fixed so that it work right and you can actually use the same extension methods that you use on startup dynamically in code.

I agree with John, the dynamically added scheme for OIDC independent of multi-tenant does not work in the sample provided (even for a single scheme at runtime). I'm working with IdentityServer4 which will resolve the idp through the arc_values if present. When removing the (working) AddOpenIdConnect from Startup and into a LoginController using the IAuthenicationSchemeProvider and even the IPostConfigureOptions an unhandled exception is chucked as the Options.ClientId is unable to be resolved. The IOptionsMonitorCache successfully adds the OpenIdConnectOptions with the named scheme.

Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectOptions.Validate()
Microsoft.AspNetCore.Authentication.RemoteAuthenticationOptions.Validate(string scheme)
Microsoft.AspNetCore.Authentication.AuthenticationHandler.InitializeAsync(AuthenticationScheme scheme, HttpContext context)
Microsoft.AspNetCore.Authentication.AuthenticationHandlerProvider.GetHandlerAsync(HttpContext context, string authenticationScheme)
IdentityServer4.Hosting.FederatedSignOut.FederatedSignoutAuthenticationHandlerProvider.GetHandlerAsync(HttpContext context, string authenticationScheme) in FederatedSignoutAuthenticationHandlerProvider.cs

Faced same issue today. As it turns out, in the controller where I dynamically added new scheme, IPostConfigureOptions was resolved to EnsureSignInScheme>. As a work around, created a new instance of OpenIdConnectPostConfigureOptions and called .PostConfigure(options) explicitly

    public AuthenticationController(
        IAuthenticationSchemeProvider schemeProvider, 
        IOptionsMonitorCache<OpenIdConnectOptions> optionsCache,
        IDataProtectionProvider dataProtection)
    {
        _oidcSchemeProvider = schemeProvider;
        _oidcOptionsCache = optionsCache;
        _oidcPostConfigureOptions = new OpenIdConnectPostConfigureOptions(dataProtection);
    }

    public async Task<IActionResult> AddExternalIdentityProvider()
    {
        _oidcPostConfigureOptions.PostConfigure(scheme, oidcOptions);
        _oidcOptionsCache.TryAdd(scheme, oidcOptions);
    }

@HaoK, It'll be helpful to have this issue looked into. Appreciate this design. With earlier version, it was far more difficult to achieve this.

Was this page helpful?
0 / 5 - 0 ratings