Skip to content

Redirect draft HTML requests to Asset Manager preflight route [WHIT-3983] - #5783

Closed
ChrisBAshton wants to merge 2 commits into
mainfrom
frontend-asset-manager-preflight-login
Closed

Redirect draft HTML requests to Asset Manager preflight route [WHIT-3983]#5783
ChrisBAshton wants to merge 2 commits into
mainfrom
frontend-asset-manager-preflight-login

Conversation

@ChrisBAshton

Copy link
Copy Markdown
Contributor

What

This PR introduces a new AssetManagerPreflightController, which forces an Asset Manager session to be set up.

The AssetManagerPreflightController loads a single, permanent, harmless placeholder image from Asset Manager's draft assets host. That's enough to complete one Signon handshake and establish an Asset Manager session cookie in the browser - the user should already have an active Signon session by the time they reach a draft preview, so this is just completing a handshake, not asking them to sign in again.

Once that's done - or once we've given it a reasonable amount of time to finish - we bounce the user back to the page they originally asked for. From then on, every image request on that page reuses the now-warm Asset Manager session instead of starting its own handshake.

Why

Last week, we shipped a change in Whitehall, changing how images work and bringing them in line with attachments. Images are now uploaded to the draft stack by default, when uploaded to a draft document - and only become 'live' when the document is published. Access-limiting metadata on the document (individual/organisation access-limiting, auth-bypass tokens) are also proliferated through to the assets in the same way as they are for attachments.

Unlike attachments, which are exclusively viewed in isolation in browser (e.g. a PDF in its own tab) or downloaded at their own endpoint, images are embedded in the page. Whereas attachments, by virtue of being visited/downloaded in isolation, funnel publishers through Signon and then allow the visit/download, images are not able to do so because they live within the page.

If only one image is embedded in the page, it works: the publisher is already signed into Signon at the point of getting through to draft-frontend, and so under the hood when the browser renders the DOM, the draft image is parsed, several round trip network requests are made to Asset Manager and Signon, and if the user is authorised, the image downloads and renders.

The problem occurs when we're dealing with more than one draft asset in the page. A draft document can contain several draft images, and each one independently triggers its own Asset Manager/Signon authentication handshake when there's no existing Asset Manager session. Because those handshakes can complete out of order, they invalidate each other's session state and the images fail to load.

To avoid this, we need to make sure a single Asset Manager session has already been established before rendering any draft page that might contain draft images.

An equivalent fix has been applied to Whitehall, where the same issue occurs when viewing the Images tab of a draft document: alphagov/whitehall#11775

This is seen as a suboptimal fix. We will raise a ticket on the Publishing Tech Debt board to reconsider a better engineered solution to the problem, but any alternatives we've considered so far require significantly more work and come with additional risk. Given the draft assets issue is affecting publishers today, we want to prioritise what we think is an acceptable patch here and pay down the technical debt later.

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

Visual changes

⚠️ This repo is Continuously Deployed: make sure you follow the guidance ⚠️

@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5783 September 9, 2026 09:56 Inactive
Comment thread app/controllers/application_controller.rb Fixed
@ChrisBAshton
ChrisBAshton force-pushed the frontend-asset-manager-preflight-login branch from 6913bd8 to 7d0296a Compare September 9, 2026 10:07
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5783 September 9, 2026 10:07 Inactive
@ChrisBAshton
ChrisBAshton force-pushed the frontend-asset-manager-preflight-login branch from 7d0296a to c9bbd68 Compare September 9, 2026 10:15
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5783 September 9, 2026 10:15 Inactive
@ChrisBAshton
ChrisBAshton force-pushed the frontend-asset-manager-preflight-login branch from c9bbd68 to 5e38e49 Compare September 9, 2026 10:20
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5783 September 9, 2026 10:20 Inactive
@ChrisBAshton
ChrisBAshton marked this pull request as ready for review September 9, 2026 10:21
@ChrisBAshton
ChrisBAshton force-pushed the frontend-asset-manager-preflight-login branch from 6e8e720 to 7b7d6a9 Compare September 9, 2026 11:23
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5783 September 9, 2026 11:23 Inactive
Comment thread app/views/asset_manager_preflight/show.html.erb Fixed
@ChrisBAshton
ChrisBAshton force-pushed the frontend-asset-manager-preflight-login branch from 7b7d6a9 to a539ae5 Compare September 9, 2026 11:29
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5783 September 9, 2026 11:30 Inactive
@ChrisBAshton
ChrisBAshton force-pushed the frontend-asset-manager-preflight-login branch from a539ae5 to 1e60501 Compare September 9, 2026 11:33
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5783 September 9, 2026 11:33 Inactive
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5783 September 9, 2026 12:32 Inactive
# rather than introducing a second cookie for the stack in front of us
# to have to forward correctly.
def redirect_to_asset_manager_preflight_if_required
return unless request.get?
@ChrisBAshton
ChrisBAshton force-pushed the frontend-asset-manager-preflight-login branch from 55f2bc5 to e5cfd14 Compare September 9, 2026 12:40
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5783 September 9, 2026 12:41 Inactive
Start using the new route we added in the previous commit.

A draft document can contain several draft images, and each one
independently triggers its own Asset Manager/Signon authentication
handshake when there's no existing Asset Manager session. Because
those handshakes can complete out of order, they invalidate each
other's session state and the images fail to load. To avoid this,
we make sure a single Asset Manager session has already been
established before rendering any draft page that might contain draft images.

The AssetManagerPreflightController loads a single, permanent, harmless placeholder image
from Asset Manager's draft assets host. That's enough to complete one
Signon handshake and establish part of the Frontend session cookie as
denoting that an Asset Manager handshake has completed. The user should
already have an active Signon session by the time they reach a draft
preview, so this is just completing a handshake, not asking them to sign in again.

Once that's done - or once we've given it a reasonable amount of time to
finish - we bounce the user back to the page they originally asked for.
From then on, every image request on that page reuses the now-warm Asset
Manager session instead of starting its own handshake.
@ChrisBAshton
ChrisBAshton force-pushed the frontend-asset-manager-preflight-login branch from e5cfd14 to 14b8684 Compare September 9, 2026 12:41
@govuk-ci
govuk-ci temporarily deployed to govuk-frontend-app-pr-5783 September 9, 2026 12:42 Inactive
Comment thread config/routes.rb
# Favicon redirect
get "/favicon.ico", to: "favicon#redirect_to_asset"

get "/draft-asset-preflight", to: "asset_manager_preflight#show", as: :asset_manager_preflight

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.

Could you move this under the /api section? (we're trying to make this file as alphabetical as possible given the constraints).

ChrisBAshton added a commit that referenced this pull request Sep 10, 2026
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 solution is to force an asset to download at the beginning of
the page, before even attempting to render the rest of the page.
Various Javascript attempts were looked into here, but there was
no reliable means of blocking rendering and waiting for the asset
request to complete. Instead, we can rely on the native behaviour
of the browser: treat the image as a JavaScript source instead and
attempt to download that in the head. That is a blocking operation,
and the full OAuth redirect chain is followed before eventually the
image is returned. That clearly isn't JavaScript, so the console will
error, but as the 'script' is external, its error doesn't affect
any other JavaScript in the page, so the console syntax error is a
no-op.

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.

Alongside the change to the layout, I've added tests for all of the environments listed in
https://docs.publishing.service.gov.uk/manual/environments.html#determining-your-environment

Jira: https://gov-uk.atlassian.net/browse/WHIT-3983
ChrisBAshton added a commit that referenced this pull request Sep 10, 2026
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 solution is to force an asset to download at the beginning of
the page, before even attempting to render the rest of the page.
Various Javascript attempts were looked into here, but there was
no reliable means of blocking rendering and waiting for the asset
request to complete. Instead, we can rely on the native behaviour
of the browser: treat the image as a JavaScript source instead and
attempt to download that in the head. That is a blocking operation,
and the full OAuth redirect chain is followed before eventually the
image is returned. That clearly isn't JavaScript, so the console will
error, but as the 'script' is external, its error doesn't affect
any other JavaScript in the page, so the console syntax error is a
no-op.

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

Alongside the change to the layout, I've added tests for all of the environments listed in
https://docs.publishing.service.gov.uk/manual/environments.html#determining-your-environment

Jira: https://gov-uk.atlassian.net/browse/WHIT-3983
eYinka pushed a commit that referenced this pull request Sep 10, 2026
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 solution is to force an asset to download at the beginning of
the page, before even attempting to render the rest of the page.
Various Javascript attempts were looked into here, but there was
no reliable means of blocking rendering and waiting for the asset
request to complete. Instead, we can rely on the native behaviour
of the browser: treat the image as a JavaScript source instead and
attempt to download that in the head. That is a blocking operation,
and the full OAuth redirect chain is followed before eventually the
image is returned. That clearly isn't JavaScript, so the console will
error, but as the 'script' is external, its error doesn't affect
any other JavaScript in the page, so the console syntax error is a
no-op.

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

Alongside the change to the layout, I've added tests for all of the environments listed in
https://docs.publishing.service.gov.uk/manual/environments.html#determining-your-environment

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

Copy link
Copy Markdown
Contributor Author

Closing in favour of #5788.

This approach wouldn't work because, as per our docs:

GOV.UK frontend applications, with the exception of Licensing and Email Alert Frontend, do not currently require cookies.
We configure Varnish to strip cookies for inbound and outbound requests.
This means any features in frontend application relying on sessions/cookies will not work.

The docs point to Puppet, which was replaced by Kubernetes a couple of years ago, but the logic still stands. There is VCL in govuk-helm-charts that prevents Set-Cookie, apart from for an allow-listed few. And even if we add our new endpoint to that allowlist, the nginx config clobbers the cookie on read, to an empty string, so we can't make use of the value. See:

https://github.com/alphagov/govuk-helm-charts/blob/3f15020fa87e412e8e67ac9c1e1339030c4ef440/charts/app-config/templates/router-nginx-config.tpl#L116-L149

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.

4 participants