Streamlined data representation and a well-focused interface for the front end are priorities for many segments like e-commerce. In such scenarios, Backend for Frontend (BFF) authentication is one of the most efficient options. Its security also makes it a coveted tool, as with the rise of microservices and distributed architectures, implementing a robust authentication strategy has become crucial.
User experience plays a pivotal role in many businesses, and its impact is discernible in an event-driven architecture. BFF is gaining more momentum where user experience is involved. Using BFF, companies can improve end-user customer experience on their UI by providing near-real-time visual updates.
The Backend for Frontend (BFF) authentication pattern is a design approach that provides a dedicated backend to handle and address all authentication requirements and challenges of the frontend application (SPA).
From my experience with startups, I have realized that knowing what the Backend for Frontend (BFF) authentication pattern is and its implementation in the Go programming language can become decisive for developers. The blog will provide a comprehensive overview of the pattern, its benefits, and the challenges it solves in the context of building authentication systems for front-end applications.
BFF pattern- A brief history
SPAs typically use token-based authentication, such as JSON Web Tokens (JWTs), to authenticate and authorize users. However, managing tokens securely in SPA can be challenging. Storing tokens in client-side storage (e.g., local storage or cookies) can expose them to potential attacks like cross-site request forgery (CSRF). Developers must implement stringent security measures to protect tokens and prevent unauthorized access or misuse.
The BFF pattern solves this problem by introducing an intermediate layer - the Backend for Frontend. This layer acts as a proxy between the front-end client and the main backend services, handling authentication-related concerns and providing a specialized authentication interface.
BFF Flow Diagram
- When the frontend needs to authenticate the user, it calls an API endpoint (/login) on the BFF to start the login handshake.
- The BFF uses OAuth2 Authorization Code Flow to connect with Auth0 to authenticate and authorize the user and get the id and access tokens.
- The backend stores the user’s tokens in a cache.
- An encrypted cookie is issued for the frontend representing the user authentication session.
- When the frontend needs to call an external API, it passes the encrypted cookie to the BFF together with the URL and data to invoke the API.
- The BFF retrieves the access token from the cache and makes a call to the backend API including that token on the authorization header.
- When the external API returns a response to the BFF, this one forwards that response back to the frontend.
Implementing BFF in Go
As a prerequisite to fully understand the proposed solution, I recommend that you get an idea about the following topics if you are not aware of them already.
Project Structure
Starting BFF implementation in Go
In order to kickstart the implementation process, I referred to the Auth0 Guide to register my web application. Following the guide, I successfully set up user authentication for the web application using Auth0. With the initial authentication functionality in place, I proceeded to modify the code to incorporate the Backend for Frontend (BFF) Pattern.
- router.go
func (router *router) InitRouter(auth *authenticator.Authenticator, redis interfaces.IRedisLayer) *iris.Application {
app := iris.New()
loginHandler := controller.LoginHandler{Auth: auth}
callbackHandler := controller.CallbackHandler{Auth: auth, RedisClient: redis}
logoutHandler := controller.LogoutHandler{RedisClient: redis}
backendApiHandler := controller.BackendApiHandler{RedisClient: redis}
middlewareHandler := middleware.MiddlewareHandler{RedisClient: redis}
app.Get("/login", loginHandler.Login)
app.Get("/callback", callbackHandler.Callback)
app.Get("/logout", logoutHandler.Logout)
// Backend Api
app.Post("/shorten", middlewareHandler.IsAuthenticated, backendApiHandler.WriterRedirect)
return app
}
In router.go code, I have set up a web server using the Iris framework and defined routes for handling HTTP requests. It also includes middleware to validate user login information and secure cookies.
- Authenticator
Authenticator defines an Authenticator structure and related methods for authenticating users using OpenID Connect (OIDC) and OAuth2.
I have defined two methods here New() and VerifyIDToken()
- New() Method
func New() (*Authenticator, error) {
provider, err := oidc.NewProvider(
context.Background(),
"https://"+config.EnvVariables.Auth0Domain+"/",
)
if err != nil {
return nil, err
}
conf := oauth2.Config{
ClientID: config.EnvVariables.Auth0ClientID,
ClientSecret: config.EnvVariables.Auth0ClientSecret,
RedirectURL: config.EnvVariables.Auth0CallbackURL,
Endpoint: provider.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
}
return &Authenticator{
Provider: provider,
Config: conf,
}, nil
}
The New method creates a new instance of the Authenticator structure by instantiating an OIDC provider with the specified Auth0 domain, and configuring the OAuth2 client with client credentials and callback URL.
- VerifyIDToken Method
func (a *Authenticator) VerifyIDToken(ctx context.Context, token *oauth2.Token) (*oidc.IDToken, error) {
rawIDToken, ok := token.Extra("id_token").(string)
if !ok {
return nil, errors.New("no id_token field in oauth2 token")
}
oidcConfig := &oidc.Config{
ClientID: a.ClientID,
}
return a.Verifier(oidcConfig).Verify(ctx, rawIDToken)
}
The VerifyIDToken method verifies that an OAuth2 token contains a valid ID token. It extracts the ID token from the token’s extras and uses the OIDC verifier to verify its authenticity and validity.
You may interested in: gRPC in Golang
Note: It is crucial to ensure that all the environment variables of the Auth0 application are properly configured and set. This step is necessary to successfully initialize the OAuth2 provider.
Conclusion
Here are the takeaways:
- BFF authentication pattern, highlighting its benefits and the problems it addresses.
- The flow diagram illustrates the step-by-step process of BFF authentication, from initiating the login handshake to making authorized API calls.
- The implementation for the `router.go` and `authenticator` structure with its methods have been introduced which forms the base component of the implementation.
In the next blog, we will look into the implementation of each of the controller in depth.
See you in the next part. Happy Coding!