IFRame in back office dashboard

I’m migrating a dashboard to V17. The dashboard has an iFrame, that calls a custom controller endpoint, which has the attribute for backoffice authentication, like this:

[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)]

In the dashboard, the iframe is loaded in as part of the template, like so:

this.attachShadow({ mode: "open" }); this.shadowRoot.appendChild(template.content.cloneNode(true));

Where template is the HTML string containing the iframe. Unfortunately, the iframe shows as unauthorized, and I get the same if I try and access the route in the iframe directly. If I remove the authorisation attribute it works like it used to.

Does anyone know if this is still a viable way of doing this?

It has to be secure. Good luck.. :sweat_smile:

I have gone through many iterations in my Hangfire package and the auth options seems to be changing again for v19, so I need to verify if it will still work the way it works now.

Honestly, I think it would be better to build your dashboard so that it is native to Umbraco, but of course I don’t know what’s in it. With a bit of AI help it is pretty trivial these days to get something native in Umbraco instead of juggling weird iframes and having to worry about it being fully protected.

As for the iframe auth, there’s quite a bit of complicated code involved since I also need to coax Hangfire into accepting that we have a valid session. The latest iteration for the Hangfire dashboard was definitely built in cooperation with Claude. I would suggest you point your solution to my GitHub repo and have an AI figure it out.

I’ve asked Claude and it gave me a very long option, I asked if it could be done any shorter, it came up with a very maybe option. I have not verified any of it so.. YMMV:

His controller keeps [Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)] exactly as-is. Fetch the HTML with the token the backoffice already has, and hand it to the iframe via srcdoc:

The trade-off: srcdoc gives the iframe an opaque origin, so relative URLs inside the returned HTML break add a <base href="/"> to the markup for CSS/images, and any link or form the user clicks inside the iframe won’t carry the token. Fine for a self-contained render, not for an interactive app. That’s precisely why Hangfire can’t use this.

import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';
import { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth';

export default class MyDashboardElement extends UmbElementMixin(HTMLElement) {
    constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.innerHTML = `<iframe style="border:0;width:100%;height:100vh"></iframe>`;

        this.consumeContext(UMB_AUTH_CONTEXT, async (auth) => {
            if (!auth) return;
            const res = await fetch('/my/endpoint', {
                headers: { Authorization: `Bearer ${await auth.getLatestToken()}` }
            });
            this.shadowRoot.querySelector('iframe').srcdoc = await res.text();
        });
    }
}

There was an alternative, but it has an even worse trade-off..

[Authorize(AuthenticationSchemes = Constants.Security.BackOfficeAuthenticationType)]
public class MyIframeController : Controller { /* ... */ }

Rhat cookie is configured SlidingExpiration = false with ExpireTimeSpan = GlobalSettings.TimeOut (default 20 minutes), and it only renews when SecuritySettings.KeepUserLoggedIn is true — default false. The backoffice itself stays alive on refresh tokens, which never touch the cookie. So after ~20 minutes the iframe starts 401’ing while the backoffice looks perfectly logged in. Setting KeepUserLoggedIn: true makes it renew on each iframe request, but that’s a site-wide security setting to flip for one dashboard.

Thanks Seb!

They have that setting set, so I can use the second option (for now). I think long term, I’ll recommend migrating it to “proper” backoffice components, as I can’t see any reason for it to be a controller, other than maybe the original developer wasn’t familiar with angular.