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.
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.
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.