Why my Web Fonts Suddenly Stopped Working

New versions of browsers have come out that extend the
Access-Control-Allow-Origin header to control access
to fonts as well as to JavaScript files. This
means that on all my sites—personal and professional—that use webfonts, they
have reverted to using default fonts, with ugly results.

Detection

To check that this is what is causing your site to lose its custom fonts,
activate the developer tools on your browser. On Chrome the console is full of
messages like this:

Font from origin 'http://static.alleged.org.uk' has been blocked from loading by Cross-Origin Resource Sharing policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://alleged.org.uk' is therefore not allowed access.

To make this worse, it is a problem that only manifests when deployed to the
production server (or your staging server, if you have one). While I am
developing the site my local, development server serves static files itself
rather than linking to files on a separate server (through the magic of
Django’s static template-tag).

What is Cross-Origin Resource Sharing for?

Cross-Origin Resource Sharing is a workaround for part of the
Same-Origin Policy, which in turn is a security feature that restricts access to
the content of a web page to JavaScript code that comes from the same server
(identified by its origin: the combination of scheme, domain name and port
number). This means that if your site has ads and the ads are served from a
separate company’s server, and they contain JavaScript code, then code introduced
with the ad cannot read information from the page and report it back to the
advertisers. It also defeats certain kinds of nasty malware propagated
through JavaScript concealed in content in some clever way.

Seen from the point of view of the server serving the JavaScript, the
same-origin policy also works to prevent JavaScript from being accessed by sites
other than those on the same origin—this might be important if the JavaScript
resource is some kind of internal API exposed as executable code.

For those occasions when accessing JavaScript from another origin is safe, the
CORS conventions can instruct the browser to relax the restrictions. This
works to protect the external server from being used by pages other than those
intended, by adding an Access-Control-Allow-Origin header. The browser
should then reject dynamically loaded JavaScript not including that header, or
quoting a different origin.

It does not apply to the other side of the equation, protecting your browser
from dynamically loading JavaScript that does bad things. For that there is an
entirely separate Content Security Policy proposal.

What has this got to do with fonts?

I seem to have talked a lot about JavaScript rather then fonts. Why should a
security mechanism designed to stop the propagation of malware in JavaScript
prevent my using custom fonts on my site?

The answer is that at some point it was decided that fonts should be included
in the single-origin policy. I have not been able to find the official
rationale, but candidate rationalizations would be the following:

some font formats are technically executable code;

font formats contain lots of internal parameters affecting resources used in
rendering the font, and up until downloadable fonts became a reality font
parsers could confidently assume they were reasonable (since no-one would
sell fonts that crashed people’s Macs), so insufficient checks were included
in font-handling code—which makes them potential vectors for viruses; and

copyright holders would prefer that font resources not be exploitable by
people who have not paid for a licence for that font.

The last of these is the only one for which CORS and
Access-Control-Allow-Origin actually makes sense, though it seems to be
extending the concept of ‘security’ to include the prevention of unauthorized
copying—which might or might not be a good idea. Many owners of widely
distributed images would love to have similar control over which web sites are
allowed to show a given image resource—but if single-origin and CORS
conventions were extended to images then 99% of websites would be horribly
disfigured, so no-one does that. This practicality means font publishers get a
privilege denied to professional photographers and artists.

The upshot of this was that a lot of sites suddenly ‘lost’ their fonts, and no
doubt a lot of web programmers got their heads bitten off by their bosses for
allowing the formatting of a deployed site to change unexpectedly overnight.

What is the fix?

The solution is of course to read all of the W3C Recommendation CORS
(warning: violently horrible use of coloured typography) and parts of
RFC 6454: The Web Origin Concept, digest it all and eventually deduce that
what you need to do to restore the previous status quo is add the following
header to your fonts’ HTTP responses:

Access-Control-Allow-Origin: *

If you prefer to exploit the new power to prevent people using fonts served by
your server on their pages, and like the idea of spending an afternoon
debugging the site, you could try instead using something like this:

Access-Control-Allow-Origin: http://alleged.org.uk

The recommendation and RFC do not tell you what you should do if you have
more than one origin; the usual convention for HTTP headers
is to repeat the header or to
repeat values, separated by commas, but on the other hand the RFC suggests
space-separated values, whereas on the third hand the W3C recommendation says
this probably does not work. Instead your server needs to echo back the
Origin header of the request (assuming it is on the access list). This makes
it seem to me that this was not particularly well thought through, but what do
I know?

How do you add this header? This depends on how you are serving your fonts. If
you have your own server it is relatively straightforward; for example, with
Nginx some variation on this will do the trick:

add_header Access-Control-Allow-Origin *;

This assumes that Access-Control-Allow-Origin is harmless when it is not
required. The alternative might be to try to trigger the header based on the
presence of an Origin header:

The problem here is that the if directive in Nginx is not
evaluated at the same time as the rest of the config and this is confusing to
get right. If you want to actually exploit the new features then the
Enable CORS site has a more elaborate sample config.

But what if you are using Amazon S3 as the backing storage for a
CDN? You don’t get to just set the
header—that would be too easy. You need instead to read through Amazon’s
documentation labyrinth and after digesting it all work out that someting like
the following XML incantation needs to be installed as a CORS policy on the
relevant bucket:

I say ‘something like this’ because I just stopped when I got it just about
working, and it is possible there are some fields in this document that could
be omitted to simplify it.

Conclusion

Web fonts stopped working because a feature designed to protect against
malicious JavaScript code was expanded to cover web fonts. The solution is
relatively simple—assuming you control the relevant web server—but requires
reading through a pile of complex, poorly organized, and contradictory
documentation for a feature far removed from the center of expertise of either
your web designer or your ops person. What a mess.

See also

In Part II I discuss the invisibilty of text while fonts are downloading.