Umbraco 17 - Shared preview

In Umbraco v13, we override the preview controller for the possibility to share the preview and auto-login using a minimal account, and auto login if you have a specific IP address.

Now we need to restore it in Umbraco 17, but the current preview controller is not used anymore. Does somebody have an idea how to restore this? Or is there a package available for Umbraco 17 that can do this?

not sure if this will help

How to Bypass Umbraco Cloud Basic Auth for Preview URLs | CodeShare

Hi @JohanReitsma

I’m sure you could create your own controller that allows you to login validating against IP etc, then setting the UMB_PREVIEW cookie which switches on the preview mode.

Here’s AI’s take on it so you would need to do something like this:

For the IP/auto-login + share-link scenarios I’d register a plain minimal ASP.NET Core endpoint at something like /share-preview/{token} (and reserve it in Umbraco:CMS:Global:ReservedPaths). In that endpoint:

  1. Validate the token (or check HttpContext.Connection.RemoteIpAddress against your allow list).

  2. Resolve your minimal back office user via IBackOfficeUserManager and build a principal with IUserClaimsPrincipalFactory, then sign them in on the back office cookie scheme (Umbraco.Cms.Core.Constants.Security.BackOfficeAuthenticationTypeUmbraco.SignInAsync(...) extension or HttpContext.SignInAsync(scheme, principal) directly).

  3. Set the UMB_PREVIEW cookie (still the cookie that flips UmbracoContext.InPreviewMode to true once an authenticated back office principal is on the request — same approach others have used in headless setups: generate a token, redirect with the token, validate it in middleware, set a back office user as the principal with the UMB_PREVIEW cookie, and InPreviewMode becomes true). Umbraco

  4. Redirect to the actual page URL (not /umbraco/preview?id=... — that’s the back office iframe shell now, which is what tripped people up in v14/15).

Hope that helps

Justin

Hi @JohanReitsma ,

I did a quick Umbraco 17 setup to see how this can be done. I was able to do few things which might help you in some bits, Please adjust the code as per your needs:

This is the controller which we need. The code is getting the GUID of content node, compares the visitor’s IP again the config which we have. If don’t match, it blocks them. It hits the Umbraco cache using GetById(true, key). The true parameter is the trick—it forces Umbraco to ignore the live site and grab the unpublished draft. It wraps that draft data into a standard ContentModel and hands it off to the Home.cshtml. This makes the draft render exactly like the real page without breaking the views. :

using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Web;

namespace MyUmbracoProjectTest1.Controllers
{
    [Route("share-preview")]
    public class SharePreviewController : Controller
    {
        private readonly IUmbracoContextAccessor _umbracoContextAccessor;
        private readonly IConfiguration _configuration;

        public SharePreviewController(IUmbracoContextAccessor umbracoContextAccessor, IConfiguration configuration)
        {
            _umbracoContextAccessor = umbracoContextAccessor;
            _configuration = configuration;
        }

        // Changed to expect a GUID in the URL
        [HttpGet("{key:guid}")]
        public IActionResult Index(Guid key)
        {
            var userIp = HttpContext.Connection.RemoteIpAddress?.ToString();
            var allowedIp = _configuration["SharePreview:AllowedIp"];

            if (string.IsNullOrEmpty(allowedIp) || userIp != allowedIp)
            {
                return Unauthorized("IP address not authorized for preview.");
            }

            if (_umbracoContextAccessor.TryGetUmbracoContext(out var context))
            {
                // Pass 'true' (for preview) and the GUID Key
                var draftContent = context.Content?.GetById(true, key);

                if (draftContent != null && draftContent.TemplateId > 0)
                {
                    // Wrap the IPublishedContent so ModelsBuilder views don't break
                    return View(draftContent.GetTemplateAlias(), new ContentModel(draftContent));
                }
            }

            return NotFound("Content not found or no template assigned.");
        }
    }
}

This is the view code for Home doc type:

@using Umbraco.Cms.Web.Common.PublishedModels;
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<ContentModels.Home>
@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels;
@{
	Layout = null;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>Preview Test - @Model.Name</title>
</head>
<body style="font-family: sans-serif; padding: 40px;">

    <h1>Node Name: @Model.Name</h1>
    
    <div style="border: 2px dashed red; padding: 20px; margin-top: 20px;">
        <h3 style="margin-top: 0;">Output from 'test' property:</h3>
        
        @* This renders your Rich Text Editor content safely *@
        @Html.Raw(Model.Value("test"))
        
    </div>

</body>
</html>

And a small block on the appsettings to allow the IPs:

{
  "$schema": "appsettings-schema.json",
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "Microsoft.Hosting.Lifetime": "Information",
        "System": "Warning"
      }
    }
  },
  "Umbraco": {
    "CMS": {
      "Global": {
        "Id": "12df46b9-8ad9-4f36-b3ee-d157fe3c1a12"
      },
      "Content": {
        "AllowEditInvariantFromNonDefault": true,
        "ContentVersionCleanupPolicy": {
          "EnableCleanup": true
        }
      },
      "Unattended": {
        "UpgradeUnattended": true
      },
      "Security": {
        "AllowConcurrentLogins": false
      },
      "Imaging": {
        "HMACSecretKey": "iDj1Z7dWWqQv9wv0poMFOq5dABDd6zCLUgZE/TMdkO4ReCwEBNUHpVH9TADozMv87kDQxanQvAy646Hpir2Tbg=="
      }
    }
  },
  "ConnectionStrings": {
    "umbracoDbDSN": "Data Source=|DataDirectory|/Umbraco.sqlite.db;Cache=Shared;Foreign Keys=True;Pooling=True",
    "umbracoDbDSN_ProviderName": "Microsoft.Data.Sqlite"
  },
  "SharePreview": {
    "AllowedIp": "::1"
  }
}

Created a small doc type with just a RTE:

Added this first and do a save and publish:

Which gave me this. This is the published version:

After that added this and only saved it (No publish):

Now tried the SharePreview to see the draft preview on the incognito mode. Got this as my IP address is not in the appsettings:

Then added my IP and tried again and got this:

Hope it helps!