asp.net-coreopenid

ASPNET CORE InvalidOperationException: Cannot redirect to the authorization endpoint, the configuration may be missing or invalid


I'm getting the error InvalidOperationException: Cannot redirect to the authorization endpoint, the configuration may be missing or invalid.

Prior to this I had the error that says something like "authority, metaaddress, configuration or configuration manager is missing". I feel this is important. So, what I had:

        /// <summary>
        /// Configures OpenID Connect authentication.
        /// </summary>
        /// <param name="builder">The <see cref="IPersonalIdentityServerBuilder"/> object.</param>
        /// <returns>The <see cref="IPersonalIdentityServerBuilder"/>.</returns>
        public static IPersonalIdentityServerBuilder AddOpenIdConnectAuthentication(this IPersonalIdentityServerBuilder builder)
        {
                IServiceCollection _services = builder?.Services ?? throw new ArgumentNullException(nameof(builder));

            _services
                .AddAuthentication(opt => opt.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme)
                .AddOpenIdConnect();

            _services.AddSingleton<IPostConfigureOptions<OpenIdConnectOptions>, ConfigureOpenIdConnectOptions>();

            return builder;
        }

I then had the following class that configures the options

/// <summary>
/// Configuration class for the <see cref="OpenIdConnectOptions"/>.
/// </summary>
internal class ConfigureOpenIdConnectOptions : 
    IPostConfigureOptions<OpenIdConnectOptions>
{
    /// <summary>
    /// My personal OpenId options.
    /// </summary>
    private readonly IOptions<PersonalIentityServerOpenIdOptions> _openIdOptions;

    /// <summary>
    /// The class that has the events for OpenId authentication.
    /// </summary>
    private readonly OpenIdNotificationEventHandler _eventHandler;

    /// <summary>
    /// Initializes a new instance of the <see cref="ConfigureOpenIdConnectOptions"/> class.
    /// </summary>
    /// <param name="openIdOptions">The personal OpenId options.</param>
    /// <param name="eventHandler">The handler for OpenId authentication events.</param>
    public ConfigureOpenIdConnectOptions(
        IOptions<PersonalIdentityServerOpenIdOptions> openIdOptions,
        OpenIdNotificationEventHandler eventHandler)
    {
        this._openIdOptions = openIdOptions ?? throw new ArgumentNullException(nameof(openIdOptions));
        this._eventHandler = eventHandler ?? throw new ArgumentNullException(nameof(eventHandler));
    }

    /// <summary>
    /// Configures the options.
    /// </summary>
    /// <param name="name">The name.</param>
    /// <param name="options">The options to configure.</param>
    public void PostConfigure(string name, OpenIdConnectOptions options)
    {
        PersonalIdentityServerOpenIdOptions _opt = this._openIdOptions.Value;

        options.Authority = _opt.Authority;
        options.ClientId = _opt.ClientId;
        options.ClientSecret = _opt.Secret;
        options.MetadataAddress = "/" + ProtocolPath.Discovery;
        options.ProtocolValidator.RequireNonce = true;
        options.ResponseType = OpenIdConnectResponseType.Code;
        options.UsePkce = true;
        options.TokenValidationParameters = new TokenValidationParameters
        {
            NameClaimType = JwtClaimTypes.Name,
            RoleClaimType = JwtClaimTypes.Role,
        };

        // Keeps id_token smaller
        options.GetClaimsFromUserInfoEndpoint = true;

        // The callback paths require a relative URL. This was a change that Microsoft made in the .NET Core version. We, however,
        // support an absolute URL. Attempting to set the callback paths to an absolute URL will cause an exception to be thrown.
        // Therefore, we will now support either. If it's an absolute URL then it gets set in the RedirectToIdentityProvider
        // event. If it's a relative URL, we'll set it here.
        if (!_opt.SetLowLevelRedirectUri) { options.CallbackPath = _opt.RedirectUri; }
        if (!_opt.SetLowLevelPostLogoutRedirectUri) { options.SignedOutCallbackPath = _opt.PostLogoutRedirectUri; }

        _opt.Scopes.ForEach(s => options.Scope.Add(s));

        options.Events = new OpenIdConnectEvents
        {
            OnAuthorizationCodeReceived = this._eventHandler.AuthorizationCodeRecieved,
            OnTokenResponseReceived = this._eventHandler.TokenResponseReceived,
            OnTokenValidated = this._eventHandler.TokenValidated,
            OnRedirectToIdentityProvider = this._eventHandler.RedirectToIdentityProvider,
        };
    }
}

I ended up, after much decompiling and Google searching, added the following line to the AddOpenIdConnectAuthentication method defined above. This is Microsofts configuration for the open ID settings.

            _services.AddSingleton<IPostConfigureOptions<OpenIdConnectOptions>, OpenIdConnectPostConfigureOptions>();

This resolved that issue but now I have the one in the title. From looking at the source, it looks like it's because the IssuerAddress is empty. For the life me I can't understand why. Furthermore, I don't understand why I was required to add the configuration for Microsofts own class to get it to work.

I know that I can get around it somehow and will likely find a workaround, but I just don't think it should be this hard.

If anyone has any thoughts, they would be most welcome.

Update

I wanted to expand on some of the things that I found and ended up changing due to the time I spent on this. I want to give a special thank you to https://stackoverflow.com/users/68490/tore-nestenius below.

I ended up changing from using IPostConfigure<OpenIdConnectOptions> to IConfigure<OpenIdConnectOptions>. When doing this, it seems to go through the Configure(name, options) for IConfigureNamedOptions<OpenIdConnectOptions> not the Configure(options) for IConfigureOptions<OpenIdConnectOptions>. Due to this, I implemented both. I don't know if you have to, because I didn't try all possible combinations, but doing so worked.

When using IPostConfigurationOptions<OpenIdConnectOptions>, you must add it to the service collection before the call to AddOpenIdConnect().

I also removed the line to manually register the Microsofts IPostConfigreOptions, this is not needed.


Solution

  • The metadata address should be an absolute address starting with https:// and not a relative URL as in your code:

       options.MetadataAddress = "/" + ProtocolPath.Discovery;
    

    then I am a bit confused why you need o add your own ConfigureOpenIdConnectOptions class? To configure the openid-connect I simply:

    services.AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    }).AddCookie(options =>
    {
        options.LoginPath = "/User/Login";
        options.LogoutPath = "/User/Logout";
        options.AccessDeniedPath = "/User/AccessDenied";
    }).AddMyTestOpenIdConnect(options =>
    {
        options.Authority = "https://localhost:6001";
        options.ClientId = "authcodeflowclient";
        options.ClientSecret = "mysecret";
        options.ResponseType = "code";
        ...
    });
    

    If the MetaData URL is on HTTP, you might need to set

    options.RequireHttpsMetadata = false;
    

    But if you set the Authority field, then I doubt you need to also set the MetaDataAddress.