Skip to content

Force asset manager preflight login - #5789

Open
eYinka wants to merge 1 commit into
mainfrom
force-asset-manager-preflight-login--2
Open

Force asset manager preflight login#5789
eYinka wants to merge 1 commit into
mainfrom
force-asset-manager-preflight-login--2

Conversation

@eYinka

@eYinka eYinka commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

What

Load a placeholder image from the draft-assets host on every draft page. Once that request has settled, retry every draft <img> already on the page with a cache-busting query param, so they get a fresh attempt against what should now be a warm Asset Manager session cookie.

Why

As of alphagov/whitehall#11667, images can now be uploaded to the draft stack, meaning Frontend needs to be able to render the draft images in the page when on draft preview.

Unlike live images, draft images require the user to be authenticated (via Signon) and for an Asset Manager session to exist. When rendering a draft asset, the request for the asset from Asset Manager is redirected through a chain of calls including Authenticating Proxy and Signon, and if 1) the user is logged into Signon, and 2) they have permission to view the asset, the asset is then downloaded. Subsequent attempts to render any asset avoids the OAuth round-trip, as the user already has a warm Asset Manager session cookie by then.

The problem arises when more than one draft asset is included in a page, and a user is coming to the page cold. When rendering the page, every draft asset begins its own authentication journey, and the various callbacks and tokens will often arrive out of order, meaning no session takes hold. The images fail to load, and subsequent refreshes of the page rarely help (unless the order of requests sent and received happens in a very particular order). Visiting a draft asset in isolation, e.g. on a new tab, forces the session cookie and then subsequent attempts to render a page full of draft assets will succeed.

In #5783 we explored adding a preflight mechanism to Frontend whereby 'cold' users are redirected to a page containing a single asset, and then redirected back to their original page. Unfortunately our nginx config would require that the single-asset-page be added to an allowlist of pages that are allowed to set cookies, and even then, subsequent pages would not be allowed to read the cookie, which is then clobbered to an empty string:
https://github.com/alphagov/govuk-helm-charts/blob/3f15020fa87e412e8e67ac9c1e1339030c4ef440/charts/app-config/templates/router-nginx-config.tpl#L116-L149

The blast radius is small - we only inject this request on the draft stack, so only publishers will see it, and the real world ramifications are perhaps a very slight delay in the rendering of the page.

The asset chosen is the default lead image placeholder referenced throughout Frontend. It is the same asset used in a similar solution in Whitehall: alphagov/whitehall#11775

Jira: https://gov-uk.atlassian.net/browse/WHIT-3983

@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 10, 2026 11:22 Inactive
@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from b867df7 to 40f6063 Compare September 10, 2026 11:24
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 10, 2026 11:25 Inactive
@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from 40f6063 to 1c58c29 Compare September 10, 2026 11:43
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 10, 2026 11:43 Inactive
@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from 1c58c29 to 36be2d2 Compare September 10, 2026 11:59
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 10, 2026 11:59 Inactive
@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from 36be2d2 to fa5dd00 Compare September 10, 2026 14:27
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 10, 2026 14:28 Inactive
@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from fa5dd00 to 7f545c3 Compare September 10, 2026 14:39
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 10, 2026 14:39 Inactive
@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from 7f545c3 to 2092801 Compare September 10, 2026 14:45
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 10, 2026 14:45 Inactive
@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from 2092801 to 9563417 Compare September 10, 2026 14:49
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 10, 2026 14:50 Inactive
@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from 9563417 to e61e602 Compare September 10, 2026 15:13
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 10, 2026 15:13 Inactive
@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from e61e602 to 0903f8d Compare September 10, 2026 15:15
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 10, 2026 15:15 Inactive
@eYinka eYinka changed the title Force asset manager preflight login 2 Force asset manager preflight login Sep 10, 2026
@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from 0903f8d to a8e7d09 Compare September 10, 2026 15:40
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 10, 2026 15:41 Inactive
@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from a8e7d09 to b64f549 Compare September 10, 2026 16:11
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 10, 2026 16:12 Inactive
@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from b64f549 to abe84f0 Compare September 10, 2026 16:16
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 10, 2026 16:16 Inactive
@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from abe84f0 to dabd80c Compare September 10, 2026 16:31
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 10, 2026 16:31 Inactive
@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from dabd80c to a400b37 Compare September 10, 2026 16:49
@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from a400b37 to 28cb7c3 Compare September 10, 2026 16:57
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 10, 2026 16:57 Inactive
@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from 28cb7c3 to f944a16 Compare September 10, 2026 17:12
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 10, 2026 17:13 Inactive
@eYinka
eYinka marked this pull request as ready for review September 10, 2026 17:24

@ChrisBAshton ChrisBAshton left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍 Some small comments below, which I can help with later if you don't get a chance to in the meantime.

Comment thread app/assets/javascripts/asset-manager-session.js Outdated
Comment thread app/assets/javascripts/modules/asset-manager-session.js Outdated
Comment thread app/views/shared/_footer_navigation.html.erb Outdated
Comment thread app/assets/javascripts/modules/asset-manager-session.js Outdated
Comment thread app/assets/javascripts/modules/asset-manager-session.js Outdated
@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from f944a16 to 8c0d9db Compare September 11, 2026 09:26
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 11, 2026 09:26 Inactive
@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from 8c0d9db to fd628c5 Compare September 11, 2026 10:55
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 11, 2026 10:55 Inactive
Comment thread app/views/shared/_footer_navigation.html.erb Fixed
@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from fd628c5 to 7e0bd86 Compare September 11, 2026 11:13
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 11, 2026 11:13 Inactive
@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from 7e0bd86 to 2961910 Compare September 11, 2026 11:14
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 11, 2026 11:14 Inactive
@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from 2961910 to d4d1e4c Compare September 11, 2026 11:54
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 11, 2026 11:55 Inactive
@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from d4d1e4c to e40b779 Compare September 11, 2026 12:09
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 11, 2026 12:09 Inactive
ChrisBAshton
ChrisBAshton previously approved these changes Sep 11, 2026

@ChrisBAshton ChrisBAshton left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me - nice one for keeping it scoped only to the draft stack, to minimise blast radius, whilst still keeping it modular and testable 🎉

Will get a 2i from a frontend dev

</div>
<%= javascript_include_tag "asset-manager-session.js", integrity: false %>
<%= javascript_tag nonce: true do %>
new GOVUK.Modules.AssetManagerSession(document.getElementById("asset-manager-session")).init()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need to manually initialise this code here (unless I've misunderstood how it works). All of our Javascript modules are started automatically when an element is given a data-module attribute e.g. in this case data-module="asset-manager-session". This would mean you can also remove the id from the element.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, that was the initial implementation but it didn't solve our race-condition problem. The auto-init is scanned on DOMContentLoaded, which meant the images already fired and triggered a csrf problem before the module kicks in. This was the only other way I could think of.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case I wouldn't write it as a module. I'd write it as a self executing block of code and put it in app/javascripts/lib or something. See https://github.com/alphagov/govuk_publishing_components/blob/main/app/assets/javascripts/govuk_publishing_components/lib/cookie-functions.js for an example.

@eYinka eYinka Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the tip. That's all done now!

Comment thread app/views/shared/_footer_navigation.html.erb Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I check that this is best place to put this new code? I think only 11 content types use this partial. It might be better in the main layouts (although you would have to duplicate it, so it would need to go in its own partial).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooh, good shout - worth us double-checking this.

@eYinka eYinka Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've now extracted it to its own partial and used across the 5 layouts in app/views/layouts

Comment thread app/views/shared/_footer_navigation.html.erb Outdated
Comment thread app/views/shared/_footer_navigation.html.erb Outdated
Comment thread app/assets/javascripts/asset-manager-session.js Outdated
* then call `new GOVUK.Modules.AssetManagerSession(element).init()`.
*/

/* istanbul ignore next */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were these ignore lines necessary for the code coverage?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking, but I'm not sure they're necessary. Might be worth removing them and seeing if the coverage still passes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just got tripped by its absence in ci. See coverage error

@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from e40b779 to 816fc4b Compare September 11, 2026 14:27
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 11, 2026 14:28 Inactive
@eYinka
eYinka force-pushed the force-asset-manager-preflight-login--2 branch from 816fc4b to 6be574d Compare September 11, 2026 14:49
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5789 September 11, 2026 14:49 Inactive
Each draft image on a page independently triggers its own Signon/Asset Manager OAuth handshake when there's no existing session. With more than one draft image, these handshakes race and clobber each other's CSRF state, so all but (at best) one image fail to load.

In this commit, we load a single placeholder image from the draft-assets host, and once its request has settled - whether it succeeds or fails, we only care that the handshake has finished; we then retry every draft image already on the page.

Added an `asset-manager-session.js` script that is inlined directly into the layout files via a `<script>` tag, and only on draft-stack pages, rather than shipped as part of the site's main JavaScript bundle. This is so that live pages are completely unaffected and don't load or run any of this code.

@ChrisBAshton ChrisBAshton left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working through the comments! ⭐

<%= javascript_include_tag "lib/asset-manager-session.js", integrity: false %>
<%= javascript_tag nonce: true do %>
window.addEventListener("load", function () {
GOVUK.warmUpAssetManagerSession('<%= placeholder_asset_url %>')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants