Optimizely 12 - Separate admin login when using OpenIdConnect

Vote:
 
I want to use OpenID Connect for all end users and use standard Episerver login for all editors.
Is there any way to accomplish this in Optimizely 12?

This is my current setup. Now everyone is moving towards the external provider.
What I would like to achieve is that the url "/episerver/cms" uses the standard login.
services
            .AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
            })
            .AddCookie()
            .AddOpenIdConnect(
                options =>
                {
                    options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                    options.ClientId = "####";
                    options.ClientSecret = "####";
                    options.Authority = "#####";
                    options.CallbackPath = "/callback";
                    options.ResponseType = "code";
                    options.GetClaimsFromUserInfoEndpoint = true;

                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuer = false,
                        RoleClaimType = ClaimTypes.Role,
                        NameClaimType = ClaimTypes.Email
                    };

                    options.Events.OnAuthenticationFailed = ctx =>
                    {
                        ctx.HandleResponse();
                        ctx.Response.BodyWriter.WriteAsync(Encoding.ASCII.GetBytes(ctx.Exception.Message));
                        return Task.FromResult(0);
                    };

                    options.Events.OnTokenValidated = (ctx) =>
                    {
                        var redirectUri = new Uri(ctx.Properties.RedirectUri, UriKind.RelativeOrAbsolute);
                        if (redirectUri.IsAbsoluteUri)
                        {
                            ctx.Properties.RedirectUri = redirectUri.PathAndQuery;
                        }

                        //Sync user and the roles to EPiServer in the background
                        //ServiceLocator.Current.GetInstance<ISynchronizingUserService>().SynchronizeAsync(ctx.Principal.Identity as ClaimsIdentity);
                        
                        return Task.FromResult(0);
                    };
                });
#271143
Edited, Feb 04, 2022 11:30
Vote:
 

Hello :-) Have you found the solution? I'm struggling with the same issue, the events are not triggered actually!

#275906
Mar 08, 2022 13:12
Vote:
 

I have got this working with Identity Server for the website user's logins and the standard ASPNet Identity for CMS logins, see my extension class below:

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

    private const string ChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;

    /// <summary>
    /// Sets up authentication based on one of the following schemes:
    /// Optimizely CMS Identities for the CMS login.
    /// Identity Server using Open ID connect for front-end user login.
    /// </summary>
    /// <param name="services"></param>
    /// <param name="environment"></param>
    /// <param name="configuration"></param>
    /// <returns></returns>
    public static IServiceCollection AddUserAuthentication(
        this IServiceCollection services,
        IWebHostEnvironment environment,
        IConfiguration configuration)
    {
        services.AddIdentityServer(configuration);
        services.AddCmsAspNetIdentity<ApplicationUser>();

        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.DefaultScheme = 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");
                })
                .AddCookie(ChallengeScheme)
                .AddOpenIdConnect(ConfigConstants.IdentityServerAuthenticationScheme, options =>
                {
                    options.SignInScheme = ChallengeScheme;
                    options.SignOutScheme = ChallengeScheme;
                    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.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) ||
                        ctx.Request.Path.StartsWithSegments("/util", StringComparison.OrdinalIgnoreCase))
                       {
                           return IdentityConstants.ApplicationScheme;
                       }

                       return ConfigConstants.IdentityServerAuthenticationScheme;
                   };
                });
    }

You then need to ensure you decorate your page controllers with the following attributes:

[Authorize(AuthenticationSchemes = AuthSchemes, Roles = AuthRoles)]
[AllowAnonymous]

with the constants for the above being as follows:

    private const string AuthSchemes = "IdentityServer,Identity.Application";
    private const string AuthRoles = "CmsAdmins,CmsEditors,Everyone";

You should be able to adapt the above to your needs, however the important parts for you are ensuring you have two separate cookies created, and also to ensure you create a policy scheme which allows you to route to the correct authentication scheme.

Hope this helps a bit?

#299510
Apr 04, 2023 13:14
Vote:
 

For me requiremens we slightly different:

  1. Login to Optimizely CMS with Microsoft Entra ID / OpenIdConnect if path is /episerver.
  2. Login to Optimizely CMS with ASPNet Identity if path is /util/login, then access /episerver without addittional authetication with Microsoft Entra ID / OpenIdConnect.

I achieved this by following Graham's example with a couple of changes:

services.AddAuthentication(options =>
{
    options.DefaultScheme = "policy-scheme";
    options.DefaultAuthenticateScheme = "policy-scheme";
    options.DefaultChallengeScheme = "policy-scheme";
})
.AddCookie()
.AddOpenIdConnect(
        options =>
        {
             options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
             options.ClientId = "####";
             options.ClientSecret = "####";
             options.Authority = "#####";
             options.CallbackPath = "/callback";
             options.ResponseType = "code";
             options.GetClaimsFromUserInfoEndpoint = true;

             options.TokenValidationParameters = new TokenValidationParameters
             {
                 ValidateIssuer = false,
                 RoleClaimType = ClaimTypes.Role,
                 NameClaimType = ClaimTypes.Email
             };

             options.Events.OnAuthenticationFailed = ctx =>
             {
                  ctx.HandleResponse();
                  ctx.Response.BodyWriter.WriteAsync(Encoding.ASCII.GetBytes(ctx.Exception.Message));
                  return Task.FromResult(0);
             };

             options.Events.OnTokenValidated = (ctx) =>
             {
                 var redirectUri = new Uri(ctx.Properties.RedirectUri, UriKind.RelativeOrAbsolute);
                 if (redirectUri.IsAbsoluteUri)
                 {
                     ctx.Properties.RedirectUri = redirectUri.PathAndQuery;
                 }

                 //Sync user and the roles to EPiServer in the background
                 //ServiceLocator.Current.GetInstance<ISynchronizingUserService>().SynchronizeAsync(ctx.Principal.Identity as ClaimsIdentity);
                    
                 return Task.FromResult(0);
             };
}).AddPolicyScheme("policy-scheme", null, options =>
{
    options.ForwardDefaultSelector = ctx =>
    {
        if (ctx.Request.Cookies.ContainsKey(".AspNetCore." + IdentityConstants.ApplicationScheme) ||
            ctx.Request.Path.StartsWithSegments("/util", StringComparison.OrdinalIgnoreCase))
        {
            return IdentityConstants.ApplicationScheme;
        }

        return "azure";
    };
});
#321809
May 09, 2024 23:55
Vote:
 

Hi Levon

According to your requirement, you don't have to go with this complex set up with forward selector. By default, /util/login will use Asp.net Identity to authenticate. /episerver will use the scheme you set in the DefaultChallengeScheme.  In other words, you just set DefaultChallengeScheme to "azure", everything will work as you expected. 

#321810
May 10, 2024 3:38
Vote:
 

Hi Vincent,

With DefaultChallengeScheme set to "azure", when I logged in to /util/login with Asp.net Identity, CMS app did not accept generated cookie, so I got into infinite loop of logging into Asp.net Identity. Same thing happened when DefaultChallengeScheme was set to IdentityConstants.ApplicationScheme, when I logged in into Microsoft Entra ID via /episerver I got into infinite loop of logging in.  I need to be able to login to /episerver when Microsoft Entra ID is not working / offline. So I login to /util/login with Asp.net Identity and then can access /episerver without additional authentication.

#321812
Edited, May 10, 2024 4:04
* 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.