London Dev Meetup Rescheduled! Due to unavoidable reasons, the event has been moved to 21st May. Speakers remain the same—any changes will be communicated. Seats are limited—register here to secure your spot!

Mixed-mode authentication - Identity Server/CMS Identity

Vote:
0
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:
0

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:
0

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:
0

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:
0

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:
0

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.