Hello,
Although I looked in several places, I'm sure that I'm missing a simple piece of information, but at this moment I totally don't understand what am I missing.
First, I setup the commerce website without any issues and without any problems - everything is working fine. I even configured the Azure AD authentication for the administration interface according to the official available guides - everything is working.
Second, I used the Service API to retrieve content information and it's working without any issues:
https://localhost:5000/api/episerver/v3.0/content/18
Then, though I configured the commerce API to disable scope validation (like I did with the content delivery), it seems that I'm not able to call any commerce API URLs being unauthenticated:
https://localhost:5000/episerverapi/commerce/catalogs
This returns 401 Unauthorized:
{
"message": "User is not authorized for this request."
}
Now, the problem is that I try to authenticate the user first by obtaining the token using this URL:
https://localhost:5000/api/episerver/connect/token
And here is the issue that I have and cannot fix:
System.InvalidOperationException: No service for type 'EPiServer.OpenIDConnect.Internal.OpenIDConnectSignInManager' has been registered.
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
at EPiServer.OpenIDConnect.Controllers.Internal.AuthorizationController..ctor()
at lambda_method14(Closure, IServiceProvider, Object[])
at Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider.<>c__DisplayClass6_0.<CreateControllerFactory>g__CreateController|0(ControllerContext controllerContext)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
And this is how I configured the authentication of the Service API:
services.ConfigureContentApiOptions(options =>
{
options.EnablePreviewFeatures = true;
options.IncludeEmptyContentProperties = true;
options.FlattenPropertyModel = false;
options.IncludeMasterLanguage = false;
});
services.AddContentDeliveryApi(JwtBearerDefaults.AuthenticationScheme, options => {
options.SiteDefinitionApiEnabled = true;
})
.WithFriendlyUrl()
.WithSiteBasedCors();
services.AddCommerceApi<ApplicationUser>(OpenIDConnectOptionsDefaults.AuthenticationScheme, o =>
{
o.DisableScopeValidation = true;
});
// Content Definitions API
services.AddContentDefinitionsApi(options =>
{
// Accept anonymous calls
options.DisableScopeValidation = true;
});
// Content Management
services.AddContentManagementApi(JwtBearerDefaults.AuthenticationScheme, options =>
{
// Accept anonymous calls
options.DisableScopeValidation = true;
});
// Service API configuration
services.AddServiceApiAuthorization(JwtBearerDefaults.AuthenticationScheme);
And this is how I configured the OpenIdConnect:
services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "azure-cookie";
options.DefaultChallengeScheme = "azure";
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme,
options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = "https://login.microsoftonline.com/" +
this.configurationProvider["Authentication:AzureTenantId"] + "/v2.0",
ValidateIssuer = true,
ValidAudience = this.configurationProvider["Authentication:AzureClientId"],
ValidateAudience = true,
ValidateLifetime = true,
// IssuerSigningKeys = oidcConfig.SigningKeys,
ValidateIssuerSigningKey = true
};
})
.AddCookie("azure-cookie", options =>
{
options.Events.OnSignedIn = async ctx =>
{
if (ctx.Principal?.Identity is ClaimsIdentity claimsIdentity)
{
// Syncs user and roles so they are available to the CMS
var synchronizingUserService = ctx
.HttpContext
.RequestServices
.GetRequiredService<ISynchronizingUserService>();
await synchronizingUserService.SynchronizeAsync(claimsIdentity);
}
};
})
.AddOpenIdConnect("azure", options =>
{
options.SignInScheme = "azure-cookie";
options.SignOutScheme = "azure-cookie";
options.ResponseType = OpenIdConnectResponseType.Code;
options.CallbackPath = "/signin-oidc";
options.ClientSecret = this.configurationProvider["Authentication:AzureAppSecret"];
options.UsePkce = true;
// If Azure AD is register for multi-tenant
//options.Authority = "https://login.microsoftonline.com/" + "common" + "/v2.0";
options.Authority = "https://login.microsoftonline.com/" +
this.configurationProvider["Authentication:AzureTenantId"] + "/v2.0";
options.ClientId = this.configurationProvider["Authentication:AzureClientId"];
options.Scope.Clear();
options.Scope.Add(OpenIdConnectScope.OpenIdProfile);
options.Scope.Add(OpenIdConnectScope.OfflineAccess);
options.Scope.Add(OpenIdConnectScope.Email);
options.MapInboundClaims = false;
options.TokenValidationParameters = new TokenValidationParameters
{
RoleClaimType = "roles",
NameClaimType = "preferred_username",
ValidateIssuer = false
};
options.Events.OnRedirectToIdentityProvider = ctx =>
{
// Prevent redirect loop
if (ctx.Response.StatusCode == 401)
{
ctx.HandleResponse();
}
return Task.CompletedTask;
};
options.Events.OnAuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.BodyWriter.WriteAsync(Encoding.ASCII.GetBytes(context.Exception.Message));
return Task.CompletedTask;
};
});
Can you please help me a bit understanding what am I missing?
Thank you very much.