Integration with PiranhaCMS on ASP.NET Core 2.1

I’ve got a PiranhaCMS site in ASP.NET Core 2.1 that I’m trying to add ecommerce capability to and use Auth0 for customer authentication. I followed (near as I can tell) the QuickStart (Auth0 ASP.NET Core MVC SDK Quickstarts: Login) guide.

When I click my Login link, I get the Auth0 login page, I can log in and I get sent back to my app/site, but I get dumped at the blog home page with User.Identity not populated.

My guess is that one of the PiranhaCMS routes is grabbing what should be routed to Auth0 handling. But, I’m not sure how to figure out what’s not working and fix it. I had already disabled the Piranha StartPages module because it wouldn’t let me route “/” to my own controller action.

What I find myself reaching for is something that would let me see how an incoming request got routed through various components to see what’s grabbing it.

So not familiar with PiranhaCMS, but do you have any authentication sample code you’re using and any logs you can share?

Here’s my entire Startup.cs, which, based on the quickstart, is where pretty much everything happens. I’m relatively new to PiranhaCMS myself and am using this as my project to learn ASP.NET Core. I’ve gotten Auth0 working with non-core ASP.NET in the past several times, so I figured I’d delegate that and focus on the other stuff.

As for logging, I think that’s what I’m trying to figure out if I can get it to do more of so I can see what’s going on. This kind of opaque failure is way more frustrating than actual errors. It just appears to work, but doesn’t.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using ObscurityWorks.Site.Models.EcomData;
using Piranha;
using Piranha.AspNetCore.Identity.SQLite;
using Piranha.AspNetCore.Identity.SQLServer;
using Piranha.ImageSharp;
using Piranha.Local;
using site.Models;

namespace site
{
    public class Startup
    {
        public Startup(IConfiguration configuration, IHostingEnvironment hostingEnvironment)
        {
            Configuration = configuration;
            HostingEnvironment = hostingEnvironment;
        }

        public IConfiguration Configuration { get; }

        public IHostingEnvironment HostingEnvironment { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            //var connectionString = "Data Source=localhost;Initial Catalog=ObscurityWorks;Integrated Security=True;Pooling=False";
            var connectionString = Configuration["Data:CMSConnectionString"];

            services.AddMvc(config =>
            {
                config.ModelBinderProviders.Insert(0, new Piranha.Manager.Binders.AbstractModelBinderProvider());
            });

            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            // Add authentication services
            services.AddAuthentication(options =>
                {
                    options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                    options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                    options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                })
                .AddCookie()
                .AddOpenIdConnect("Auth0", options =>
                {
                    // Set the authority to your Auth0 domain
                    options.Authority = $"https://{Configuration["Auth0:Domain"]}";

                    // Configure the Auth0 Client ID and Client Secret
                    options.ClientId = Configuration["Auth0:ClientId"];
                    options.ClientSecret = Configuration["Auth0:ClientSecret"];

                    // Set response type to code
                    options.ResponseType = "code";

                    // Configure the scope
                    options.Scope.Clear();
                    options.Scope.Add("openid");

                    // Set the callback path, so Auth0 will call back to http://localhost:5000/signin-auth0
                    // Also ensure that you have added the URL as an Allowed Callback URL in your Auth0 dashboard
                    options.CallbackPath = new PathString("/account/signin");

                    // Configure the Claims Issuer to be Auth0
                    options.ClaimsIssuer = "Auth0";

                    options.Events = new OpenIdConnectEvents
                    {
                        // handle the logout redirection
                        OnRedirectToIdentityProviderForSignOut = (context) =>
                        {
                            var logoutUri =
                                $"https://{Configuration["Auth0:Domain"]}/v2/logout?client_id={Configuration["Auth0:ClientId"]}";

                            var postLogoutUri = context.Properties.RedirectUri;
                            if (!string.IsNullOrEmpty(postLogoutUri))
                            {
                                if (postLogoutUri.StartsWith("/"))
                                {
                                    // transform to absolute
                                    var request = context.Request;
                                    postLogoutUri = request.Scheme + "://" + request.Host + request.PathBase +
                                                    postLogoutUri;
                                }

                                logoutUri += $"&returnTo={Uri.EscapeDataString(postLogoutUri)}";
                            }

                            context.Response.Redirect(logoutUri);
                            context.HandleResponse();

                            return Task.CompletedTask;
                        },
                        OnRedirectToIdentityProvider = context =>
                        {
                            context.ProtocolMessage.SetParameter("audience", "https://pragmapool.auth0.com/api/v2/");

                            return Task.FromResult(0);
                        }
                    };
                });


            services.AddPiranhaApplication();
            services.AddPiranhaFileStorage();
            services.AddPiranhaImageSharp();


            if (HostingEnvironment.IsDevelopment())
            {
                services.AddPiranhaEF(options => options.UseSqlite(connectionString));
                services.AddPiranhaIdentityWithSeed<IdentitySQLiteDb>(options => options.UseSqlite(connectionString));
            }
            else
            {
                services.AddPiranhaEF(options => options.UseSqlServer(connectionString));
                services.AddPiranhaIdentityWithSeed<IdentitySQLServerDb>(options =>
                    options.UseSqlServer(connectionString));
            }

            services.AddScoped<IEcommerceContext, EcommerceContext>(provider =>
                new EcommerceContext(HostingEnvironment.EnvironmentName, Configuration));

            services.AddPiranhaManager();
            services.AddSingleton<ICache, Piranha.Cache.MemCache>();

            return services.BuildServiceProvider();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider services)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            // Initialize Piranha
            var api = services.GetService<IApi>();
            App.Init(api);

            // Configure cache level
            App.CacheLevel = Piranha.Cache.CacheLevel.None;

            // Build content types
            var pageTypeBuilder = new Piranha.AttributeBuilder.PageTypeBuilder(api)
                .AddType(typeof(Models.BlogArchive))
                .AddType(typeof(Models.StandardPage))
                .AddType(typeof(Models.StartPage));
            pageTypeBuilder.Build()
                .DeleteOrphans();
            var postTypeBuilder = new Piranha.AttributeBuilder.PostTypeBuilder(api)
                .AddType(typeof(Models.BlogPost));
            postTypeBuilder.Build()
                .DeleteOrphans();

            // Register middleware
            app.UseStaticFiles();
            app.UseAuthentication();

            app.UsePiranhaApplication();
            app.UsePiranhaAliases();
            app.UsePiranhaPages();
            app.UsePiranhaPosts();
            app.UsePiranhaArchives();
            app.UsePiranhaSitemap();

            app.UsePiranhaManager();
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "Root",
                    template: "/",
                    defaults: new {controller = "SitePage", action = "Index"}
                );
                routes.MapRoute(
                    name: "Signin",
                    template: "/account/signin",
                    defaults: new {controller="Account", action="SignIn"}
                );
                routes.MapRoute(
                    name: "SitePage",
                    template: "/SitePage/{action}/{id?}",
                    defaults: new {controller = "SitePage", action = "Index"}
                );
                routes.MapRoute(
                    name: "Account",
                    template: "/Account/{action}/{id?}",
                    defaults: new { controller = "Account", action = "Index" }
                );
                routes.MapRoute(
                    name: "Cart",
                    template: "cart/{action=index}/{id?}",
                    defaults: new {controller = "Cart"});

                routes.MapRoute(name: "areaRoute",
                    template: "{area:exists}/{controller}/{action}/{id?}",
                    defaults: new {controller = "Home", action = "Index"});

                routes.MapRoute(
                    name: "default",
                    template: "{controller=home}/{action=index}/{id?}");
            });
        }
    }
}

That file has become a bit of a mess as the various tutorials have had me add things. I’ll definitely be re-factoring it once it works.

Do you see any logs in the console?

I had been running in IIS Express, so I switched over to console. This is the relevant bit from the output:

info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 POST http://localhost:49790/signin-auth0 application/x-www-form-urlencoded 332
info: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler[10]
      AuthenticationScheme: Identity.External signed in.
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 206.9253ms 302
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 GET http://localhost:49790/
info: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler[7]
      Identity.Application was not authenticated. Failure message: Ticket expired

From there, it moves on to the CMS path stuff that leads to the blog URL being loaded.

Hey there!

Sorry for such delay in response! We’re doing our best in providing the best developer support experience out there, but sometimes the number of incoming questions is just too big for our bandwidth. Sorry for such inconvenience!

Do you still require further assistance from us?