Documentation
¶
Overview ¶
Package bff provides the building blocks of a Backend for Frontend (BFF) for Fox. It logs users in with OpenID Connect, keeps their tokens in an encrypted session, and sets the access token on the requests to the APIs called on their behalf.
Index ¶
- Constants
- Variables
- func AccessToken(ctx context.Context) (*oauth2.Token, bool)
- func AllowAnonymous() fox.RouteOption
- func DefaultUnauthorized(c *fox.Context)
- func ProxyErrorHandler(w http.ResponseWriter, r *http.Request, err error)
- func ResourceAccessToken(ctx context.Context, resource string) (*oauth2.Token, error)
- func ResourceTransport(base http.RoundTripper, resource string) http.RoundTripper
- func Transport(base http.RoundTripper) http.RoundTripper
- func UserClaims(ctx context.Context) (map[string]any, bool)
- type AEAD
- type Client
- type Frontend
- func (fe *Frontend) Authenticate() fox.MiddlewareFunc
- func (fe *Frontend) BackchannelHandler() fox.HandlerFunc
- func (fe *Frontend) CallbackHandler() fox.HandlerFunc
- func (fe *Frontend) LoginHandler() fox.HandlerFunc
- func (fe *Frontend) LogoutHandler() fox.HandlerFunc
- func (fe *Frontend) Unauthorized(c *fox.Context)
- func (fe *Frontend) UserHandler() fox.HandlerFunc
- type Option
- func WithAbsoluteTimeout(d time.Duration) Option
- func WithCSRFHeader(name, value string) Option
- func WithDPoP(enable bool) Option
- func WithIdleTimeout(d time.Duration) Option
- func WithLogHandler(h slog.Handler) Option
- func WithPushedAuthorization(mode PushedAuthorization) Option
- func WithRequestObject(enable bool) Option
- func WithResources(resources ...string) Option
- func WithSigningAlgorithms(algs ...string) Option
- func WithStore(s store.Store) Option
- func WithTrustedOrigins(origins ...string) Option
- func WithUnauthorizedHandler(handler fox.HandlerFunc) Option
- func WithUserInfo(enable bool) Option
- type Provider
- type ProviderOption
- type PushedAuthorization
Constants ¶
const ( // DefaultIdleTimeout is the default idle timeout of a session. DefaultIdleTimeout = 30 * time.Minute // DefaultAbsoluteTimeout is the default absolute timeout of a session. DefaultAbsoluteTimeout = 8 * time.Hour )
Variables ¶
var ErrInvalidConfig = errors.New("invalid config")
var ErrNoSession = errors.New("no session")
Functions ¶
func AccessToken ¶
AccessToken returns the access token of a request authenticated by Frontend.Authenticate. It reports false for an anonymous request and for a request not handled by the middleware. The returned token has no refresh token. A DPoP-bound access token must be sent with Transport.
func AllowAnonymous ¶
func AllowAnonymous() fox.RouteOption
AllowAnonymous returns a route option that lets the requests without a session through Frontend.Authenticate. These requests have no access token.
func DefaultUnauthorized ¶
DefaultUnauthorized responds with 401 Unauthorized.
func ProxyErrorHandler ¶
func ProxyErrorHandler(w http.ResponseWriter, r *http.Request, err error)
ProxyErrorHandler is an ErrorHandler for an [httputil.ReverseProxy] that uses Transport or ResourceTransport. A request whose session ended gets the response of the unauthorized handler. The other errors are logged and respond with 502 Bad Gateway, or 500 Internal Server Error for a failure of the session store. It must only be called by the reverse proxy.
func ResourceAccessToken ¶
ResourceAccessToken returns the access token of the resource for a request authenticated by Frontend.Authenticate, as defined by RFC 8707. The resource must be declared with WithResources. The token is requested from the provider when the session has none or when it expires. It returns ErrNoSession for an anonymous request, for a request not handled by the middleware and when the provider ends the session. The returned token has no refresh token. A DPoP-bound access token must be sent with ResourceTransport.
With a session kept in the cookie, the token must be requested before the response is written, so the new cookie can be set.
func ResourceTransport ¶
func ResourceTransport(base http.RoundTripper, resource string) http.RoundTripper
ResourceTransport returns an http.RoundTripper like Transport, with the access token of the resource as returned by ResourceAccessToken. A request without session on a route with AllowAnonymous and a request for a resource that is not declared are sent unchanged. A request fails without being sent when the access token cannot be obtained, with ErrNoSession when the provider ends the session. The reverse proxy should use ProxyErrorHandler.
func Transport ¶
func Transport(base http.RoundTripper) http.RoundTripper
Transport returns an http.RoundTripper that sets the Authorization header from the access token of the request context, as returned by AccessToken, and the DPoP proof of a DPoP-bound access token. A request without access token is sent unchanged. A nil base uses http.DefaultTransport.
func UserClaims ¶
UserClaims returns the claims about the user of a request authenticated by Frontend.Authenticate, as returned by Frontend.UserHandler. It reports false for an anonymous request and for a request not handled by the middleware.
Types ¶
type AEAD ¶
type AEAD interface {
Encrypt(plaintext, associatedData []byte) ([]byte, error)
Decrypt(ciphertext, associatedData []byte) ([]byte, error)
}
AEAD provides authenticated encryption with associated data. A Frontend uses it to encrypt its sessions. Key rotation is the responsibility of the implementation.
type Client ¶
type Client struct {
// Provider is the OpenID Provider the client is registered with.
Provider *Provider
// ClientID is the client identifier.
ClientID string
// ClientSecret is the client secret, sent with client_secret_basic.
ClientSecret string
// ClientKey is the private key that authenticates the client with private_key_jwt. It must be an ECDSA P-256 key,
// an RSA key of at least 2048 bits or an Ed25519 key. A client has either a secret or a key.
ClientKey crypto.Signer
// ClientKeyID is the optional ID of the client key, sent in the assertions.
ClientKeyID string
// RedirectURL is the URL the provider redirects the user to after login, where [Frontend.CallbackHandler] is served.
RedirectURL string
// PostLogoutRedirectURL is the optional URL the provider redirects the user to after logout.
PostLogoutRedirectURL string
// Scopes specifies the requested scopes. It must include openid.
Scopes []string
}
Client is the configuration of an OpenID Connect Relying Party, a confidential OAuth 2.0 client registered with the provider. Its URLs must use https, except for a loopback host.
type Frontend ¶
type Frontend struct {
// contains filtered or unexported fields
}
Frontend is the BFF of a browser-based application, with its own OpenID Connect client and user sessions.
func NewFrontend ¶
NewFrontend returns a Frontend for the OpenID Connect client.
The aead encrypts the data the frontend stores in cookies and in its store, such as the access, refresh and ID tokens. When the frontend is served by several replicas, they must all use the same keys.
func (*Frontend) Authenticate ¶
func (fe *Frontend) Authenticate() fox.MiddlewareFunc
Authenticate returns a middleware that authenticates the requests with the session cookie. It rejects cross-origin requests with an unsafe method, except from the trusted origins (see WithTrustedOrigins), refreshes the access token before it expires, and makes it available with AccessToken. Requests without a valid session are handled by the unauthorized handler, unless the route has AllowAnonymous. The session cookies are removed from the request passed to the next handler and from its response.
func (*Frontend) BackchannelHandler ¶
func (fe *Frontend) BackchannelHandler() fox.HandlerFunc
BackchannelHandler returns a handler that ends the sessions designated by a logout token, as defined by OpenID Connect Back-Channel Logout. The provider must call it with a POST request. It panics if the frontend has no store.
func (*Frontend) CallbackHandler ¶
func (fe *Frontend) CallbackHandler() fox.HandlerFunc
CallbackHandler returns the handler of the redirect URL. It completes the login, creates a new session and redirects to the return URL. If the provider returns an error, such as login_required for prompt=none, it redirects to the return URL without a new session.
func (*Frontend) LoginHandler ¶
func (fe *Frontend) LoginHandler() fox.HandlerFunc
LoginHandler returns a handler that starts the authorization code flow with the provider. The browser must initiate the login flow by navigating to it. After login, the browser is redirected to the URL in the returnUrl query parameter, or to "/" if it is absent. It must be a local URL or a URL of a trusted origin (see WithTrustedOrigins), of at most 1024 bytes once encoded as JSON. The optional prompt query parameter is passed to the provider, such as prompt=none for a silent login. The optional max_age and acr_values query parameters are passed to the provider, and the callback rejects an ID token whose auth_time or acr does not satisfy them.
When Pushed Authorization Requests (PAR, RFC 9126) are used, each login sends a request to the pushed authorization request endpoint of the provider, so the route should be rate limited. If this request fails, the handler responds with 502 Bad Gateway.
func (*Frontend) LogoutHandler ¶
func (fe *Frontend) LogoutHandler() fox.HandlerFunc
LogoutHandler returns a handler that ends the session and redirects to the end session endpoint of the provider, as defined by OpenID Connect RP-Initiated Logout. It requires the csrf_token query parameter or form field.
func (*Frontend) Unauthorized ¶
Unauthorized responds with the unauthorized handler of the frontend, such as in a handler when ResourceAccessToken returns ErrNoSession.
func (*Frontend) UserHandler ¶
func (fe *Frontend) UserHandler() fox.HandlerFunc
UserHandler returns a handler that responds with a JSON object describing the session of the user, such as
{
"claims": {"sub": "248289761001", "name": "Jane Doe", "email": "janedoe@example.com"},
"logoutCsrfToken": "pNmKN2uL7x0Hbf3Sx9VTq2nqZ2bJ1ws6Zx1cA5tDqKE",
"expiresIn": 1800
}
The logoutCsrfToken member is the CSRF token expected by Frontend.LogoutHandler, and expiresIn is the number of seconds before the session ends. The application must call it from JavaScript.
type Option ¶
type Option interface {
// contains filtered or unexported methods
}
Option configures a Frontend.
func WithAbsoluteTimeout ¶
WithAbsoluteTimeout sets the maximum lifetime of a session. The default is DefaultAbsoluteTimeout.
func WithCSRFHeader ¶
WithCSRFHeader requires the header with the given value on the requests handled by Frontend.Authenticate and Frontend.UserHandler.
func WithDPoP ¶
WithDPoP binds the access tokens to a key generated for each session, as defined by RFC 9449. The remote APIs must accept DPoP-bound access tokens.
func WithIdleTimeout ¶
WithIdleTimeout sets the duration of inactivity after which a session ends. The default is DefaultIdleTimeout.
func WithLogHandler ¶
WithLogHandler sets the handler of the logs written when a request is rejected or fails. By default, nothing is logged.
func WithPushedAuthorization ¶
func WithPushedAuthorization(mode PushedAuthorization) Option
WithPushedAuthorization sets when the authorization requests are pushed to the provider. The default is PushedAuthorizationIfAvailable.
func WithRequestObject ¶
WithRequestObject sends the authorization parameters in a request object signed with the client key, as defined by RFC 9101. NewFrontend fails if the client has no key or if the authorization requests are not pushed to the provider. It also fails if the provider requires request objects and this option is disabled.
func WithResources ¶
WithResources declares the protected resources the frontend requests access tokens for, as defined by RFC 8707. Each resource is an absolute URI without fragment, usually the base URI of an API. The resources are requested at login, and the access token of a resource is obtained with ResourceTransport or ResourceAccessToken. Each call adds to the resources of the previous ones.
func WithSigningAlgorithms ¶
WithSigningAlgorithms restricts the algorithms accepted for the signature of the ID tokens and logout tokens. They should include the algorithm registered for the client at the provider. Each call adds to the algorithms of the previous ones. By default, the algorithms advertised by the provider are accepted.
func WithStore ¶
WithStore keeps the sessions in the store, and the session cookie only contains a key to its session. By default, the session is kept in the cookie.
func WithTrustedOrigins ¶
WithTrustedOrigins accepts the requests from the given origins, for a browser-based application that is cross-origin with the BFF, and lets a login return to a URL of these origins. An origin has the form "scheme://host[:port]" sent in the Origin header, in lowercase and without the default port, with https or, on a loopback host, http. The application and the BFF must be same site, such as https://app.example.com and https://bff.example.com, as the session cookie is SameSite=Strict. NewFrontend fails if an origin is not same site with the redirect URL of the client. A CORS middleware must allow only these origins, with credentials. The BFF should also require the header of WithCSRFHeader, as recommended by RFC 10017.
func WithUnauthorizedHandler ¶
func WithUnauthorizedHandler(handler fox.HandlerFunc) Option
WithUnauthorizedHandler sets the handler called for the requests without a valid session. The default is DefaultUnauthorized.
func WithUserInfo ¶
WithUserInfo requests the UserInfo endpoint at login and adds its claims to the ID token claims.
type Provider ¶
type Provider struct {
// contains filtered or unexported fields
}
Provider is an OpenID Provider configured from its metadata. A Provider can be shared by several clients.
func NewProvider ¶
NewProvider retrieves the provider metadata of the issuer with OpenID Connect Discovery. The issuer must use https, except for a loopback host.
type ProviderOption ¶
type ProviderOption interface {
// contains filtered or unexported methods
}
ProviderOption configures a Provider.
func WithEndSessionEndpoint ¶ added in v0.2.0
func WithEndSessionEndpoint(endpoint string) ProviderOption
WithEndSessionEndpoint sets the end session endpoint of OpenID Connect RP-Initiated Logout, for a provider that supports it without publishing it. It replaces the endpoint of the provider metadata.
func WithHTTPClient ¶
func WithHTTPClient(client *http.Client) ProviderOption
WithHTTPClient sets the HTTP client used for the requests to the provider. The default client uses http.DefaultTransport and stops a request after 30 seconds.
func WithRevocationEndpoint ¶ added in v0.2.0
func WithRevocationEndpoint(endpoint string) ProviderOption
WithRevocationEndpoint sets the token revocation endpoint (RFC 7009), for a provider that supports it without publishing it. It replaces the endpoint of the provider metadata.
type PushedAuthorization ¶
type PushedAuthorization uint8
PushedAuthorization defines when a Frontend pushes its authorization requests to the provider, as defined by RFC 9126.
const ( // PushedAuthorizationIfAvailable pushes the authorization requests when the provider has a pushed authorization // request endpoint. PushedAuthorizationIfAvailable PushedAuthorization = iota // PushedAuthorizationRequired pushes the authorization requests. [NewFrontend] fails if the provider has no pushed // authorization request endpoint. PushedAuthorizationRequired // PushedAuthorizationDisabled never pushes the authorization requests. [NewFrontend] fails if the provider requires // pushed authorization requests. PushedAuthorizationDisabled )
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
internal
|
|
|
oidctest
Package oidctest provides an OpenID Connect provider for tests.
|
Package oidctest provides an OpenID Connect provider for tests. |
|
Package store defines the interface between stateful frontends and their session storage.
|
Package store defines the interface between stateful frontends and their session storage. |
|
memory
Package memory provides a store.Store that keeps the sessions in memory.
|
Package memory provides a store.Store that keeps the sessions in memory. |
|
storetest
Package storetest tests implementations of store.Store.
|
Package storetest tests implementations of store.Store. |
|
redis
module
|