Skip to main content
795

July 15th, 2024 × #Cloudflare#Fonts#Caching

Hosting Private Fonts on the Edge With Cloudflare

Discussion on building a custom font hosting server to securely serve licensed fonts only to allowed origins using Cloudflare Workers, Pages and KV store.

or
Topic 0 00:00

Transcript

Scott Tolinski

Welcome to Syntax on this Monday hasty treat. We're gonna be talking about how we solved a problem that we had using some interesting technology.

Topic 1 00:10

Licensed fonts hosted securely with custom font server

Scott Tolinski

We had some fonts in our application, and our app is open source as you Node, but these fonts are licensed. And you Scott keep these licensed fonts secure. So we built a miniature little font serving platform to help us serve fonts with a white list across any of the sites that I'm using personally. My name is Scott Tolinski. I'm a developer from Denver, and with me as always is Wes Bos. What's up, Wes?

Wes Bos

Hey. Not too much. Excited to be here. I'm up at the cottage now for for a good chunk of the summer, which I'm pretty excited about. So I Scott my cottage office right on the rock. Yeah. So if you hear beavers or something like that, that's that's probably just outside.

Wes Bos

I got the windows open right now. You guys got loons up there? Is that a thing? You got loons? Tons of loons. Yeah. You can JS that a thing? The we our money is called the loony. Oh, yeah. Like yeah. Oh, yeah. We got lots of it. Actually Oh, man. Oopsies. The loons are really interesting because there's only 1 loon per kinda like area of the lake.

Wes Bos

So every summer, you you hope that, like, a loon stays in your area. Right? Because they're they're, like, mean.

Wes Bos

Loons like, if there's 2 loons in the area, they'll, like, spear each other's Really? Brains out. Yeah. There's there's some gnarly. If you ever wanna, like, go down a YouTube rabbit hole, like, search up, like, loons being aggressive.

Scott Tolinski

I love the sounds they make. They're fantastic.

Wes Bos

Yeah. We love apparently, that sound is when they Yarn stressed out.

Wes Bos

Oh, wow. Every time we hear, we're like, oh, no. And it's usually when there's, like, a sea dew or we just had Canada Day yesterday. They hate the fireworks. But every year, we see, like, 2 little Loon babies just trailing the mom, and we're happy to see it.

Scott Tolinski

Wow. You know what else I'm happy to see? I'm happy to see that Sanity has put together a behind the code discussion with back end experts. What's so cool about this is it's going to be coming up soon after you're listening to this. So July 16th. We'll have a link in the show notes. It's a Tuesday at 10 MST.

Scott Tolinski

So what is that? 9 o'clock? Mountain time.

Scott Tolinski

Either Yeah. 12 Nobody knows what mountain time is. Either give us eastern or Pacific. Well, you know what? It's showing the time zone I'm in, and I I'm trying to do this on the fly here.

Scott Tolinski

Okay. So it's 9 Pacific, 12 ESLint.

Scott Tolinski

And this is it's gonna be awesome because it has a bunch of folks that have been on the show, but, even some folks that haven't, just really brilliant people. So, Paul Copplestone, as you, listened to on syntax, the CEO and founder of Supabase, Taylor Otwell, the Lambo man himself, the founder of Laravel, and Yagiz Nizipli, who is on here, he's Node TSC member. He works for Century now and has made some really incredible Yeah. He's got some cool perks.

Topic 2 02:29

Behind the code discussion on July 16th

Scott Tolinski

Yeah. That should be a good 1. Yeah. If you wanna get get some, intelligence from the brightest minds in the space, check it out. We'll have a link in the show Node. You could sign up and, yeah, give it a watch. It's gonna be really neat. So let's get into the problem here. What's going on? What happened? Well, nothing really happened. We just have licensed fonts in our application JS many of you do. You Node, you purchase a font license, and sometimes that licensee has a way to serve that font itself, and you just use that. Other times, you're just using Google Fonts. Right? But, like, what about not having Google Fonts? What about just loading the font when there's a font that is licensed? Now that 1 of the problems that people don't often talk about with Google Fonts, and this is not necessarily related to the licensing issue, is that Google, they put tracking into those font scripts.

Topic 3 03:51

Google Fonts tracking prompts self-hosting

Scott Tolinski

And because of using, Google Fonts, you didn't have to have a cookie banner. Just something to be aware of that hosting your own fonts, even if they're downloaded initially from somewhere else, can be a good thing. Now many times, it's just as easy as dropping that font into your static folder and loading that bad boy up directly on your server with some caching, whatever.

Scott Tolinski

However, because our fonts are licensed, we don't wanna throw them into our Deno. And then everybody can just search GitHub for our licensed fonts and download them directly from there. So Yeah. I took this as a fun little challenge to make something. Have you ever gotten a DMCA from a company for having something licensed in a GitHub repo? I haven't, but I'm surprised I haven't because I'm starting to see a lot. A premium plug in at 1 point

Wes Bos

that I was like it was like a starter. Right? And then the people who own the the premium plug in, they're like, hey. They sent a DMCA. They didn't even ask me to take it down. They just DMCA'd it. Oh, yeah. I don't know if that takes it, like, out of the Git history. Probably that's why. Right? It probably scrubs Git history from from being in there, and I realized, oh, yeah. You probably shouldn't be doing that type of thing. The fonts 1 is is really tricky though because yeah, sometimes you do want it to be part of your your source code. Right? I have some fonts in my render, and I always get, like, these, like, you're running out of bandwidth emails from them where I, like, just squeak in under the month, and I'm thinking, like, yeah, I probably shouldn't have things like fonts being served up by my compute.

Topic 4 04:41

GitHub DMCA for licensed assets

Wes Bos

Those are better to put in, like, a, like, a CDN or at least put a CDN in front of it.

Scott Tolinski

Yeah. Yeah. And there's a lot of, a lot of strategies here, and I'll I'll talk about some of the things that I thought about or tried. And we can talk about if the decision I went with was ultimately the right 1 because none of these decisions are permanent. You can always change things. So, what I made is a a quick little font server using Cloudflare Pages. Node, initially, I wrote this thing with just a straight up worker, and I'll pop over to that code. As you can see, you know, workers are great for doing little things. But the moment I started writing HTML templates and strings that started to look like this Wes the moment I was like, yeah. You Node? Maybe III need routing. I need pages. I need a little bit more here.

Scott Tolinski

And I think the concern initially for me, considering I didn't have a ton of experience with Cloudflare workers in general, and I don't need to do that much with serverless functions, to be honest, was what's the difference practically between, Cloudflare Pages function and a worker speed wise? And it turns out, functionally, there's no difference there. They both function the exact same as far as Cloudflare is concerned. So, I wasn't taking a performance hit by turning this into a pages function.

Topic 5 06:37

Pages vs Workers for serverless functions

Scott Tolinski

So, therefore, I converted this into a SvelteKit repo, of course. Now it it does feel like that could be considered to be, like, overkill for something like this. But what you Scott understand is at the end of the day, ultimately, I had, like, a couple of pages. 1, to be able to manage the white list for the fonts, because I wanted to be able to choose where I'm serving these things to. But also, I wanted to be able to have individual functions, which are, you know, CloudFlare functions.

Scott Tolinski

And that can be done with a simple path server dot t s.

Scott Tolinski

So in something like SvelteKit with the routing system, you're you're just creating a route and that route is going to essentially function exactly like a CloudFlare function is because that's the way it bundles itself up given the CloudFlare adapter here. So it's not like I'm running a big old Node application to do this even though it is using

Wes Bos

something like SvelteKit. Right? Yeah. I was always surprised that Cloudflare Workers doesn't have the idea of, like, folder based routing, you know, like a very light routing where you wanna have a couple files. You know? And I always reach for, like, a Deno JS or or something like like SvelteKit just because you want the a little bit of baked in routing and some helpers.

Scott Tolinski

Yeah. Because when I did it with workers, this was it. You Node? You grab the path name. You're splitting the path name and checking the path for, different strings just in, you know, just in case. But with this, again, I could use the the various SvelteKit routing stuff, and it just worked really nice. So this was really super easy. CloudFlare has a SvelteKit starter, so you just start with that if you wanted to use that type of thing. But, again, there's there's starters for everything.

Scott Tolinski

So I I decided to throw this into a worker and a function.

Scott Tolinski

And the first question might be, like, why not a bucket? Because the bucket seems like it makes a lot of sense. Right? You just dump the font in the bucket. You set up your access rules. You access the font. That's the easy way, I think. Yeah. And that works perfectly fine. In fact, I I actually did that at 1 point here JS part of my testing. It worked

Topic 6 08:19

Storing fonts in Buckets vs KV store

Wes Bos

just fine. And a bucket to be clear, a bucket JS, like, a place where you can dump files. Right? And Correct. Have control over. And so, like, a font file at what dot warp 2 is the 1 that we're all using right Node. That would be good to just throw into a bucket.

Scott Tolinski

Yes.

Scott Tolinski

Yeah. And and this also was a big change for us too. We're not gonna get too much into this aspect on this episode, but we we went from how loading, like, 6 or 7 fonts, Wes. I don't even know if you because you were on vacation for this. We went from loading 6 or 7 fonts to 1 variable font.

Scott Tolinski

And what I learned is that variable fonts have a whole host of crazy new properties that you have to learn, like new syntax. Like, if I just set, like, font weight 900 on a variable font, it's not guaranteed to work the way I want it to depending on how the variable font was created. So, I have a lot of, interesting things I learned about variable fonts involved in this. Okay. So I I put these into a worker. And what about the fonts themselves? I actually put those into KV, which is a key value store, which was an interesting approach because I was able to upload the the font itself with a Wrangler KV key put with my Node space ID and my ID, and then I just passed in the font. And guess what it did? It's up those right into KV. And to be clear, KV is not like a, file store. It's a, like, a key value store.

Scott Tolinski

So these things were able to be stored as an act they're small enough to be able to store as an actual value and then caches that value and send along as a string data. So I did this approach just to see just to see how it would work. Right? And I gotta say, after tracking performance so I I loaded up from a bucket, from KV via the worker, and then from KV via Cloudflare Pages.

Scott Tolinski

And, consistently, the the worker and the pages were faster than loading it from a bucket.

Scott Tolinski

I don't know if that's hard fact. I ran Yeah. I ran refresh without cache a 100 couple couple 100 times and checked the results. It was always faster. That doesn't mean that it's necessarily the fastest approach or something. Google Fonts is, like, crazy, crazy fast. But, ultimately, the the key here is that we have a white list. Whitelist is stored as a key value in the same key KV.

Scott Tolinski

When a request comes in, I I check to make sure the origin is in the allowed domains, which is in our KV, whitelist, and then I go on and serve it with some hard caching. Now the caching part is something that I'm going to wanna get your advice on, Wes, because we had some weird issues with caching, and I was occasionally having to turn off the CDN cache or or dump the entire Cloudflare CDN cache at various points.

Scott Tolinski

But this is pretty much the whole thing.

Topic 7 11:26

Summary of font serving architecture

Scott Tolinski

Now to manage the white ESLint, again, the white list is just a KV store. So what I did to manage the whitelist is I'll pop open.

Scott Tolinski

Here we are.

Scott Tolinski

And, basically, I'm loading up the whitelist. I'm fetching it with a normal fetch request, and I'm throwing it into just a text area.

Scott Tolinski

Now for security, what I did is I hard coded an access PIN, which is an ENV PIN that only I know. So anytime I wanna update the white list instead of having auth yeah. I just type in my PIN and hit enter. It checks to make sure that PIN is correct with the, the ENV PIN. Now as long as that ENV variable doesn't get leaked, I'm golden. Right? Because, you know, it's not a hash password that had that password's being encrypted already. It's being stored on on Cloudflare. I don't have to worry about that, becoming publicly available or anything. So this was a nice little super easy auth, just a easy way to to lock this white list down. But that's really it. You know? I I'm in here. I'm running a function. That function is really just hitting a fetch where I'm posting to the white list. I'm stringifying the white ESLint. And on that, the white list page, which is right here, all I'm doing with that for the post request here is I'm running platform E and V Scott fonts put. I'm putting the fonts into the KV as domains, and I'm stringifying them. That's it. It it's this couldn't be pnpm any more simple application. There's no date database beyond the KV, which is just a few values.

Topic 8 13:00

Private font hosting with whitelist completed

Scott Tolinski

Scott not a lot of stuff happening here. But at the end, the result is we get full control over where we host these fonts and which fonts are served and uploaded. And then I can have my own little private font store. Right?

Wes Bos

So this is essentially stopping anybody from taking our hosted fonts and then just hotlinking them in their own website because we're paying for the bandwidth and we're paying for the actual font. So if somebody were to try and import the font directly into their own website, the origin request would not match your allow list, and it would it would not send it. Right?

Scott Tolinski

That's correct. Yeah. And that that's the intention here. So was this a overkill approach to something that could have been solved with a bucket? Absolutely. But you know what? I think these types of projects Yarn, like, the ones that we have the most fun doing. Right? We have this fun little problem of font hosting. I'm gonna try to build a custom font hosting service. And how am I gonna do that? Well, I'm gonna throw the fonts into a key value store, and see how that performs. And the truth is it performed really well. So a lot of really interesting stuff, and I I have to solve this code public if you wanna give it a try or check it out. Now, Wes, I have some questions for you as being, the JavaScript dude around here.

Scott Tolinski

So 1 1 of the things that we're doing here is we're handling the white listing in code. A lot of it is there's an actual white list, and we're checking is allowed domain using the origin, which actually, that's a whole another thing. Apparently, Safari doesn't reliably set the origin header. I don't know if you knew this. Oh, really? Yes. You can't rely on the origin header for Safari. So I just had to use, refer, which was reliable enough for Safari here.

Topic 9 14:17

Use Cors instead of whitelist code?

Wes Bos

Okay. But I think I I went into this once. I had somebody, like, submit, like, like a bug bounty to me where I was I forget what it was. Essentially, I was, like, checking if the domain name had, like, west Bos .com in it, but then somebody figured out that you could do, like, west boss.com.whatever.whatever.

Wes Bos

Yeah. And they were able to, like, embed my videos or something like that. Yeah. And, I was like, oh, yeah. I have to actually check. And then I I moved it from just like a a checking strings. I moved it to, like, a proper URL parser, in in JavaScript because that was frustrating. But sorry. That wasn't your question, though. What was that? No. No. No. That wasn't my question. But, the question is, do you think

Scott Tolinski

the whitelisting stuff that I'm doing here in JavaScript is worth it, or would it be better off just to have that all being done with the whitelist and inside of our allow origin header? So, like, should I remove the white listing JavaScript and have white listing being done entirely through Cors instead?

Wes Bos

Yeah.

Wes Bos

That's a good question.

Wes Bos

I would think so

Scott Tolinski

because I Wes, in this case, this here's a thought I'm just having right now live because I am loading the font here. I'm loading the font as an array buffer here. So, like, if I'm quitting early because they're not allowed, that's stopping this font from being loaded as an array buffer before being passed into a request. So that is saving me some compute. Is it not?

Wes Bos

Yes. I'm just trying to wonder. Like, does does a font face respect a course issue?

Scott Tolinski

Yes. Very much so. Okay. Because a a script tag does not. It does because I had a quite a bit of issues

Topic 10 16:30

Confirm font face respects Cors

Wes Bos

with Chorus in this. So Okay. Okay. Good. Man, I don't know. Like, sorry. The the question was, is this would it be enough just to use the allow origin? I would think, like, if the the platform has it built in, I would probably lean on that. And I think the only reason people Scott of would opt out of, some of the platform features is because if they wanted a little bit more, control over that type of thing, especially because you're you're caching it forever Yes. Which means that if you ever want to update the allow origin, you can't unless you were to change the file name of the font, which also makes sense, I guess. Like, the the whole idea JS, like, cash forever. And if you update the font and change the font name, right, like, put a hash on it. Wes, so that's a whole another thing. So the 1 thing that we're saving by doing the white list is this line. That's it. Loading the array buffer. But

Topic 11 17:33

Efficient caching for multiple origins?

Scott Tolinski

so like you mentioned, we are caching the origin forever, but I'm serving these things to several different websites. So that's where the very, accepting coding refer header comes in. Now Oh, okay.

Scott Tolinski

I don't know if this is the right solution to this. So if you're out there and you're a header whiz, and I'm serving this to multiple different origins, the big question is, how do you efficiently cache this, to multiple different origins? I guess the answer might be, Wes, to have a unique URL for each whitelisted site. Like, maybe that's it. Maybe you append something with the whitelisted site to the URL, and that's that's how you get around the caching there.

Wes Bos

Oh, I see.

Wes Bos

Can you put multiple URLs in and allow origin?

Scott Tolinski

No.

Scott Tolinski

No? I don't think so. Maybe worth Googling. Yeah. Yeah. Let me

Wes Bos

no. You're right. It's, yeah, it's based on 1 each.

Scott Tolinski

So Yes. So right. You know what? It it was a tough problem with this course, and I don't know if this is the final best, correct solution here. But this is currently working for us.

Scott Tolinski

So if you're out there and you you know your headers in and out, and you have a better solution for this, I I'm thinking as we talk through this, which I think is a good a good thing to do, I think maybe having a custom URL because all it would be doing would be appending a URL string. You parse part of that string, and then now you have a custom string URL per website.

Wes Bos

Okay.

Wes Bos

And how does this work on, like, local host then?

Scott Tolinski

Local host is just whitelisted.

Scott Tolinski

Okay. So it it does allow it. Okay. Yeah. Which at that point, if you wanna load up our site and no one's gonna the the long story about, like, stealing a font file is no one's ever going to prevent you from stealing a font file. There's an endless amount of ways to get to a font file even in these scenarios where you're you're trying to protect it. That's not gonna happen. You know? If if if font file JS being loaded, people can access it. So loading it up in local host and downloading the fonts, not a huge deal.

Scott Tolinski

You're you're still breaking the license if you use it. So

Wes Bos

Yeah. The the way that people get around people stealing fonts is they do not serve up all of the weights.

Wes Bos

They only serve up the WAF 2, which can be tricky if you want to try to convert that back to something that works ESLint, like, a Figma, if you wanna, like, design with it. And then also, people will they'll figure out what characters need to be used and only compile the font for that much. Like, I there's some of these font websites, and I was always really curious. Like, how do these font websites allow you to test it on the website but also get you to buy it? And it's really interesting because you what you type in, like like, testing testing 123, it will it will go off and compile a version of the font only for that amount of, characters and then bring it back. So there's some pretty tricky canvas too. Yeah.

Scott Tolinski

Sometimes yeah.

Wes Bos

Yeah. I I always hate that because you can't, like, select it, and you can't see the browser rendering, and it doesn't, like, wrap and things like that. So some of the the really nice websites allow you to, like, actually use it on there, which is it's probably very frustrating for, like, font boundaries with that. There's no there's nothing built in. Like, there's no DRM built into the browser for specific fonts.

Scott Tolinski

Yeah. Yeah. That's a, a touchy topic right there. Ultimately, again, you're not gonna be able to prevent somebody from getting a font no matter what. So, hey. It's all about, you can your foundry.

Scott Tolinski

Yep. Yeah. So, yeah, if you're out there and and you have a better solution for this for headers, let me know. I thought this was just an interesting project, an interesting idea. And if you wanna see, like, maybe what working inside of a a worker like this is like to accomplish something like this, the code is all public. We'll have it available in the show notes. That's all I have for you today, Wes.

Scott Tolinski

The last little thing might be just to use the fonts. You just load it up with think it's fonts.tolin.skiforward/thefontname, bingo bango. As long as the access is good, you're good, and it loads like a normal font, caches it forever, like I said.

Topic 12 21:40

Project code available publicly

Scott Tolinski

Pretty speedy.

Wes Bos

Awesome.

Wes Bos

That's good. We should get that on, syntax domain. Oh, it's because we gotta move the syntax

Scott Tolinski

Cloudflare over to the syntax. Yes. Yeah. Yes. This is a a major conversation while you were gone. I was huge pain

Wes Bos

of moving Cloudflare. Did I did I did I tell you that I had a domain name on Cloudflare, and I needed to transfer it to somebody else also on Cloudflare.

Wes Bos

Yeah. And it is not possible at all. The registration I'm talking about the registration, not the, like Yeah. Not the being, not the, like, being in Cloudflare. So I had to transfer it out of Cloudflare to Vercel, and then you have to wait 60 days and then transfer it all back over to the Air Cloudflare.

Wes Bos

You can obviously use it right away because you can still add it to Cloudflare, but it's a Why why would it use that? I'm betting that because Cloudflare has so much fancy stuff around domain names, because, like, you can have workers attached to a domain name. You can have, access control attached to a domain name. You can have, like, the tunnels.

Wes Bos

You know, Wrangler stuff. All of the stuff is attached to a domain Node, and it might be simple to think like, yeah, let's just move it over.

Wes Bos

But I think people probably assume that all of the parts of the domain name also move over. So there's currently no way to transfer a domain name over. It will, like, suck up the DNS and try to, like, repopulate it. It's very good at that. But the actual ownership of the domain name, you cannot go Cloudflare to Cloudflare account. Wow. Yeah. Thanks. We don't we don't have that issue with the syntax. Like, we we sent the syntax domain ownership to whatever Sanity uses, but we have to like, I have a couple workers on there, a couple subdomain names. I gotta move all that over, and that just doesn't seem fun. There's some some golden rule that should be there. It's like never do anything on your personal account. Ever. Yeah. Just Yeah. Moving it over is

Scott Tolinski

a pain. It always feels like the the right thing at the time. You're like, I just need to get this done. Oh, yeah. Yeah. Okay.

Wes Bos

Alright. Thanks everybody for tuning in. We will catch you later.

Wes Bos

Peace. Peace.

Share