Product
Platform
Platform
Platform
Developers
Quickstarts
Resources
Explore
Pricing
Download
get a demoLogin

OAuth token storage determines how much damage an attacker can do after stealing a token. This webinar breaks down where access and refresh tokens should live across browser, server-side, and mobile applications, why local storage creates avoidable exposure, and when Secure, HttpOnly cookies or a backend-for-frontend architecture provide the better control.
.png)

.png)
Hey, folks. Thanks for joining us. We're gonna wait a minute or two to let people filter in.
Hi, folks. We're gonna go on. Josh just grabbed some water real quick, but before he gets back, we'll just do some quick nuts and bolts and intro.
The first thing is that if you have questions, please go ahead and ask them in the Q&A section so that we'll have a chance to take a look at them and respond to them. It is much more fun for this to be kind of an interactive thing than it is just me and Josh talking, although we like to chat. It's gonna be a kind of informal chat around token storage, but we'd love to hear your questions as well. The other piece of housekeeping is that this will be recorded and sent to you. So if you need to tune out or run out and do something for ten or fifteen minutes, totally get it — you won't miss anything because you'll be viewing the recording.
My name is Dan Moore. I am a principal product engineer at FusionAuth. We are an auth provider that is downloadable, API- and dev-friendly. I've been at FusionAuth for about half a decade and have seen a lot of things, talked to a lot of people, and really, surprisingly, I've actually nerded out about auth more than I thought I would have if you'd talked to me five years ago. It's actually a really interesting, deeply complex piece of technology for something that is so simple as, "Hey, I just wanna get access to this application." There's a lot of nuance. So that's me. Josh, you wanna introduce yourself?
Yep. Hey, everybody. I'm a support lead here — been with the company since we were really small, and now we're not so small, we're very big. It's been exciting to learn all about auth. It's like layer after layer. Every year I dive into this stuff, you learn more and more about it just from customer use cases and how customers build with us. So it's been a real exciting journey so far.
Cool. Great. And as you know if you've signed up for this, we're gonna be talking about how you store OAuth tokens. One thing worth noting is that we are going to be starting from the place where you actually have tokens. We're not gonna talk about the authorization code grant or any of the other grants that are an important piece of any login scenario — that's probably a different webinar. Right here, you've gone through the process, you've gotten the tokens: what do you do with them? But first, Josh, can we talk about what OAuth tokens are and what types there are?
Yeah, great question. The most common kind of token you'll see is an access token, and this is kind of your base form of authentication — what can someone do. It's usually short-lived. The metaphor that's pretty common is it's like a car key. You can get it and you can start the car, but it's made of ice. On a hot day, it's gonna melt in a short amount of time. As long as you have that car key, you can do the things that this car can do.
There's another kind of token as well, called the refresh token. The reason you have a refresh token is because you might want a short-lived access token. What I mean is: if I had an access token that's good for twenty-four hours, that might make sense for something like a coupon app — some service that doesn't really care about how long the user can access something. But for a bank, I probably want to limit my users to only thirty minutes of access. In those cases, I'll have an access token — which lets the user, say, deposit money in my app — and a refresh token, which after the access token expires lets me get a new access token. The reason for this paradigm is that if an attacker gets that access token, it's only usable for thirty minutes. The refresh token minimizes how long the access token has to live, because as long as you're the integrator, as long as you're the user, you'll have access to that refresh token and can get a new access token.
Those are the two main OAuth tokens as part of the authorization code grant. Another layer on top of the authorization code grant is OIDC, which allows you to get something called an ID token. An ID token is really meant to tell you who this user is — their name, surname, family name, that kind of thing. You can put that in a React app, in your front end, to give a nice profile picture and things like that. So those are the three primary tokens. OAuth is complicated, so there are specs and other things — there are other tokens out there, other methodologies — but those are the three you probably need to concern yourself with when building a modern auth framework.
A couple of points. One: you said thirty minutes for the bank, but some banks might want it to be a minute, or ten seconds. I've heard of clients of ours who have one-shot access tokens where it's good for five seconds, and then you're always going back to get a new one from the refresh token. The other thing I'd say — tugging on your metaphor, which I love — is that the access token is the thing that can turn on the car and it's made out of ice. And the refresh token is like this magic thing that, when you present it to — call it a fridge — you can get a new frozen access token, which will immediately start melting again. I mean, now we're stretching the metaphor. But yeah, cool. We have this key that gets us access to things — either it turns on the car, or gets us access to a bank account, or a coupon app. Why do we need to protect these keys?
Yeah, it's a great question — why do you protect your car in the garage? With an access token and a refresh token, it's decoupled. In the past, in older systems, every time a user wanted to do something — like banking in the eighties — you had to call to a central server before the user could even click into a profile page or a deposit page. With OAuth, you get a token. It sits somewhere safe, which we're gonna discuss. And as long as it sits there, I don't have to call back to any server to prove I have the key. I have the key already. So it's a lot more efficient. But if someone gets your access token and we don't protect them, now they can be you. This is what's historically called a man-in-the-middle attack — in the modern era, probably a person-in-the-middle attack — where they're able to intercept and compromise your system to grab your access token and your refresh token. If they get either of those, that's no bueno. You wanna protect them as much as possible.
You mentioned the person-in-the-middle attack — that's one set of attacks. There are others that involve phishing or impersonation, pretending to be the bank, for example. We're not gonna go deep into the class of attacks — we'll mention them — because the type of attack obviously impacts the way you safely secure a token.
But I think we've done a bit of a long walk, and it's worth noting before we start talking about storing: one of the great things about OAuth is its flexibility. It's also a pain point sometimes, because people are looking for guidance — hence why we're producing content like this. The fact is that you can use the same standard with the same taxonomy and nomenclature to secure a coupon app and a banking app. You need to think about your threat model and what happens if someone steals that access token. Obviously, if it's a game with no money involved, that's a totally different risk profile than someone accessing your bank account. That's another sublayer to the question of how and where we store tokens. Do you have any other thoughts on that, Josh?
Yeah, what I think I hear you saying is that, unfortunately, auth isn't one size fits all. And I know this is gonna sound really marketing-y, but whether you're buying from FusionAuth or buying from another auth company or building your own auth — heaven forbid — it's not all one size that works. You really wanna find an auth partner. It could even be colleagues who know a lot about how to secure systems. You might have a security engineer on your team, and you wanna make sure that what you're doing is appropriate for your product, because the same company could have three or four different products with different requirements. When you start to bake in auth, you wanna make sure you've set all your cookies correctly, so to speak.
Oh yeah, there he is — leading the witness. I think that's what that's called. Well, let's jump into the purpose of this, the stated topic. We've given some nice preface about what tokens are, what types there are, and why you need to protect them. So what are ways to do that? Whenever you think about securing data, you want to think about how you secure it in transit — when it's getting shifted around between different places in the network — and at rest. So let's take that first category. Josh, how should I secure my OAuth tokens in transit?
Yeah, great question, and this is the beauty of the modern era. TLS is your go-to there. The easy equivalent is: does the web page have HTTPS on the scheme? Before — I don't know, at some point in the past — there wasn't a guarantee that you had TLS, so you weren't guaranteed a secure connection. All these protocols, including SAML, have accommodations for being in that environment potentially. But in the modern era, especially with OAuth and some of the more modern standards, you have almost a guaranteed TLS connection — but you should still ensure you're doing this. There is one caveat: if you're doing local development, or you can prove you're behind a secure backplane at all times, then potentially you wouldn't need TLS or some sort of in-transit encryption as data moves across the wire. But generally speaking, setting up TLS is one of the main ways to handle that.
Yeah, I think that's totally true. I do think you may have a very high estimation of what computing was like in the eighties — I don't even know that the web browser was born until the early nineties. But yes: TLS, Let's Encrypt, and all the other benefits — Google, bless their hearts, pushed TLS in the early 2010s. Don't do anything over non-TLS, especially unless you're working on localhost. If everything is contained in your laptop, a TLS certificate is overkill. But definitely for any remote dev, staging, or UAT environments, you should be using TLS.
The other thing I'd add is: you want to avoid access tokens getting caught in logs or proxies. That means don't put them in the URL, because you can't control what's happening between your user's browser and your system — things get logged, things get captured by proxies. Don't put tokens in URL parameters. POST bodies are your friend.
Cool. Let's talk about storing tokens at rest. This is a bigger question — you can't just wave the "use TLS" wand — because there are different types of clients storing these tokens. Let's start with a React app or SPA situation. What are some options, Josh, and what are our recommendations?
Yeah, great question. If you'd asked me when I was first starting out writing code and doing front-end stuff, I would have said, "Let's just throw it in local storage." But that is probably not the best path for you.
You get back your access token in your React app, your Angular app, whatever have you, and you decide: I'm just gonna drop this access token in local storage, and then I have access to it. Makes sense, it's a straightforward solution, but it does present security risks. What we see when integrating with our own customers is that legacy systems sometimes have this implementation and move to something better. The reason local storage can be problematic is that if I have any JavaScript running in that browser, it could potentially be malicious and grab the cookies out of local storage, or see when I'm doing asynchronous calls — fetch calls — what the access token actually is.
Most people say, "I check all my code, nothing malicious is running — how could that be?" The reality is that most organizations are going to be installing a lot of packages just to get the base thing running. If you have React, React installs a ton of software dependencies that you don't necessarily know the full pedigree of. If you look at some of the great exploits in internet history, people have dropped malicious code into pretty widely-used repositories. If you're using local storage, they can start to eavesdrop on a lot of your integration, which is not good. So local storage is kind of out as a method to store things. Dan, have you ever seen any fun local storage stories from your past companies and lives?
I have not, actually. Two things before we jump into what we actually do recommend.
First: it's not just that you need to check and trust all these libraries at the time you're building and deploying your React app. You need to trust them in the future, and you need to trust anybody else who adds anything to your application in the far future — and all the dependencies they pull in. That's a lot of trust to extend.
Second: I'm actually a subscriber to AWS Amplify. They have an open-source project used to quickly build applications leveraging the AWS ecosystem, and they still store things in local storage. There are still people, three or four years later, asking, "What's the better way to secure this?" I totally understand how it gets embedded, and I understand that it's a solution people know. But the same way you wouldn't leave your car key lying around in the middle of a parking lot, you should think about other places to store your access tokens and refresh tokens. That's enough doom and gloom — let's talk about the good aspects of storing tokens.
Sorry, real quick — if you joined a little bit late and have questions for us, go ahead and tap the Q&A tab and drop them in. We'd love to react in the middle; we definitely have time for questions at the end too. Go ahead, Josh. Sorry, I stopped you before you were about to go on your knowledge drop.
Oh yeah, no problem. I was actually just looking at the Q&A too, and we had one from earlier.
I think we got that one, right, Dan? So Morris asks: why is the access/refresh token pattern more secure than a long-lived access token? Great question, Morris — I think we addressed it kind of implicitly. Josh, do you have a direct response? I think it's a great question.
Yeah. If someone steals a long-lived access token, now they can do whatever they want with your integration — whatever your integration allows. If I steal an access token for a coupon app, maybe it's not a big deal. But if I get someone's access token and they're an administrator at a bank, that could be a very, very bad situation.
Yep. Two things. One: the whole reason we're talking about this is to try to prevent people from stealing access tokens and refresh tokens — so you store them securely, but if someone was able to inject cross-site scripting and you're storing your access token in local storage, they will get it.
The second thing — and this is the reason why the pattern of access and refresh tokens is a good one — is that the access token is what's presented to APIs or other services as a delegated form of authentication. It says: "The holder of this went through an authentication or authorization process at some point in the past, and now they're presenting it — they should be able to do things: turn on a car, access your bank balance, find a great coupon, whatever that is."
The refresh token is tied to — in the jargon of the spec — the authorization server, the thing that does the authentication. And that has a lot more understanding of the user and their situation. By using a short-lived access token and regular refresh grants, you can centralize the intelligence about stopping creation of new access tokens or revoking them — basically not letting a refresh grant succeed — at the authorization server. Because access tokens are stateless, you don't necessarily get that kind of centralized revocation by default. We do have some articles on our website about revocation of access tokens, and there's a tension here: one of the benefits of using access tokens is that they're decentralized and you don't need to make that call back to the auth server, which means it's scalable and you aren't dependent on a single point of failure if your authorization server goes away. For the lifetime of that access token, you can still get access to the service or API.
But you don't want to be too decentralized, because you want to be able to say: this access token was stolen, I want to revoke the refresh token that corresponds to it — and then no more access tokens. You have this window — ten seconds, a minute, thirty minutes, whatever it is based on your security needs — where there is risk, but they can't get more access tokens. Whereas if you had an access token good for a year, you'd have essentially no way to revoke it.
Yeah. To take our ice car key metaphor further: if I had an elixir that made the world's longest-lasting ice and it could survive a whole day, and that was my long-lived access token — if someone stole it, I couldn't do anything about it because they have the key, and I'm kind of stuck. But if I had a short-lived access token — cheap ice, melts in half an hour — I can create mechanisms in this decoupled system. When this thing melts, go back to the ice maker, the refresh token. And I'm gonna have a blacklist of things I won't let happen. Since I know this car was stolen from me, I'm not going to issue a new key.
We do have some articles on how to do this, and there are actually a few approaches. The benefit, as Dan was saying, of having access tokens is that I don't have to have a helicopter parent checking if this user can do this. In old systems, you had to always check with some server: "Can this user go left? Go right?" With OAuth, you just give them the keys and they do their thing. But there are ways to say: here's a hall pass for thirty minutes, here's a key for thirty minutes — come back, and then use blacklists or other mechanisms to protect yourself in those scenarios.
Yep. I spoke earlier about the flexibility of OAuth and how it's a blessing and a curse. It gives you the tools to build the system that fits your security needs, and therefore you need to think about that system. As Josh mentioned earlier, you need to think about what security needs and risk profile you have. But anyway, Morris, thanks for the question — good question. Let's skip forward. We were talking about local storage. Let's talk about what the other options are for storing tokens.
Yeah. If you don't want to do local storage — which you probably shouldn't — and you still have a React app, Angular app, or whatever the new JavaScript framework hotness is, you may decide you want to store something in cookies. The beautiful thing about the browser is it's built on these APIs that all browsers iterate on slowly, but there are generally accepted standards for things you can do to protect cookies.
You can drop your access token and refresh token in a cookie, but you can't just use any old cookie. You have to make sure it's set as Secure and HttpOnly, and that your SameSite policy is Lax or Strict. The reason you do this is it says: whoever sets this cookie is the only thing that can access it. The domain that sets this cookie has to access it over TLS, and JavaScript — rogue JavaScript or JavaScript I don't expect to be running — can't come in and read it. When I make an asynchronous call in my React app and do a fetch call, if I set fetch with credentials, the browser API actually protects that call because it's going to grab the cookie that's been set in this specific way, and no JavaScript is going to be able to see that. Whereas if you had local storage and were doing some calls, you might be able to see the access token.
If you have everything stored under a single domain and you own that domain and are responsible for it, you're not going to have another person trying to intercept or some other attack vector where they can grab the access token via cookies. That's another option we see and recommend — it's one of the patterns we have a lot of documentation on how to implement.
Yeah. I love that, because by using Secure, HttpOnly cookies, you are leveraging the browser security model. There are a lot of people who spend a lot of time working on that browser security model. Yes, it has some wrinkles and some weirdnesses that are sometimes hard to get your head around. We definitely have people who call us and say, "How do I make these cookies work cross-domain?" And we're like, "I'm sorry — you cannot." You can't directly have a fetch call from JavaScript to example-b.com that carries cookies set on example-a.com. They just will not work — that's a fundamental tenet of the browser. So by leveraging cookies for your access tokens and refresh tokens, you're basically getting the benefit of hundreds of years of people working on the security model and trying to make it accurate and safe.
But that doesn't mean cookies are right for every solution. What are some of the drawbacks of cookies for this particular situation, Josh?
Yeah. The drawback is, of course, you have to own the domain and have everything under that same domain. If, for instance, I'm a media company and I have a radio app under my-favorite-radio.com, and then I have another app — a painting app — under paintmagic.com, it's going to be really hard using just the browser APIs to share OAuth information between those two domains. There might be use cases where I need to share state or other things across them.
In those cases, you really want to reach for a slightly different tool — similar in idea, but just a different tool. You may have heard this term: backend for frontend (BFF). The idea is you create a backend server, set a session, and store your tokens in that session. That backend server drops session cookies in each app you're accessing. Every time your app needs to access a protected path, even across domains, it has the session cookies stored locally, and it sends off a request. It goes to your app server, which looks up the session, finds an access token, and can forward it on to the cross-domain app that needs it, check its validity, or whatever have you. It's a very straightforward way — I shouldn't say "easy" because auth is hard, but it's straightforward — to build across domains or when your systems are diverse and you want a centralized landing spot for all your tokens to be stored in an app session.
Yeah. I love this because it's almost like a lightweight proxy that handles — it's like a bag for your access tokens. And because it's server-side, there are a couple of other interesting attributes. First: you can actually revoke access tokens, because you control the server. You can say, "This person logged out," or their account is delinquent, or they changed their password — and you can wipe away the access tokens, which gives you a lot of flexibility. It also gives you flexibility because if you use cookies only, all your APIs need to be able to read your access token from your cookie header. Some legacy APIs or third-party APIs you don't control might not understand that. Whereas the server-side component can put the access token wherever it needs to be — whether it's in the Authorization header, an X-API-Key header, or wherever — because it's server-side code and you don't have to worry about malicious JavaScript stealing it.
The downside of the BFF pattern in my experience is it's one more piece of code to maintain, and it also becomes something of a single point of failure — not that it has to be, you can scale it out and it doesn't need to be complicated code, but it is a single place all your app requests need to go to. With a cookie solution, the client is distributed and can talk to whatever APIs it needs to talk to — again, as long as they're on the same domain. So it's a trade-off. Do you have any other thoughts on the BFF pattern, Josh?
Yeah. In tech, if you can do cookies and you have the ability — let's say I have that media company and the painting app — and you have the ability to maybe reassign domains and have everything under one main company domain, that's probably the path I'd choose, because you've simplified your entire setup. But for some products, moving a user base or making the technical changes to bring everything under one shared domain — especially for auth cookies — just isn't possible. Then you're kind of left with either using the BFF pattern, or there are probably less common use cases too. If you're on a mobile app, there are some storage solutions there. But generally speaking, these are the two common paradigms people use, and you're driven by what you have available. Can you control the domain? Great — cookies are one less thing to build, they'll be secure, and until Google decides to get rid of them entirely — which they threatened to do in the past — we're going to be fine. If you can't control the domain, you need to set up a session and build a server for the BFF pattern.
Yep. And you kind of alluded to this — it feels to me like more server-side rendered apps are very similar in their considerations: if you just have a little bit of JavaScript sprinkled in but you're mainly in a server-side rendered page built in Rails, Java, or whatever, you pretty much have the same two choices. Is that your assessment?
Yeah, Rails gives you a lot of nice helpers — I'm a Ruby guy. But I also appreciate a good bit of code in any language. You have these two choices in front of you, and you also have a lot of built-in helpers you can just use. If I was using Rails, I'd just throw it in the session and it's ready to go. Is that what you're getting at, Dan?
Well, I think the bigger point is that we've talked about the React SPA situation and outlined the three options: local storage — don't do it; cookies — if you can; and BFF — if you must. That "if you can" and "if you must" depends on your needs. I'll drop some documentation that walks you through some of these choices, because — as Josh said — auth is multi-faceted. It's very hard to give general advice. Whether you're a SPA or a more server-side browser app, the same applies. Let's talk about mobile apps, which are another major classic client type with a different set of trade-offs for storing OAuth tokens.
Yeah, mobile apps are fun. If you have a mobile app, we recommend storing tokens in — I'm an iPhone guy, don't hate me if you have the green messages — Keychain. If you're using something like AppAuth, which you'll see in a lot of our documentation, or other helper libraries that wrap your auth into a nice function you can call to store an access token, you don't have to worry about it. You just need to know how the function is storing it — as long as it's storing it in an encrypted vault, which is what Keychain is on the iOS side, as I understand it. Android has its own implementation called KeyStore, which lets you do the same thing. Mobile gets a little tricky, but generally speaking, if you can use a helper library that integrates auth with either Android or iOS, that's going to make your task a lot simpler.
Yeah. Even though the words you used — Keychain and KeyStore — are different from Secure, HttpOnly cookies, they're serving the same purpose. You have this key, which is the access token or the refresh token, and you need to make sure nobody else can get access to it. For browsers, it's Secure, HttpOnly cookies, which have their own characteristics. The mobile world is a little more flexible in terms of native apps, but you still want to store it somewhere that no other apps can access. That's the fundamental thing — because that eliminates that entire class of attacks where someone finds and exfiltrates your access token and uses it to impersonate you and get access to your systems.
Yeah. When we talk about mobile apps, we usually hear people say, "Storing in Keychain is great." But then another buzzword we hear a lot is PKCE. I just want to make mention of that. PKCE is a little bit different from token storage. So if you're new to auth and you're trying to figure out why Next.js is throwing on state or PKCE or doing some of these things, that's just a different protocol — it's more about binding the initial request to the actual authorization server request for the token. We have other talks that go into that; I just want to dispel that real quick.
The other thing I was going to mention — and maybe this is just me — when I first started learning about all this stuff four or five years ago, I didn't quite understand: I can store in the browser, I can store in mobile storage. My thought was: what happens if someone just walks up to my computer and looks at my cookies? In that case, you're kind of screwed either way. I just wanted to mention that because it was confusing to me — I didn't quite understand what kinds of attacks we're talking about. If someone gets a hold of your computer and has access to your browser, it's kind of game over at that point. That's more about the physical security policies you have in place to prevent that. What we're talking about is the broader attack vector of: you're just cruising online, which is what you usually do. That's what we're trying to protect you from.
Yeah. If you ask someone how many people have access to the set of sites they visit online during the day — all the employees who work at all those different sites — versus how many people have access to their house, the second category is probably a fraction of a percent.
It's a really good point, and there actually are solutions to deal with physical access, which takes us into our next topic — more esoteric storage options. One of these is storing tokens in memory. You get back the access token, you put it in memory, and that's great because it's really hard to get access to browser memory. If you have physical access to a computer, you might be able to — if you're capable of reading hex and can log in and do all those things. But the issue with in-memory storage is that if you reload the page in the browser, or restart the phone, or restart your app, that memory is not guaranteed to be there. You then have to go through the authentication process again. For certain kinds of situations — and this gets back to the risk modeling discussion — you may need to store the access token in memory and be willing to accept the user experience friction of having them log in again.
But there are some other standards-based options too, Josh — mTLS and DPoP. Do you wanna talk about those?
Those are more emergent technologies. We do have some documentation on them. Generally speaking, they're not widely adopted. What they do is what they call token binding. Even our own engineering team is looking at how to build this into our product in a seamless way. For now, they're something to keep on your radar. If you do have a use case for them, there are solutions from auth providers and other software to implement them — DPoP and mTLS (mutual TLS) are both emergent technologies, and as the web moves forward I imagine we'll see them more commonly used. They fall under the umbrella of token binding.
I'd also say, practically: if I was logged into Facebook and they put their tokens in the browser in memory, and I close my tab and go back, I have to log in to Facebook every time. Is that a good user experience? Facebook's marketing people, who make a lot of money, would probably say no — so they don't do that. But let's say I was part of the CIA or some cool operative — well, look at me, I'm probably not gonna be part of the CIA anytime soon, but let's just say — maybe it's okay that every time a user closes the browser, they have to log in again. So Dan was talking about your risk posture and risk exposure. If I close the browser window and the tokens are only in memory, I open it again and I have to log in — and that's kind of painful in certain situations. You can look at our documentation where we describe that option, as well as token binding and a few other methodologies to consider for token storage.
Great. To give people a quick overview of those two standards-based options: mTLS basically uses client certificates. When you're first logging in, the authorization server looks at the client certificate you presented, makes a hash of it, and puts it in the access token. Whenever you're presenting that access token to the APIs or services you want access to, the resource server gets the access token, sees the hash, and also gets the client certificate — and it can recreate that hash. So it knows that whoever initially got the access token has the same client certificate as the thing presenting to the API.
This kind of pushes the problem down a bit, because now you have to manage deploying client certificates at scale. If this is an internal application and you already control the clients — the laptops, the mobile phones — maybe that's a good solution. We've seen some of that in financial services especially. But as a widely deployed consumer solution, it's just not there yet, and the spec has been out for a couple of years.
DPoP is a similar idea that uses JavaScript's cryptographic technology, and it's a newer one. Both are good solutions. To go back to Josh's analogy: it's a way to have this ice key, but it also has a biometric sensor. So only Josh can put his thumb on it and turn the car. If someone else steals it, they don't have Josh's thumbprint — let's just ignore the case where they cut off his thumb, that's too James Bond for me — and they can't turn the key no matter what. That's what token binding gets you, and it's a great, interesting solution. The problem is that for the consumer market, neither of these is widely deployed in our experience. Any other final thoughts, or should we move on to Q&A?
Yeah. If you remember anything from this talk: the two storage methods. And if your eyes glazed over a bit at DPoP and mTLS, that's fine — unless you're working for a highly secure organization, those are probably not something you need to worry about right now. But if you are, hopefully you have a security engineer you can talk to about implementing those things.
What are those two widely-implemented methods for storing OAuth tokens that we'd recommend? Just to sum up.
Yep. We'd recommend storing in a Secure, HttpOnly cookie with SameSite=Lax or Strict. And if that's not an option, you do the backend-for-frontend pattern: you stand up a server, create a session in that server, and make cross-domain requests using that session.
Cool. It looks like one of our audience members asks: can I store the ID token in the browser? You kind of touched on the ID token way back when we covered what types of tokens there are. How do you feel about that, Josh?
I feel like that's okay. That token is meant to be used in a way that gives you a profile image — the claims generated in that token are meant to be exposed so your front end can use them. So it can be stored in local storage. FusionAuth is pretty extensible so I don't want to call it out exclusively, but other products probably can too. I just wouldn't jam sensitive data into the ID token. You might be able to do that in our product, but I'd advise against it. The standard claims — if someone got your first name, what's that gonna do? There are a lot of Joshes in the world, so I feel like I'm pretty good there. But if you jammed a Social Security number into the ID token, I wouldn't recommend that. For the lightweight purpose of letting your app know who someone is and give them a nice profile picture, that's exactly what the ID token is for.
Yeah, that makes a lot of sense. The reason it's okay to store the ID token in local storage in the browser is that the whole point of it is for the client. It's meant to live there, as opposed to being a key that gets you access to something that needs to be secured.
Another question: someone's asking about decoupled access. You touched on that a little bit earlier, but could you give us an overview of decoupled access, Josh?
Yeah. I think I learned this a while back — I think actually from one of your talks, Dan, three or four years ago. This idea of decoupled access was like, "Alright, more jargon to learn." It's kind of exhausting when you're first getting into auth, and I just want to acknowledge that it's a lot to understand.
Historically, you would have a server that you'd check with every time a user wanted to do something. That's what they call a really chatty connection. At some point, someone said: why are we doing this? Can we store who someone is and what they can do — authn and authz, some more shorthand for you all on this call — somewhere safe and only check when we actually need to? Can the user do the thing? With OAuth, the question becomes: when do I check the access token? That can vary depending on the use case. One paradigm is checking the expiration: I get the token, I say "can this user do this?" — is it expired? There are a few common claims you should check for, and we have documentation on that. Generally speaking, in a decoupled system: I don't need to check every time the user needs to do something. I can just give them this piece of identity, check that it isn't expired, check it hasn't been tampered with, and check the claims for what they can do. And then I'm out. That's way more scalable, because now I don't have this chatty connection. If it's just me and you, Dan, our app has two people — that paradigm is fine. But in a decoupled system with millions of users, that's not going to scale cheaply. I mean, it can scale in a modern world of Kubernetes and EC2, but you have to pay the bill at the end of the month.
Yeah. Go ahead.
No, go ahead — I don't know if you have anything else to add that would fill in where I've left some blanks.
No, I just think it's really worth calling out that this is an architectural choice. We're happy to talk to anybody and work with folks to help them find the right architectural choice. One of the reasons I think OAuth has been around since 2012 and has been really successful is because it gives you this flexibility. But with great power comes great responsibility, as I think of it. Based on timing and how you build your system and what you set those expiration checks to be, you can actually choose to move that dial: do I want more decentralized and greater scalability, or do I want less decentralized and, frankly, less scalability per dollar? That might be a good way to put it, because as Josh said, we have lots of options.
One final question: you keep mentioning "check the validity" and "check the signature." What do you mean by that in the context of access tokens?
Yeah. In an access token, there's going to be a signature value. In a decoupled OAuth scenario, you have a couple of options. Generally speaking, you can call into the auth server if you need to grab a public key. In OAuth, there is this endpoint called the well-known endpoint, and you can get what's called JWKS — which people abbreviate as "JWKS," though it took me five years to figure out the pronunciation. You call this endpoint, you get all the public keys, and then you can say: give me the public key for this access token, and I can do some public key cryptography to verify it hasn't been tampered with. Because if someone took that token and added a claim — like "roles: admin" to try to get elevated access — they would have changed the token, and that's how you'd catch it. You check that signature value. Once you get it, it makes a lot of sense: I have a signature value, I have this expiration value — if I do that, it's like having a sealed Mylar bag, like a bag of potato chips. If you open it and reseal it, and it's not factory-sealed, you're probably gonna notice. It's the same idea with an access token.
Yeah, that makes a lot of sense. In my understanding, there are really two ways an access token can work. The first — which fits well with the scalability properties we talked about — is to have a self-signed JWT (JSON Web Token) as the access token. OAuth 2.0 was codified in 2012; JWTs just celebrated their tenth anniversary, so they came afterwards. In the actual OAuth 2.0 spec, it basically says this access token — we don't really know what it looks like, and you really shouldn't care as a client. The resource server, the API, the services need to know about that. That's what's called an opaque token. We definitely see a lot more of the self-signed JWT approach, and that's what FusionAuth uses and what a lot of other providers do. But you may run into an older OAuth implementation like Facebook that uses opaque tokens, where you always need to go back to the authorization server and say: "I have this thing that was presented to me — tell me more about this user and this request." But yeah, last call for questions.
We got one more: what is a threat surface? This is not a security talk focused on network security, but it's worth defining. Josh, do you have thoughts on that?
Yeah. I'll share the Josh's-view-of-the-world version, which probably isn't the official spec-sheet view, so it'd be interesting to hear your thoughts too, Dan.
When you're looking at your architecture — when I'm engaging with a customer — I just ask some common sense questions: Where is your app situated? How are you storing tokens? Are you using any JavaScript in your login process? What's your registration — how do you get users into your product? What technology stacks are you using? How are you securing those? Some customers have specific questions like: "We're doing this and this and this — what's our threat surface?" It's kind of an evaluation landscape of: we're doing this, we know it has this specific danger zone, and we think we're mitigating that danger with these controls. A threat surface for OAuth, like we've been talking about, would be someone tampering with the token — which you check for by verifying the signature. But it's really just an overall picture of where you could be attacked and how you're mitigating it. You can never mitigate everything — at least in my opinion — but you can definitely cover the 99%, where that 1% attacker with thirty years and unlimited time might still get in. Really, you want to protect against the 99%, because it's always the sticky note on the computer that gets in. That's the path of least resistance. So if you're doing your due diligence and saying: how could we be exposed? How could we be attacked? You want to make sure that at every point where we could be exposed, we have mitigation in place.
Yep. Because we're so deep in the OAuth world, we often think about OAuth threat surfaces specifically. A lot of what we've talked about here — whether that's signing your access token, considering your expiration times, how refresh token revocation works, whether you use the BFF or the cookie — that is all dealing with the threat surface.
Another way you can think about it: we talked about getting into someone's house or stealing a car. You can actually think about the threat surface of more normal human activities, which are way less abstract. The threat surface of your car is everything from where you store your keys to whether you leave the doors unlocked, whether you pull into a garage or leave in the driveway, whether you leave it in a parking lot overnight or for a week when you go on vacation. All of these things play in. Josh, walking down that list of things you commonly ask a customer of ours is a great starting point. These are things you want to think about — not because you want to mitigate every single one of them, because everything has trade-offs. We talked about the friction of requiring someone to authenticate every time they close their Facebook tab. You need to think about them and determine what business and user experience trade-offs you're willing to make to achieve higher security, or what security trade-offs you're willing to make to have more conversion or an easier experience for the user. We're always going to advocate for higher security, because that's where our minds are at. You, as a possible customer or user of FusionAuth or other auth services, know your business a lot better than we do. We just want to make sure you understand what you're walking into and are as educated as possible — that's why we talk about threat surfaces.
So: cookies if you can for storing OAuth tokens, and the BFF pattern is a great option if you need cross-domain access or need to put tokens in different headers or whatnot. That would be my summary. Any final thoughts, Josh? We've only got a couple minutes left.
We've got about a minute left. Auth is complicated — this is one facet of it. But if you're building a product yourself in your basement and you're just trying to figure things out and have fun, try some things out. If you're building with a company, try some things out, follow some of our building-block guides in our article section, and then have someone audit your work: could this be a problem? Could this be a problem? And if you have an auth partner like us, bring us into a conversation. If you're one of our community members, post to the forum and say: I'm concerned about these things — what do you guys think? We love to hear it, whether it's from a community member or a paid customer, and we love seeing the different approaches people are taking. It's great to talk with you all about this.
Josh, I'd say that was forty-seven seconds, so I'm afraid we're gonna have to cut that. No — thank you all for joining us. Really appreciate it. Enjoyed the chance to nerd out about token storage. Thanks for your questions, and we'll see you in the next webinar.