Why is BFF pattern deemed safer for SPA's?
Asked Answered
S

4

17

I am designing a new web application which requires an oAuth2 implementation. I've been reading up on oAuth2 Authorization Code flow with PKCE. That makes sense, it ensures that de client who is initiating the Authorization Code flow is the same client as the one who is exchanging the authorization code for access token (and/or refresh token).

But then I was wondering on how we could deal with refresh tokens. I understand that BFF is now the preferred solution for this, where we use a separate component (Backend for Frontend) that handles all calls from the web app and proxies those to the backend api, all the while handling all access tokens and refresh tokens. The web app and BFF maintain a session cookie, so the BFF can track which access token should be added to which request and so forth.

Most blogs mention something in the lines of "and this is safe if you set the session cookie to strict and http only and secure, because no malicious JS can get that cookie then".

And that is where I have trouble understanding why this is safer. If cookies are safe enough to deal with session id's, then why are they not safe enough to deal with access tokens? Or even refresh tokens? If they are cookie based, then they are sent with each request and no malicious JS can access it. If they could, then the BFF model doesn't provide any additional security, just a little more complexity, no?

So bottomline is: if BFF is deemed safe(r) because the sessions are kept in secure http-only cookies, why is keeping access/refresh tokens in secure http-only cookies not safe?

Sycee answered 24/7, 2022 at 6:49 Comment(0)
P
11

BFF is considered safer not because of the cookie usage when using the access tokens but because the way of obtaining tokens is more secure. SPAs by definition are not able to keep a secret (in the browser) thus have to use a flow that involves a public client. The BFF allows for a confidential client because the client secret is kept in the backend.

Using PKCE with the public client gives you assurance indeed about the same entity requesting and receiving the tokens, but it give you little assurance about the authenticity of that client. A confidential client takes care of the latter.

Polyphyletic answered 24/7, 2022 at 7:41 Comment(7)
Right, but a malicious code could as easily hijack a session cookie as a acces token cookie. And if the BFF is just proxying the request, then what does it matter that the proxied request came from a confidential client to the backend api, rather than directly from the front end? (Sorry if this is completely wrong train of thought on my part)Sycee
your train of thought is on usage, and as mentioned, there's no advantage therePolyphyletic
lets put it this way: in the BFF case, an attacker would still have to hack the original client to get hold of the access token, in the public client case, the attacker does not need to hack anything (just customise their own device/client) to get hold of access tokens meant for the original clientPolyphyletic
But in the BFF case, the original client (I read this as the front-end web app, correct?) is never concerned with tokens, it just holds an session ID. So why does an attacker have to hack it to maliciously obtain access tokens? Capturing the session id from the original client, yields the exact same access as obtaining the access token from the BFF (even more, because the backend API can restrict access only to BFF). Why is it inherently bad that anybody with a valid access token can make requests to an API?Sycee
"Capturing the session id from the original client" is the hack here; the question about the access token is a different one, but yes, having unknown parties access your APIs is considered bad practicePolyphyletic
(Super thanks for the back and forth on this!) But if a malicious actor is able to get the session cookie, then this is not at all safe but exactly the same as using access tokens and refresh tokens in cookies, with the exception that there is a component less to worry about. E.g. in case of BFF: steal session from cookie -> make request to BFF -> get access to user data. In case of no BFF: steal AT/RT from cookie -> make request to API -> get access to user data. I still don't understand how this is safer, I am sorry for not understanding it.Sycee
you're focus is still on storing and using tokens of (to be) compromised clients, but the security improvement is in the realm of token issuance to malicious clientsPolyphyletic
S
25

Adding a BFF to the mix does change the security of using tokens. The BFF is a confidential client so it makes the whole solution more secure, for sure, but this is not the only reason you would use a BFF. One of the main benefits is to keep tokens away from the browser. You don't want the tokens to be accessible by Javascript, that's why you want to rely on cookie-based sessions instead. This protects you from XSS attacks that want to steal the tokens. However, using sessions opens you to CSRF attacks and session-riding attacks. Protecting against CSRF and session-riding should be a bit easier than mitigating XSS as you can become vulnerable to XSS through a third-party library's dependency, for example.

E.g. in case of BFF: steal session from cookie -> make request to BFF -> get access to user data. In case of no BFF: steal AT/RT from cookie -> make request to API -> get access to user data. I still don't understand how this is safer, I am sorry for not understanding it.

You don't have to be sorry! It's good that you're trying to understand that.

The problem with your example is this: you assume that there is no BFF, but AT/RT are kept in cookies. If you're using cookies then it means that you have some sort of a backend component that sits between your SPA and APIs. Otherwise, you would have to deal with tokens in the browser (readable by JS). So the difference that you should be contemplating here is — am I using HTTP-only cookies to call my APIs or do I need tokens and set Authorization headers in JS? For the former case, it doesn't matter if you have a session ID in the cookie or the actual AT. The important part is that JS can't read the content of that cookie.

Another thing is that it's harder to steal sessions than tokens kept in JS. For someone to steal data from cookies you would need a Man-in-the-browser attack. If the tokens are available to JS, then all you need is an XSS attack to steal them.

As I mentioned, using cookies opens you up to CSRF and session-riding attacks, but their impact is limited:

  • the attacker can only perform an attack when the user has an open session. As soon as the user closes the browser no more data can be stolen (whereas, when an attacker steals a token, they can read data for as long as the token is valid)
  • the attacker can only perform the same actions that a user can from the front end. This should also be the case for a stolen AT, but in reality, access tokens usually have too broad privileges, and there are APIs you can call with a token that are not normally accessible from the UI.

At Curity we have spent some time researching this and you can have a look at this whitepaper we wrote about the security of SPAs and the pros and cons of different approaches: https://curity.io/resources/documents/single-page-application-security-whitepaper

Softpedal answered 25/7, 2022 at 11:20 Comment(3)
I never thanked you for this response and the Curity resource. That was an incredible interesting read! Just leaving this comment here to encourage anyone ending up on this page to read that whitepaper!Sycee
Wouldn't we achieve the same result of a BFF if the /token endpoint returned the tokens in cookies? To abide by the RFC, placeholder tokens could be returned in the body, so that when they are sent in the Authorize header, the server would instantly know that it has to read them from the cookies instead.Mesial
There are a few problems with this approach: 1. the /token endpoint is a standard OAuth endpoint and the standard requires the server to return tokens in the body. 2. The API now needs to know about cookies and how to handle them. 3. The API, the SPA, and the Authorization Server must work on the same domain, or the browser might drop the cookies.Softpedal
P
11

BFF is considered safer not because of the cookie usage when using the access tokens but because the way of obtaining tokens is more secure. SPAs by definition are not able to keep a secret (in the browser) thus have to use a flow that involves a public client. The BFF allows for a confidential client because the client secret is kept in the backend.

Using PKCE with the public client gives you assurance indeed about the same entity requesting and receiving the tokens, but it give you little assurance about the authenticity of that client. A confidential client takes care of the latter.

Polyphyletic answered 24/7, 2022 at 7:41 Comment(7)
Right, but a malicious code could as easily hijack a session cookie as a acces token cookie. And if the BFF is just proxying the request, then what does it matter that the proxied request came from a confidential client to the backend api, rather than directly from the front end? (Sorry if this is completely wrong train of thought on my part)Sycee
your train of thought is on usage, and as mentioned, there's no advantage therePolyphyletic
lets put it this way: in the BFF case, an attacker would still have to hack the original client to get hold of the access token, in the public client case, the attacker does not need to hack anything (just customise their own device/client) to get hold of access tokens meant for the original clientPolyphyletic
But in the BFF case, the original client (I read this as the front-end web app, correct?) is never concerned with tokens, it just holds an session ID. So why does an attacker have to hack it to maliciously obtain access tokens? Capturing the session id from the original client, yields the exact same access as obtaining the access token from the BFF (even more, because the backend API can restrict access only to BFF). Why is it inherently bad that anybody with a valid access token can make requests to an API?Sycee
"Capturing the session id from the original client" is the hack here; the question about the access token is a different one, but yes, having unknown parties access your APIs is considered bad practicePolyphyletic
(Super thanks for the back and forth on this!) But if a malicious actor is able to get the session cookie, then this is not at all safe but exactly the same as using access tokens and refresh tokens in cookies, with the exception that there is a component less to worry about. E.g. in case of BFF: steal session from cookie -> make request to BFF -> get access to user data. In case of no BFF: steal AT/RT from cookie -> make request to API -> get access to user data. I still don't understand how this is safer, I am sorry for not understanding it.Sycee
you're focus is still on storing and using tokens of (to be) compromised clients, but the security improvement is in the realm of token issuance to malicious clientsPolyphyletic
E
1

In earlier versions of the OAuth protocol, front-ends would obtain a token using the implicit flow. This was considered insecure because there's no way of telling who had received the token. It was just sitting there in the return-url for anyone to read. So, a better way to obtain an access_token is by using the PKCE flow. The application who requested the token could "proof" it had requested it by including the 'code_verifier' in the request to the /token endpoint. But still there's a problem there.

With PKCE on the client-side, there is still no way of telling who the token is being sent to. Ideally, you want to know you're sending the token to the same computer/application that has requested it (by invoking the /authorize URL) and not to the attacker. To be sure you're sending the token to the correct recipient you must include the client_secret in the token request.

If the client_secret has been compromised, then there's no point in having a client_secret. What I'm trying to say is: You can't have a client_secret in the front-end. Therefor, you must have a dedicated server-side component that has the client_secret and exchanges the authorization code for an access token. This is where the need of the BFF comes from.

Implementing this is not very straight forward. In fact, over the years, implementing OAuth2 based authentication has become more difficult by the year. Therefor, if you want to implement a bff, have a look at the following approaches:

  • You can use the dotnet angular template. It hosts a server-side app which you can use to implement authentication in. It hosts an angular spa too
  • Have a look at blazor.

For more complex applications:

Epinasty answered 2/6, 2023 at 9:15 Comment(0)
S
1

At the end of the day, BFF only looks at protecting the token and move the whole security to something else. AS the OP pointed out, there is still something that can be stolen from the client; the session/cookie may be a bit more difficult to hijack but we are just 1 CVE away from a breach.

The only way to solve this is to have some form of anchors; passkeys, DPoP using a private key with a passphrase / PIN, etc. Anything else, such as this BFF pattern, is just giving a partial answer and is therefore giving a false security feeling.

Stroud answered 18/9, 2023 at 21:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.