BFF is an OpenID Connect Relying Party and OAuth 2.0 client for the Fox router. It provides a set of handlers and middleware to build a Backend For Frontend, the server-side gateway between a browser application and its APIs, as described in OAuth 2.0 for Browser-Based Applications.
The current API is not yet stabilized. Breaking changes may occur before v1.0.0 and will be noted on the release note.
go get -u github.com/fox-toolkit/bffpackage main
import (
"context"
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
"github.com/fox-toolkit/bff"
"github.com/fox-toolkit/fox"
"github.com/tink-crypto/tink-go/v2/aead/subtle"
)
func main() {
provider, err := bff.NewProvider(context.Background(), "https://idp.example.com")
if err != nil {
log.Fatal(err)
}
aead, err := subtle.NewAESGCM([]byte(os.Getenv("BFF_KEY")))
if err != nil {
log.Fatal(err)
}
fe, err := bff.NewFrontend(bff.Client{
Provider: provider,
ClientID: "app",
ClientSecret: os.Getenv("CLIENT_SECRET"),
RedirectURL: "https://app.example.com/bff/callback",
PostLogoutRedirectURL: "https://app.example.com/",
Scopes: []string{"openid", "profile"},
}, aead)
if err != nil {
log.Fatal(err)
}
api, _ := url.Parse("https://api.example.com")
proxy := &httputil.ReverseProxy{
Rewrite: func(r *httputil.ProxyRequest) { r.SetURL(api) },
Transport: bff.Transport(nil),
ErrorHandler: bff.ProxyErrorHandler,
}
f := fox.MustRouter()
f.MustAdd(fox.MethodGet, "/bff/login", fe.LoginHandler())
f.MustAdd(fox.MethodGet, "/bff/callback", fe.CallbackHandler())
f.MustAdd([]string{http.MethodGet, http.MethodPost}, "/bff/logout", fe.LogoutHandler())
f.MustAdd(fox.MethodGet, "/bff/user", fe.UserHandler())
// Proxy to the resource server, with the user's access token.
f.MustAdd(fox.MethodAny, "/api/*{path}", fox.WrapH(proxy), fox.WithMiddleware(fe.Authenticate()))
// The browser-based application is served here by the BFF. For other deployments, see
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps#section-6.1.2.5. A cross-origin
// application requires bff.WithTrustedOrigins and a CORS middleware that allows its origin with credentials.
f.MustAdd(fox.MethodGet, "/*{filepath}", fox.WrapH(http.FileServer(http.Dir("dist"))))
log.Fatal(http.ListenAndServeTLS(":8443", "cert.pem", "key.pem", f))
}BFF implements Open Standards set by the IETF:
- The OAuth 2.0 Authorization Framework
- The OAuth 2.0 Authorization Framework: Bearer Token Usage
- OAuth 2.0 Token Revocation
- Proof Key for Code Exchange by OAuth Public Clients
- JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants
- Resource Indicators for OAuth 2.0
- The OAuth 2.0 Authorization Framework: JWT-Secured Authorization Request (JAR)
- OAuth 2.0 Pushed Authorization Requests
- OAuth 2.0 Authorization Server Issuer Identification
- OAuth 2.0 Demonstrating Proof of Possession (DPoP)
- Best Current Practice for OAuth 2.0 Security
and the OpenID Foundation:
- OpenID Connect Core 1.0
- OpenID Connect Discovery 1.0
- OpenID Connect RP-Initiated Logout 1.0
- OpenID Connect Back-Channel Logout 1.0
- FAPI 2.0 Security Profile
- FAPI 2.0 Message Signing (signed authorization requests)
BFF passes the client test plans of the OpenID Foundation conformance suite v5.3.3: FAPI 2.0 Security Profile and FAPI 2.0 Message Signing with the configuration below, and OpenID Connect Basic, refresh token, Back-Channel Logout and RP-Initiated Logout with a client secret.
fe, err := bff.NewFrontend(bff.Client{
Provider: provider,
ClientID: "app",
ClientKey: key, // EC P-256, RSA or Ed25519 private key
RedirectURL: "https://app.example.com/bff/callback",
Scopes: []string{"openid"},
}, aead,
bff.WithPushedAuthorization(bff.PushedAuthorizationRequired),
bff.WithDPoP(true),
bff.WithRequestObject(true), // FAPI 2.0 Message Signing only
bff.WithSigningAlgorithms("PS256", "ES256", "EdDSA"),
)