Mixed-mode authentication - Identity Server/CMS Identity

Vote:
 
I am struggling with getting mixed-mode authentication working in CMS12, we have an Identity Server that is accessed via OpenIdConnect for members to login via the website /login route, and we also need to retain the normal CMS ASP.Net Identity for logging into the CMS. I can get the members /login to work fine, but the login via the CMS then does not work, I see the login page but upon login I am just returned to the login page with no error messages shown. 

The code I have in my startup class as a middleware extension is below:
namespace xxx.Web.Infrastructure.ServiceExtensions;

using System.Text;

using xxx.Features.Common.Configuration;
using EPiServer.Cms.UI.AspNetIdentity;

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

public static class UserAuthenticationServiceExtensions
{
    private const string AuthenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme;

    private const string ChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; 

    public static IServiceCollection AddUserAuthentication(
        this IServiceCollection services, 
        IWebHostEnvironment environment, 
        IConfiguration configuration)
    {
        services.AddCmsAspNetIdentity<ApplicationUser>();
        services.AddIdentityServer(configuration);

        return services;
    }

    /// <summary>
    /// Sets up authentication based on Identity Server 4 using Open ID Connect
    /// </summary>
    /// <param name="services"></param>
    /// <param name="configuration"></param>
    /// <returns></returns>
    public static void AddIdentityServer(this IServiceCollection services, IConfiguration configuration)
    {
        var identityServerSettings = configuration.GetSection(nameof(IdentityServerSettings)).Get<IdentityServerSettings>();
        var authority = identityServerSettings?.Authority ?? string.Empty;
        _ = bool.TryParse(identityServerSettings?.RequireHttpsMetadata ?? "true", out bool requireHttpsMetadata);
        var clientId = identityServerSettings?.ClientId ?? string.Empty;
        var clientSecret = identityServerSettings?.ClientSecret?? string.Empty;

        services.AddAuthentication(options =>
                {
                    options.DefaultAuthenticateScheme = AuthenticationScheme;
                    options.DefaultChallengeScheme = "policy-scheme";
                })
                .AddCookie(AuthenticationScheme, options =>
                {
                    // Defines a path to redirect the user to if they don't have access to a page.
                    // This page should return a 200 response so as to not cause authentication loops.
                    options.AccessDeniedPath = new PathString("/no-access");
                })
                .AddOpenIdConnect(ChallengeScheme, options =>
                {
                    options.SignInScheme = AuthenticationScheme;
                    options.SignOutScheme = AuthenticationScheme;
                    options.ResponseType = OpenIdConnectResponseType.Code;
                    options.CallbackPath = "/signin-oidc";
                    options.UsePkce = false;

                    options.Authority = authority;
                    options.RequireHttpsMetadata = requireHttpsMetadata;
                    options.ClientId = clientId;
                    options.ClientSecret = clientSecret;

                    options.Scope.Clear();
                    options.Scope.Add(OpenIdConnectScope.OpenId);
                    options.Scope.Add("xxx");
                    options.MapInboundClaims = false;

                    options.Events.OnRedirectToIdentityProvider = context =>
                    {
                        // Prevent redirect loop
                        if (context.Response.StatusCode == 401)
                        {
                            context.HandleResponse();
                        }

                        if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
                        {
                            var idTokenHint = context.HttpContext.User.FindFirst("id_token");
                            if (idTokenHint != null)
                            {
                                context.ProtocolMessage.IdTokenHint = idTokenHint.Value;
                            }

                        }

                        return Task.CompletedTask;
                    };

                    options.Events.OnAuthenticationFailed = async context =>
                    {
                        context.HandleResponse();

                        await context.Response.BodyWriter.WriteAsync(Encoding.ASCII.GetBytes(context.Exception.Message));
                    };
                })
                .AddPolicyScheme("policy-scheme", null, options =>
                {
                    options.ForwardDefaultSelector = ctx =>
                    {
                        if (ctx.Request.Path.StartsWithSegments("/episerver", StringComparison.OrdinalIgnoreCase))
                        {
                            return "Identity.Application";
                        }

                        return OpenIdConnectDefaults.AuthenticationScheme;
                    };
                });
    }
}
The Authentication controller for the front-end users has been decorated with the following attribute:

[Authorize(AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme)]

I have referenced the following Optimizely page https://docs.developers.optimizely.com/content-cloud/v12.0.0-content-cloud/docs/mixed-mode-authentication but after trying various ways I still cannot get it to allow me to login via the /login method as well as being able to login via the /episerver/cms method.

Does anyone have any experience of successfully implementing mixed-mode authentication in CMS12, or anyone who can provide any pointers/help.

Thanks in advance.
#295675
Jan 31, 2023 17:14
Vote:
 

Hi Graham

Your code seems fine at first glance.

One thing to call out is that you should use "policy-scheme" for your default schemes as it is this custom policy scheme which then controls which authentication scheme to use

 {
                    options.DefaultScheme = "policy-scheme";
                    options.DefaultChallengeScheme = "policy-scheme";
 })
#295873
Feb 03, 2023 2:32
Vote:
 

Hi Ron,

Many thanks for your reply, I have updated the default scheme as shown above but the issue still persists. The functionality to login via the FE via Identity Server works fine until you add the:

services.AddCmsAspNetIdentity<ApplicationUser>();

line and then it stops working, you can login via Identity Server fine but the user session/cookie is not retained. You can however login to the CMS no problem with it's own session/cookie being retained with no problem.

Thanks,

Graham

#296115
Feb 07, 2023 14:27
Vote:
 

Did you find a solution?

I'm facing the same problem. Coming to the login page and loging in but then stay on the login page instead of redirecting to the actual page.

#310900
Oct 16, 2023 8:52
Vote:
 

hi, I did find a solution in the end, and I wrote a blog post about it which can be viewed here https://world.optimizely.com/blogs/allthingsopti/dates/2023/2/a-day-in-the-life-of-an-optimizely-developer---implementing-identity-server-4-and-asp-net-identity/ hope that helps?

#310901
Oct 16, 2023 8:59
Vote:
 

I've read your post, very nice! 

Unfortunatly it doesn't work for me although I don't have exactly the same setup.

I need to have azuread and aspnet identity. I followed your guide, my problem is that loging in with azuread into /episerver admin and edit is working well. Then I want some pages to have read access rights for a specific user in the db. So I set the access right in the tree in the admin.

Then when I try to access this page I get redirected to the login so far so good.

I then log in with the user credential, it worked BUT the redirect is not to the page but back to the login page?

#310902
Oct 16, 2023 9:06
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.