DEV Community

Cover image for Web App Security, Understanding the Meaning of the BFF Pattern
Dalibor Kundrat
Dalibor Kundrat

Posted on • Edited on

Web App Security, Understanding the Meaning of the BFF Pattern

Identity and security overview

Identity server with BFF flow

When it comes to security. There is a lot of guidance and also advice and commentary on why you should or should not use each approach. It's hard to understand all the different options and security issues they have.

You also have different clients (mobile/web/app) and each has different security requirements for managing, storing and processing data in the form of tokens, cookies or sessions.

At the end, your app may scale and not all options can be deployed as multiple instance nodes without additional changes to the app and infrastructure.

This means that you really need to make decisions at the beginning and ask questions like: What are your customers? and What are your requirements? and What can be added to the system in the future? and How is it possible to deal with the chosen approach? etc. etc..

This article is part of opensource training.

Authentication vs Authorization

  • Authentication - process of verifying who a user is (identity).
  • Authorization - process of checking what a user has access to (checking permissions to access a particular resource).

Tokens vs Cookie and Sessions

Tokens, cookies and sessions can be interpreted as resources or tools used by various protocols/standards/patterns (OAuth, OpenID, BFF) to perform identity-related tasks.

It is important to understand the basics as they end up being used in combination.

In the end, you can wrap the JWT token in a cookie and use it with session data for 'authorization' and 'authentication'.

Cookies

Cookies can be understood as small blocks of data created by the server that are written once in the response and automatically resent with each subsequent request from the web browser until their lifetime expires.

They can be used for authentication/authorization or tracking and marketing purposes. And much more...

IndentityServer cookies example:
IdnetityServer Cookies in web browsers

There are 3 main properties of cookies:

  • HttpOnly - An http-only cookie cannot be accessed by client-side APIs, such as JavaScript. The browser will not allow you to access this cookie from the front-end code.
  • Secure A secure cookie can only be transferred over an encrypted HTTPS connection.
  • SameSite 3 option values Strict, Lax or None - this tells the browser which domain and address the cookie can be sent to.
What is origin?

The origin is usually ip and port or domain name and subdomain name.

// This are different origins since subdomain are different
https://developer.mozilla.org
https://mozilla.org

// This are also different origins since port number is different
https://localhost:5001
https://localhost:7001
Enter fullscreen mode Exit fullscreen mode
Another cookie definitions:
  • Session cookies - Created only for the browser session (in memory) and deleted/lost after closing.
  • Third-party cookies - Usually the domain attribute of a cookies matches the domain displayed in the address bar of the web browser. as first-party cookies. The third-party cookies does not match the current domain and is used as tracking cookies to track user activity.

Sessions

Session is used to temporarily store information on the server for use across multiple pages of the site. It is usually associated with a cookie that is used to identify the session stored on the server, but does not contain any data.

Session diagram

Tokens

Tokens are data elements that allow application systems to perform the authorization and authentication process. They are usually encoded as base64 strings.

There are several types of tokens:

  • access token - Includes user claims and signs them with a secret. It uses JWT tokens.
  • refresh token - Used to "refresh " and get a new 'access token' after its lifetime expires.
  • id token - JSON encoded data about user profile information
  • etc, etc...
JWT Tokens

JSON Web Token is an open standard that defines how information can be securely transferred between parties as a JSON object.

They are used for authorization and information exchange as they provide a security proof that the information wrapped in them is valid and written by a trusted source.

You can easily write arbitrary data to tokens, sign that data, and then have clients use it to access server resources. The server can verify that the token was signed and is still valid.

Basic JWT token flow example:

Auth diagram JWT token

JWT content:

JWT token

JWT consist of 3 parts:

  • Header - Contains information such as the type of token (JWT) and the signing algorithm used, e.g. HMAC SHA256 or RSA.

    {
    "alg": "HS256",
    "typ": "JWT"
    }
    
  • Payload - Securly signed data (claims)

    {
    "sub": "1234567890",
    "name": "John Doe",
    "admin": true
    }
    
  • Signature - Encrypted header, the encrypted payload, a secret and signed by an algorithm specified in the header.

    HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    secret
    

    More additional info about JWT tokens can be found at: official documentation.

    The most commonly used token for authorizing access to APIs is the Bearer token.

Identity protocols

There are several protocols/specifications available to manage your identity or authorization process.

This was necessary to standardize authentication and authorization between services and clients. So we can use different global identity/authentication providers like Facebook, Google (external/internal) and also standardize the way the process is implemented.

Oauth and OpenId logo

This demo focuses on the most commonly used protocols OAuth and OpenID Connect.

Both protocols use a JWT token by default to encrypt and sign sensitive data, or to verify that the request was sent from a trusted source. It is also possible to use cookies on the front end and let the back end do the session and token authorization for you.

You can also watch and learn from various talks:

OAuth

Primarily used to authorize an app's access to a specific resource. This is done without having to share your password with external sites.

Oauth slack grant prompt example

If you have ever signed in to a new app and agreed to access your contacts, calendar, etc., you have used OAuth 2.0. This protocol does not provide any information about the user's endpoint, just a token to access certain resources. You can read more about OAuth at this document.

Oauth is authorization not authentication

OAuth generally provides clients with "secure delegated access " to certain resources. Imagine you are a Google user and an app wants to access your calendar data. This can be an example of a flow:

OAuth flow example:

Oauth flow explain

In the above example, an application like Slack, Jira, etc. only gets permission to access a specific resource (e.g. the calendar), but not the user itself, so profile data like username and email are not transferred and remain protected.

If you want to learn more about OAuth, you can watch the following presentations:

Token exchange flows

There are several ways in which grant can be substituted. The choice depends on what kind of client is requesting access and how much that client is trusted.

  • Authorization code flow
  • Flow of authorization code with PKCE
  • Implicit Flow
  • Client credentials flow

Oauth grant flow choice Image from Okta

OpenID Connect

OpenID is a protocol for decentralized authentication

A login used by multiple internal/external applications. If you used your Google or Facebook etc. to log in to an external web or app, then you used OpenID Connect.

OpenID Connect is based on OAuth 2.0. (OAuth is the underlying protocol and OpenId is the identity layer built on top of it) and also uses a JWT token called id_token which encapsulates identity claims in JSON format. For more information about OpenId, see under this specification.

id_token example:

{
"iss": "http://server.example.com",
"sub": "248289761001",
"aud": "s6BhdRkqt3",
"nonce": "n-0S6_WzA2Mj",
"exp": 1311281970,
"iat": 1311280970,
"name": "Dalibor Kundrat",
"given_name": "Dalibor",
"family_name": "Kundrat",
"gender": "male",
"birthdate": "0000-10-31",
"email": "d.kundrat@example.com",
"picture": "http://example.com/somepicture_of_dalibor.jpg"
}
Enter fullscreen mode Exit fullscreen mode

OpenId flow example:

OpenId Flow

There are several flows that can be used with OpenId. You can read more about them under this article.

Each OpenId server by specification provides multiple endpoints to interact with.

The URLs of all endpoints can be explored using the global discovery endpoint. Often referred to as disco. It is available under the path: /.well-known/openid-configuration and returns JSON OpenID Connect metadata related to the specified authorization server.

Example disco response:

  {
   "issuer":"https://localhost:5001",
   "jwks_uri":"https://localhost:5001/.well-known/openid-configuration/jwks",
   "authorization_endpoint":"https://localhost:5001/connect/authorize",
   "token_endpoint":"https://localhost:5001/connect/token",
   "userinfo_endpoint":"https://localhost:5001/connect/userinfo",
   "end_session_endpoint":"https://localhost:5001/connect/endsession",
   "check_session_iframe":"https://localhost:5001/connect/checksession",
   "revocation_endpoint":"https://localhost:5001/connect/revocation",
   "introspection_endpoint":"https://localhost:5001/connect/introspect",
   "device_authorization_endpoint":"https://localhost:5001/connect/deviceauthorization",
   "frontchannel_logout_supported":true,
   "frontchannel_logout_session_supported":true,
   "backchannel_logout_supported":true,
   "backchannel_logout_session_supported":true,
   "scopes_supported":["profile","openid","email","role", "offline_access" //etc..],
   "claims_supported":["name","family_name","profile","email", etc..],
   "grant_types_supported":["authorization_code","client_credentials", "refresh_token", //etc..],
   "response_types_supported":["code","token", "id_token","id_token token" //etc..],
   "response_modes_supported":["form_post","query","fragment"],
   "token_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post" ],
   "id_token_signing_alg_values_supported":["RS256"],
   "subject_types_supported":["public" ],
   "code_challenge_methods_supported":["plain","S256"],
   "request_parameter_supported":true,
   "request_object_signing_alg_values_supported":["RS256","RS384" //etc..],
   "authorization_response_iss_parameter_supported":true
}
Enter fullscreen mode Exit fullscreen mode

Main OpenId endpoints:

  • /authorization_endpoint - interacting with the resource owner and obtaining an authorization permission.
  • /token_endpoint - Obtain an access and/or ID token by presenting an authorization permission (code) or refresh token
  • /revocation_endpoint - Revoke an access or refresh token.
  • /end_session_endpoint - Ends the session associated with the specified ID token.
  • /userinfo_endpoint - Provides information about the authenticated end user.


NOTE: All of these values in the discovery endpoint refer to the current server configuration. You can adjust or enable/disable certain options in your code during idnetityserver configuration.

Backend for Frontend pattern (BFF)

BBF is a backend used by a particular front-end application.

Since endpoint APIs may have multiple clients with different requests, BFF can provide a client-specific backend mediator and act as a proxy that forwards and merges multiple requests to different service APIs.

Backend for frontend - BFF example


Ok, we have cookies, tokens, and sessions. We use them for various authentication/authorization protocols (OpenId, OAuth, etc.) and what the hack BFF is good for.?

Answer is:

  • Security reasons
  • Architecture reasons ⠀

Security reasons

In recent years, it was common to implement OpenID Connect for SPAs in Javascript (React, Angular, Vue...), and this is no longer recommended:

  • Using access tokens in the browser has more security risks than using secure cookies.
  • A SPA is a public client and cannot keep a secret, as such a secret would be part of the JavaScript and could be accessible to anyone inspecting the source code.
  • Recent browser changes to prevent tracking may result in 'third-party cookies' being dropped.
  • It is not possible to store something securely in the browser for a long period of time, as it can be stolen by various attacks.

Due to the above issues, the best security recommendation for SPA is to avoid storing tokens in the browser and create a lightweight backend to help with this process, called Backend for Frontend pattern (BFF).

This way, you can still use acces_tokens to authorize access to all your APIs, but clients will use session cookies or tokens in case of mobile devices and BFF will proxy this sides.

BFF can be:

  • statefull - stores tokens in memory and uses a session to manage them.
  • stateless - stores the tokens in encrypted HTTP-only, same-page cookies.

Architectural reasons

When you design your application, you have several options on how to access APIs from clients (web/mobile/external).

1) A single API gateway that provides a single API for all clients
2) A single API gateway that provides an API for each type of client
3) A per-client API gateway that provides an API to each client.

BFF vs API gateway

While an API Gateway is a single entry point into to the system for all clients, a BFF is only responsible for a single type of client.

Backend for frontend vs API gateway

BBF cookies termination and token isolation

As mentioned in the text, the most important thing:

  • Avoid storing tokens in the browser. (No tokens in the browser policy).
  • Store tokens on the server side and use encrypted/signed HTTP-only cookies.

Recommended BFF pattern to secure SPA frontends:

Backend for frontend  (BFF) cookie and tokens flow + proxy

  • Using this, all communication from the SPA frontend to the authorization server now passes through the BFF and tokens do not reach the SPA.
  • The BFF now issues session cookies. These are part of the request to APIs and are exchanged for an access token at the proxy level.
  • Client-side cookies are terminated by the BFF proxy.

What's next?

Next time I will show you concrete implementation of BFF.

Repository

You can find the full source code of the app, including identity, distributed logging, tracing and monitoring, in the open source Github repo:

https://github.com/damikun/trouble-training

Top comments (15)

Collapse
 
xspynks profile image
Rafael de Paula

KIller article! Thanks.

Collapse
 
mihaiandrei97 profile image
Mihai-Adrian Andrei

Awesome article! A little question if you don't mind. Should both the access token and refresh token be saved in the http only cookies? I have an express server that provides the tokens, and a nextjs bff. And I'm not sure if nextjs should store both of them in http only cookies, or encrypt them somehow :D

Collapse
 
damikun profile image
Dalibor Kundrat • Edited

The think about BFF is that you do not store Access or Refres Token in client cookie at all... Client have only session cookie and make request to BFF =>The BFF adds data to request like accesstoken (that is stored on BFF server side) => and proxy request to API...

In case token is invalid BFF req. automaticaly new token From ID provider... Client have no idea what is happening behind the scenes.... Client have only session cookie...

In case your NextJS is BFF server it store access and refresh token in some DB/Table (SessionStore) and with all requests looks on client session cookie based on identifier search for token in this database/redis and adds this to request and query that from API.... basicaly BFF is some man-in-middle... But it is server side so thats main reason why it is more secure like storing this data on client...

Important:

Clinet <> BFF -> Session cookie (no tokens just BFF client identifier)
BFF <> API -> BFF proxy request to API and adds access_token to headers...
BFF <> IdentitiyServer -> Query refresh token automaticaly...

Refresh token is holded on BFF and only used for renew and deleted on logout... (invalidated). You dont send refresh token to Client or API... Only to ID provider to renew...

Collapse
 
mihaiandrei97 profile image
Mihai-Adrian Andrei

This is much clear now. Thank you!

Collapse
 
andreideholte profile image
Andrei Deholte

In the case of my SPA application not having a login and password, how do I ensure the security of requests between my SPA and BFF without storing tokens on the SPA side? This has been my main concern, and I've been considering server-side rendering with Next.js.

Collapse
 
vsavic profile image
Vladica Savic

Very nice article.

Collapse
 
gsustek profile image
gsustek

End user identity is contained in JWT stored in BFF(he is OIDC confidential client). I assume that API services are Resource servers.. My question is, what if we have chained API service calls one after another(API Service 1 i calling API Service 2), booth of them are Resource Server, how the edge cases are solved with JWT expiration, because RS are bearer only clients...?

Collapse
 
bartogabriel profile image
Gabriel

Nice article!
I have a question for you. Do you put the identity server on a domain that is shared between the mobile app and the web?
Example:
login.example.com -> identity server
app.example.com -> BFF web
apimobile.example.com-> BFF mobile

Or put everything on the same domain:
example.com/ -> BFF website
example.com/login -> IS
example.com/apimobile -> BFF mobile

I usually use the first approach, for a user who logs in from the web. When you enter the mobile app and are redirected to login.example.com, you are already logged in.

I don't know if there is a better approach.

Collapse
 
damikun profile image
Dalibor Kundrat

I personali had it under equal domain since Google cloud allow you to do this mapping domain>IP easy...

But most of companies use separate subdomain as identity.example.com... or (login.example.com)

If you use different domain you need to ensure CORS allow list is properly configured..

Collapse
 
carlosabud profile image
Carlos A.

nice article!

Collapse
 
akilaweerat profile image
akilaweerat

nice article, waiting for the concrete implementation article too

Collapse
 
frankozgul profile image
frank-ozgul

Hi, Thanks for the article. I would like to ask the fate of the promised follow-up article. :))
Thanks a lot.

Collapse
 
easonzen profile image
dada

Nice article