Authentication is hard. Many people have been struggling to find better ways to secure the browser-based application, especially with SPAs. Traditionally, websites use cookies to authenticate user requests, then with SPAs people moved to using token for authentication. Let’s review how the cookies and token authentication works and the differences between them.
Cookies-based authentication
Cookies are small pieces of data created by a web server and placed on the user’s web browser. The browser will automatically send them for subsequence requests in the same domain. Authentication cookies are used by web servers to authenticate that a user is logged in.
The advantages
- Cookies are managed by the browser. It is automatic.
- You can prevent client JavaScript to read them by setting the HttpOnly flag to true. This will prevent cross-site scripting (XSS) attacks on your application to steal them or manipulate them.
The downside
- It is vulnerable to cross-site request forgery attacks (XSRF or CSRF). Although there are workarounds to mitigate this threat, the risk still there. Recently the major browsers have introduced SameSite attribute that allow us to decide whether cookies should be sent to third-party websites using the Strict or Lax setting.
- Cookies is not friendly with REST APIs
Token-based authentication
The web browser will receive a token from the web server after it has verified the user’s login detail. Then in subsequent requests, that token will be sent to server as an authentication header.
The advantages
- Unlike cookies, token is not automatically received or sent to server. It has to be done by JavaScript. Therefore, it is invulnerable to cross-site request forgery attacks (CSRF)
- Token is friendly with REST APIs
The disadvantages
- Because the token must be read and sent by JavaScript so it is vulnerable to cross-site scripting (XSS)
- Granting, storing and renewing token is complicated. In 2012 when the OAuth2 RFC was released, the implicit flow is the recommended way for SPAs. However, it has many drawbacks, the main concern is that the access token is delivered to browser via a query string in the redirect URI, which is visible in the browser’s address bar, the browsers history. The access token can also be maliciously injected. The implicit flow is deprecated by code flow with PKCE. Regarding to any approaches, the token has to be stored in the browser, and this is a risk.
Authenticate SPAs with Backend for Frontend (BFF)
So, which is the best option for authenticating SPAs. A recommended way is bringing the SameSite cookies and token together with BFF pattern. Recently we have developed a sample microservice application in Java called Yet Another Shop or Yas for short. In this project we have applied BFF pattern to implement authentication with Spring Cloud Gateway, used Keycloak as the authentication server and next.js as the frontend. Let take a look at the diagram below.

The BFF work as a reverse proxy for both Next.js and resource servers behind. The authentication between Browser and BFF is done by cookies. The BFF takes the OAuth2 client role and authenticate with Keycloak by OAuth2 code flow using spring-boot-starter-oauth2-client. When received the access token, BFF keeps it in memory and automatically append it along with api requests to resource servers.
With this implementation, we can take out the risk of storing token in the browsers. Renewing tokens also handled automatically by the OAuth2 client. Below is the excerpt of the pom.xml of the backoffice-bff
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
And the Spring Cloud Gateway configuration
spring:
application:
name: backoffice-bff
security:
oauth2:
client:
provider:
keycloak:
issuer-uri: http://identity/realms/Yas
registration:
api-client:
provider: keycloak
client-id: backoffice-bff
client-secret: ********************
scope: openid, profile, email, roles
cloud:
gateway:
routes:
- id: api
uri: http://api.yas.local
predicates:
- Path=/api/**
filters:
- RewritePath=/api/(?<segment>.*), /$\{segment}
- TokenRelay=
- id: nextjs
uri: http://localhost:3000
predicates:
- Path=/**
Hoping this post will give you some knowledge about authentication for SPA with BFF. For the detail implementation please check out the source code of Yas on GitHub https://github.com/nashtech-garage/yas