1. Introduction
The
Storage
Access
API
supports
"authenticated
embeds"
by
providing
a
way
to
request
access
to
unpartitioned
cookies
in
an
embedded
context.
This
currently
requires
an
explicit
call
to
a
JavaScript
API
(namely
requestStorageAccess()
)
to:
-
Potentially prompt the user for permission; and
-
Explicitly indicate the embedded resource’s interest in using unpartitioned cookies (as a protection against CSRF attacks by an embedder).
As the above list suggests, this single API invocation is serving two orthogonal purposes:
-
It enforces a privacy boundary between the top-level site and the embedded site, and gives the user (and/or user agent) an opportunity to relax or maintain that privacy boundary.
-
It enforces a security boundary between the top-level site and the embedded site (namely, the aforementioned CSRF protection), and serves as the embedded site’s explicit signal to relax that security boundary (by allowing credentialed requests to be sent to the embedded site, in the given context).
The
requirement
to
invoke
requestStorageAccess
is
therefore
useful,
but
it
imposes
some
challenges:
-
Use of the Storage Access API may currently require multiple network round trips and multiple resource reloads before an
iframecan work as expected, since theiframemust executerequestStorageAccess()before fetching all of its embedded resources (presuming that they require access to unpartitioned cookies). In practice this means that theiframeloads, executesrequestStorageAccess(), then refreshes itself in order to re-do all of the embedded fetches (including unpartitioned cookies this time). -
Embedded resources currently must execute JavaScript in order to benefit from this API. This effectively means that the embedded resource must be an
iframethat has the ability to run JavaScript, or must be a subresource fetched by such aniframe(see the first bullet). This imposes an unnecessary burden on sites that serve access-controlled resources (i.e. resources that require authentication cookies) which are embedded in cross-site pages.
These challenges can be mitigated by supporting a new pair of headers. In particular, this document introduces:
-
`
Sec-Fetch-Storage-Access`, a request header to convey information about whether unpartitioned cookies were included in the request, and possibly whether thestorage-accesspermission has been granted. -
`
Activate-Storage-Access`, a response header that can be used to activate an existingstorage-accesspermission grant and "retry" the request, or to activate an existingstorage-accesspermission prior to loading a Document (typically, aniframe).
2. Infra
This specification depends on the Infra standard. [INFRA]
3. Storage-Access Request Infrastructure
In addition to the new headers themselves, this document introduces some new infrastructure to store and convey metadata in the user agent , particularly on a request .
A
request
has
a
boolean
eligible
for
storage-access
.
It
is
initially
false.
Note:
The
eligible
for
storage-access
boolean
indicates
whether
the
user
agent
is
allowed
to
include
unpartitioned
cookies
when
sending
a
request
where
the
site
obtained
from
the
request’s
url
has
a
storage-access
permission.
Note:
A
request
also
has
a
has
storage
access
boolean,
indirectly
through
its
client
.
That
value
is
distinct
from
the
request
’s
eligible
for
storage-access
boolean.
Both
represent
an
"opt
in"
signal
for
accessing
unpartitioned
cookies
in
a
cross-site
context,
but
the
signal
comes
from
different
origin
s.
The
request
’s
client
’s
has
storage
access
field
represents
whether
the
site
obtained
from
the
environment
’s
origin
has
opted
in.
The
request
’s
eligible
for
storage-access
field
represents
whether
the
site
obtained
from
the
request
’s
url
’s
origin
has
opted
in.
A
request
has
an
associated
single-hop
cache
mode
,
whose
value
is
null
or
a
cache
mode
.
It
is
initially
set
to
null.
This document renames a request ’s cache mode field to internal cache mode .
-
If request ’s single-hop cache mode is not null, return request ’s single-hop cache mode .
-
Return request ’s internal cache mode .
A storage access status is one of " none ", " inactive ", or " active ".
-
If the user agent’s cookie store would attach cookies with the
SameSite=Strictattribute to request , return null. [COOKIES] -
Let allowed be a boolean , initially set to the result of determining whether the user agent’s cookie store allows unpartitioned cookies to be accessed given request ’s url , request ’s client , and request ’s eligible for storage-access .
-
If allowed is true, return "
active". -
If request ’s eligible for storage-access is
true,"eligible", return "none".Note: the "
storage-access" policy-controlled feature was checked before setting request ’s eligible for storage-access to true. -
Let featureIsAllowed the result of running Should request be allowed to use feature? given "
storage-access" and request . -
If featureIsAllowed is false, return "
none". -
Set allowed to the result of determining whether the user agent’s cookie store allows unpartitioned cookies to be accessed given request ’s url , request ’s client , and
true. -
If allowed is true, return "
inactive".Note: allowed will be true in the above step if the permission store entry obtained by getting a permission store entry given a
PermissionDescriptorwithnameinitialized to "storage-access" and a permission key of(the site obtained from request ’s client ’s top-level origin , the site obtained from request ’s url ’s origin )has a state of "granted". Otherwise, allowed will remain false. -
Return "
none".
-
Let top level site be the result of obtaining a site from environment ’s top-level origin .
-
Let destination site be the the result of obtaining a site from url ’s origin .
-
Let key be
( top level site , destination site ). -
Let allowed be the result of determining whether the user agent explicitly allows unpartitioned cookie access given key .
-
If allowed is true, return true.
-
Return false if all of the following conditions are false:
-
eligible for storage-accessstorage access eligibility istrue"eligible" -
environment ’s has storage access is true and the site obtained from environment ’s origin and the site obtained from url ’s origin are same site
This condition ought to check a boolean that is updated after cross-site HTTP redirects. I.e., it ought to read a boolean derived from the environment ’s has storage access , not that value itself. See storage-access#210 .
-
-
Let entry be the result of getting a permission store entry given a
PermissionDescriptorwithnameinitialized to "storage-access" and a permission key of key . -
Return true.
4. Storage-Access Headers
The
following
sections
define
a
request
header
and
a
response
header.
The
request
header
exposes
information
about
the
request
’s
access
to
cookies
to
a
server.
The
response
header
allows
a
server
to
opt
into
accessing
unpartitioned
cookies
on
a
particular
request
or
when
loading
an
iframe
.
4.1.
The
Sec-Fetch-Storage-Access
HTTP
Request
Header
The
Sec-Fetch-Storage-Access
HTTP
request
header
exposes
a
request
’s
ability
or
inability
to
access
cookies
to
a
server.
It
is
a
Structured
Field
item
which
is
a
token
.
[RFC9651]
Valid
Sec-Fetch-Storage-Access
values
include
"
none
",
"
inactive
",
and
"
active
".
In
order
to
support
forward-compatibility
with
as-yet-unknown
semantics,
servers
SHOULD
ignore
this
header
if
it
contains
an
invalid
value.
// When the request’s credentials mode is "omit", the header is omitted: (no header sent in this case) // When the request is same-site, the header is omitted: (no header sent in this case) // When the request has no access to unpartitioned cookies, the header’s value is "[=storage access status/none=]": Sec-Fetch-Storage-Access: none // When the request has no access to unpartitioned cookies, but // 'storage-access' permission has already been granted, the header’s value is // "[=storage access status/inactive=]": Sec-Fetch-Storage-Access: inactive // When the request has access to unpartitioned cookies, the header’s value is "[=storage access status/active=]": Sec-Fetch-Storage-Access: active
Sec-Fetch-Storage-Access
header
for
a
request
request
:
-
Assert : request ’s url is a potentially trustworthy URL .
-
If request ’s credentials mode is not "
include", abort these steps. -
Let access be request ’s storage access status .
-
If access is null, abort these steps.
-
Let value be a Structured Field value whose value is a token .
-
Set value ’s value to access .
-
Set a structured field value given ("
Sec-Fetch-Storage-Access", value ) in request ’s header list .
4.2.
The
Activate-Storage-Access
HTTP
Response
Header
The
Activate-Storage-Access
HTTP
response
header
allows
a
server
to
opt
in
to
accessing
its
unpartitioned
cookies
in
a
cross-site
request
context.
It
is
a
Structured
Field
item
which
is
a
token
.
[RFC9651]
Valid
Activate-Storage-Access
values
include
load
and
retry
.
The following parameter is defined:
-
A parameter whose key is "
allowed-origin", and whose value is a string . See below for processing requirements.
// The server’s response requests that the user agent activate storage access // before continuing with the load of the resource. (This is only relevant when // loading a new document.) Activate-Storage-Access: load // The server’s response requests that the user agent activate storage access, // then retry the request. The "allowed-origin" parameter allowlists the // request’s origin. Activate-Storage-Access: retry; allowed-origin="https://foo.bar" // Same as above, but using a wildcard instead of explicitly naming the request’s origin. Activate-Storage-Access: retry; allowed-origin=*
-
If request ’s credentials mode is not "
include", return failure. -
If request ’s eligible for storage-access is
true,"eligible", return failure. -
Let storageAccessStatus be request ’s storage access status .
-
If storageAccessStatus is not "
inactive", return failure. -
Let parsedHeader be the result of getting a structured field value given "
Activate-Storage-Access" and "item" from response ’s header list . -
If parsedHeader is null, return failure.
-
Let ( value , params ) be parsedHeader .
-
If value is not a token , return failure.
-
If value ’s value is not "
retry", return failure. -
If params ["allowed-origin"] does not exist, return failure.
-
Let allowedOrigin be params ["allowed-origin"].
-
If allowedOrigin is a token whose value is "
*", return success. -
If allowedOrigin is not a string , return failure.
-
If the result of byte-serializing a request origin with request is not allowedOrigin ’s value, then return failure.
-
Return success.
-
Let storageAccessStatus be request ’s storage access status .
-
If storageAccessStatus is not one of "
inactive" or "active", return failure. -
Let parsedHeader be the result of getting a structured field value given "
Activate-Storage-Access" and "item" from response ’s header list . -
If parsedHeader is null, return failure.
-
Let ( value , params ) be parsedHeader .
-
If value is not a token , return failure.
-
If value ’s value is not "
load", return failure. -
Return success.
5. Integration with Fetch Metadata
The
`
Sec-Fetch-Storage-Access
`
header
is
appended
to
outgoing
requests
alongside
other
Fetch
Metadata
headers.
[FETCH-METADATA]
Modify
the
definition
of
append
the
Fetch
metadata
headers
for
a
request
by
inserting
the
following
as
step
6:
6. Integration with Fetch
Handling these headers requires modifications to a few different parts of Fetch. [FETCH]
6.1.
Origin
header
When
making
a
decision
on
whether
to
retry
a
request
and
force
it
to
include
unpartitioned
cookies,
a
server
ought
to
be
informed
as
to
the
initiator
of
the
request.
I.e.,
the
request
ought
to
include
the
`
Origin
`
header
whenever
it
also
includes
the
Sec-Fetch-Storage-Access:
inactive
header.
Modify
the
definition
of
append
a
request
Origin
header
by
rewriting
step
4
as:
-
If at least one of the following conditions is true:
-
the result of getting `
Sec-Fetch-Storage-Access` from request ’s header list is "inactive" -
request ’s method is neither `
GET` nor `HEAD`
Then:
-
The rest of the algorithm is unmodified.
6.2. HTTP-fetch
Insert a new step after step 5 in HTTP fetch :
-
If the result of performing a storage access retry check for request is success, then return the result of running HTTP-storage-access-retry-fetch given fetchParams .
Insert a new step after step 6.1 (before "switch on request’s redirect mode"):
-
Set request ’s single-hop cache mode to null.
The rest of the algorithm is unmodified.
-
Let request be fetchParams ’s request .
-
Assert: request ’s storage access status is "
inactive". -
Assert: request ’s eligible for storage-access is
false.not "eligible". -
If request ’s redirect count is 20, then return a network error .
-
Increase request ’s redirect count by 1.
-
Set request ’s single-hop cache mode to "
reload". -
Set request ’s eligible for storage-access to
true."eligible". -
Assert: request ’s storage access status is "
active". -
Let recursive be true.
-
Return the result of running main fetch given fetchParams and recursive .
6.3. HTTP-redirect-fetch
Insert a new step after step 17 in HTTP-redirect fetch :
-
If request ’s eligible for storage-access is "
eligible" and locationURL ’s origin is not same origin with request ’s url ’s origin , set request ’s eligible for storage-access tofalse."ineligible".
The rest of the algorithm is unmodified.
7. Integration with HTML
7.1. Changes to navigation
This integration builds upon the changes introduced by the Storage Access API specification. [STORAGE-ACCESS]
In particular, modify the changes when creating the request’s reserved client in create navigation params by fetching to be the following:
-
Let compute has storage access be an algorithm with the following steps, which return a boolean:
-
If response is not null and the result of performing a storage access load check given request and response is success, return true.
-
If sourceSnapshotParams ’s environment id does not equal navigable ’s active document ’s relevant settings object ’s id , return false.
-
If originalURL ’s origin is not same origin with currentURL ’s origin , return false.
-
If response is not null and response ’s has-cross-origin-redirects is true, return false.
-
Return true.
-
-
Set request ’s reserved client ’s has storage access to the result of executing compute has storage access .
8. Security Considerations
8.1. Opt-In signal
The primary security concerns for this specification are those laid out in privacycg/storage-access#113 . Namely: since the Storage Access API makes unpartitioned cookies available even after those cookies have been blocked by default, it is crucial that the Storage Access API not preserve the security concerns traditionally associated with unpartitioned cookies, like CSRF. The principal way that the Storage Access API addresses these security concerns is by requiring an embedded cross-site resource (e.g. an iframe) to explicitly opt in to accessing unpartitioned cookies by invoking
requestStorageAccess()
.
Storage
Access
Headers
continues
in
the
same
vein
by
requiring
embedded
cross-site
resources
(or
rather,
their
servers)
to
explicitly
opt-in
to
accessing
unpartitioned
cookies
(by
supplying
an
HTTP
response
header),
before
any
unpartitioned
cookies
are
included
on
the
request.
When
a
server
opts
in
by
sending
the
Activate-Storage-Access:
retry
header,
it
also
must
explicitly
name
the
origin
that
it
grants
the
ability
to
send
credentialed
requests
(via
the
"
allowed-origin
"
parameter).
This
fails
closed
by
blocking
credentialed
requests,
in
the
event
of
an
origin
mismatch.
8.2. Forbidden header name
This proposal uses a new forbidden name for the `
Sec-Fetch-Storage-Access
`
header
to
prevent
programmatic
modification
of
the
header
value.
This
is
primarily
for
reasons
of
coherence,
rather
than
security,
but
there
is
a
security
reason
to
make
this
choice.
If
a
script
could
modify
the
value
of
the
header,
it
could
lie
to
a
server
about
the
state
of
the
storage-access
permission
in
the
requesting
context
and
indicate
that
the
state
is
"
active
",
even
if
the
requesting
context
has
not
opted
in
to
using
the
permission
grant.
This
could
mislead
the
server
into
inferring
that
the
request
context
is
more
trusted/safe
than
it
actually
is
(e.g.,
perhaps
the
requesting
context
has
intentionally
not
opted
into
accessing
its
unpartitioned
cookies
because
it
cannot
conclude
it’s
safe
to
do
so).
This
could
lead
the
server
to
make
different
decisions
than
it
would
have
if
it
had
received
the
correct
header
value
("
none
"
or
"
inactive
").
Thus
the
value
of
this
header
ought
to
be
trustworthy,
so
it
ought
to
be
up
to
the
user
agent
to
set
it.
8.3. Deeper CORS Integration
It is tempting to design this specification such that it piggy-backs and/or integrates with CORS (i.e., the CORS protocol) deeply, since CORS intuitively feels like it is meant to address a similar problem of enabling cross-origin functionality. However, this would be undesirable for a few reasons:
-
If CORS (and the relevant SAA permission, of course) were a "sufficient" condition for attaching unpartitioned cookies...
-
Then this would allow the top-level site to attack the embedded site by sending (CORS-enabled) credentialed requests to arbitrary endpoints on the embedded site, without requiring any opt-in from the embedded site before it received those requests. This would make CSRF attacks against the embedded site more feasible. This is undesirable for security reasons.
-
-
If CORS were required for the user agent to attach unpartitioned cookies to the request...
-
Then this would mean the embedded site would be required to allow the top-level site to read the bytes of its responses and response headers, just so that the user agent would include cookies when fetching the embedded resource. This is a more powerful capability than simply attaching unpartitioned cookies, so this would expose the embedded site to unnecessary attack vectors from the top-level site. This is undesirable for security reasons.
-
This would also mean that in order to fix an embedded widget on some page, the top-level site must perform some action to enable CORS; the embedded site alone would be unable to update the page and fix the widget. This is undesirable from a developer usability / composability standpoint.
-
Therefore, CORS ought to be neither necessary nor sufficient for attaching unpartitioned cookies to a cross-site request. This specification is therefore designed to be orthogonal to CORS.
Note:
This
specification
does
rely
on
the
`
Origin
`
header,
which
is
defined
by
CORS,
so
this
specification
does
integrate
with
CORS
in
a
technical
sense.
This
is
intentional,
since
in
that
case
we
are
able
to
reuse
an
existing
header
that
sends
exactly
the
information
that
this
specification
needs,
and
both
the
new
usage
and
existing
usage
are
for
security
features.
9. Privacy Considerations
This
specification
simplifies
some
ways
in
which
developers
can
use
an
API
that
allows
access
to
unpartitioned
data.
However,
it
does
not
meaningfully
change
the
privacy
characteristics
of
the
Storage
Access
API
[STORAGE-ACCESS]
:
sites
are
still
able
to
ask
for
the
ability
to
access
unpartitioned
cookies;
user
agents
are
still
able
to
handle
those
requests
how
they
see
fit.
Importantly,
if
the
storage-access
permission
is
not
granted
by
the
user
or
user
agent,
then
this
specification
does
not
allow
use
of
unpartitioned
data.
The
`
Sec-Fetch-Storage-Access
`
header
does
expose
some
user-specific
state
in
network
requests
which
was
not
previously
available
there,
namely
the
state
of
the
storage-access
permission.
However,
this
information
is
not
considered
privacy-sensitive,
for
a
few
reasons:
-
The embedded site could have learned this information anyway by calling
queryand/orrequestStorageAccess()in an embedded iframe. These APIs are not treated as privacy-sensitive. -
The `
Sec-Fetch-Storage-Access` header’s value is "none" unless the relevant context would be able to access unpartitioned state after callingrequestStorageAccess()without triggering a user prompt. Thus, in the cases where the `Sec-Fetch-Storage-Access` header conveys interesting information (i.e. "inactive" or "active"), the site in question already has the ability to access unpartitioned state, by assumption. So, there is zero privacy benefit to omitting the `Sec-Fetch-Storage-Access` header altogether in those cases.-
Conversely, since the `
Sec-Fetch-Storage-Access` header only has one valid non-"active" and non-"inactive" state (namely "none"), there’s no privacy benefit to omitting the `Sec-Fetch-Storage-Access` header when its value is neither "inactive" nor "active".
-
10. Deployment Considerations
10.1. Vary
If
a
given
endpoint
might
use
the
`
Activate-Storage-Access
`
header,
then
developers
should
include
`
Sec-Fetch-Storage-Access
`
in
the
response’s
Vary
header
[RFC9110]
,
to
ensure
that
caches
handle
the
response
appropriately.
For
example,
Vary:
Accept-Encoding,
Sec-Fetch-Storage-Access
.
10.2.
Origin
Header
Interoperability
Some
servers
misbehave
if
they
receive
the
`
Origin
`
header
when
they
weren’t
expecting
to.
However,
the
`
Origin
`
header
conveys
exactly
the
information
that
a
server
would
need
before
making
an
informed
choice
on
whether
to
respond
with
the
Activate-Storage-Access:
retry
header,
so
it’s
a
perfect
candidate
for
reuse
by
this
specification
(rather
than
inventing
some
new
Origin2
header).
This
specification
strives
to
minimize
new
breakage
due
to
including
the
`
Origin
`
header
on
more
requests,
by
minimizing
the
set
of
requests
that
newly
include
the
`
Origin
`
header.
In
particular,
the
`
Origin
`
header
is
only
(newly)
included
on
cross-site
request
s
whose
storage
access
status
is
"
inactive
"
and
whose
credentials
mode
is
"
include
".
10.3.
Sec-
Prefix
The
`
Sec-Fetch-Storage-Access
`
header’s
name
is
prefixed
with
Sec-
because
only
the
user
agent
is
permitted
to
set
such
headers
(as
they
are
forbidden
request-headers
).
Therefore,
the
`
Sec-Fetch-Storage-Access
`
name
is
guaranteed
to
not
conflict
with
any
preexisting
headers
in
use
on
the
web.
10.4. Header Compression
The
`
Sec-Fetch-Storage-Access
`
header
has
exactly
3
legal
values.
Therefore
it
should
perform
well
with
HPACK,
per
[MNOT-DESIGNING-HEADERS]
.
Optimizing
the
length
of
the
header
name
has
little
impact
compared
to
minimizing
the
number
of
legal
header
values.
The
`
Activate-Storage-Access
`
header
has
an
unbounded
number
of
legal
values,
but
only
a
small
number
of
them
(perhaps
1-2)
can
reasonably
be
expected
to
occur
in
a
single
HTTP
connection.
This
header
should
therefore
also
perform
reasonably
well
with
HPACK.
11. IANA Considerations
The permanent message header field registry should be updated with the following registrations for the headers defined in this specification: [RFC3864]
11.1.
Sec-Fetch-Storage-Access
Registration
- Header field name
-
Sec-Fetch-Storage-Access
- Applicable protocol
-
http
- Status
-
draft
- Author/Change controller
-
Me
- Specification document
-
This specification (See § 4.1 The Sec-Fetch-Storage-Access HTTP Request Header )
11.2.
Activate-Storage-Access
Registration
- Header field name
-
Activate-Storage-Access
- Applicable protocol
-
http
- Status
-
draft
- Author/Change controller
-
Me
- Specification document
-
This specification (See § 4.2 The Activate-Storage-Access HTTP Response Header )
12. Acknowledgements
Thanks to Johann Hofmann, Artur Janc, Ben VanderSloot, Dom Farolino, Matt Menke, Adam Rice, and Maks Orlovich, who all provided valuable insight and support in the design of this mechanism.