Skip to content

Add auth, security headers & a gateway

The transport servers are composed from small building blocks: authentication and security-header middleware from transport/http (and AuthInterceptor from transport/grpc), stitched together with a go/transit Chain, plus an optional grpc-gateway.

Authenticate HTTP requests

AuthMiddleware verifies each request against a go/authn verifier and stores the identity in the request context. It is an ordinary transit Middleware, so it composes into a Chain:

import (
    "gitlab.com/phpboyscout/go/authn"
    transithttp "gitlab.com/phpboyscout/go/transit/http"
    transporthttp "gitlab.com/phpboyscout/go/transport/http"
)

auth, err := transporthttp.AuthMiddleware(
    transporthttp.WithBearerVerifier(jwtVerifier),   // authn.Verifier
    transporthttp.WithAuthLogger(log),
)
if err != nil {
    return err
}

chain := transithttp.NewChain(auth, transporthttp.SecurityHeadersMiddleware())
srv, _ := transporthttp.Register(ctx, "api", controller, log, handler, settings,
    transporthttp.WithTLSPair(pair), transporthttp.WithMiddleware(chain))

On failure it writes a generic 401/403 with the credential redacted from logs. Read the verified identity downstream with transporthttp.IdentityFromContext(ctx).

Add security headers

SecurityHeadersMiddleware sets a secure baseline (X-Content-Type-Options, X-Frame-Options, Referrer-Policy, and optionally HSTS/CSP). Tune it with options:

sec := transporthttp.SecurityHeadersMiddleware(
    transporthttp.WithHSTS(365*24*time.Hour, true, true),
    transporthttp.WithContentSecurityPolicy("default-src 'self'"),
    transporthttp.WithFrameOptions("DENY"),
)

Authenticate gRPC calls

transport/grpc mirrors the HTTP auth surface as an interceptor:

import transportgrpc "gitlab.com/phpboyscout/go/transport/grpc"

ic, err := transportgrpc.AuthInterceptor(
    transportgrpc.WithGRPCBearerVerifier(jwtVerifier),
    transportgrpc.WithGRPCMethodSkipper(func(m string) bool { return m == "/grpc.health.v1.Health/Check" }),
)
srv, _ := transportgrpc.NewServer(settings, transportgrpc.WithInterceptors(
    transitgrpc.NewInterceptorChain(ic),
))

Require client certificates (mTLS)

Server-side mTLS is config-driven end to end through the go/tls Pair: set ClientCAs (PEM CA files client certificates must chain to) and ClientAuth (request, verify-if-given, or require-verify) on the pair you pass to Register via WithTLSPair, and pair it with the mTLS verifier on the auth middleware/interceptor:

pair := gtls.Pair{
    Enabled:    true,
    Cert:       "/etc/certs/server.pem",
    Key:        "/etc/certs/server-key.pem",
    ClientCAs:  []string{"/etc/certs/client-ca.pem"},
    ClientAuth: gtls.ClientAuthRequireVerify,
}

// HTTP: the handler sees non-empty r.TLS.VerifiedChains, and WithMTLSVerifier
// authenticates the request from them.
auth, _ := transporthttp.AuthMiddleware(transporthttp.WithMTLSVerifier(authn.NewMTLSVerifier()))

// gRPC: WithGRPCMTLSVerifier sees the verified chains via peer.FromContext.
ic, _ := transportgrpc.AuthInterceptor(transportgrpc.WithGRPCMTLSVerifier(authn.NewMTLSVerifier()))

Both managed paths honour a caller-supplied server TLS config (transporthttp.WithServerTLSConfig / transportgrpc.WithServerTLSConfig) by merging the pair into it rather than replacing it: the pair contributes its certificate (and, on the gRPC listener, ALPN h2), while everything the caller set — MinVersion, verification hooks, a client-certificate policy — survives. Precedence: a config that already carries ClientCAs or a non-zero ClientAuth wins over the pair's ClientCAs/ClientAuth fields.

Serve TLS from an in-memory certificate

A gtls.Pair describes a certificate by file path. When the certificate is minted in memory and never written to disk — a self-signed fallback used when the local CA cannot be installed into the system trust store, an in-memory ACME/autocert certificate, a short-lived issuer, or a test — pass it with WithServedCertificate and leave the pair disabled:

cert, _ := someIssuer.Mint() // *tls.Certificate, held in RAM

_, err := transporthttp.Register(
    ctx, "studio", controller, logger, handler,
    transporthttp.ServerSettings{Port: 8443},
    transporthttp.WithServedCertificate(cert),     // serve TLS from RAM, no pair
)

The server serves over TLS even though no pair is enabled — there is no need to persist a throwaway certificate to disk to satisfy the pair shape. The certificate is appended to the server's TLS config, so a config supplied via WithServerTLSConfig keeps its MinVersion, verification hooks, and any client-certificate policy; the passed *tls.Certificate is not mutated. A certificate placed directly on a WithServerTLSConfig is likewise served — populated certificate material is treated as TLS intent.

Expose a gRPC service over REST with the gateway

transport/gateway wraps a gRPC connection in a grpc-gateway http.Handler. Give it a connection (dial the gRPC server with go/grpcclient or transport/grpc.DialLocal) and your generated registration function:

import transportgateway "gitlab.com/phpboyscout/go/transport/gateway"

conn, _ := transportgrpc.DialLocal(grpcSettings, grpcPair)
handler, err := transportgateway.New(ctx, conn,
    func(ctx context.Context, mux *runtime.ServeMux, c *grpc.ClientConn) error {
        return widgetv1.RegisterWidgetServiceHandler(ctx, mux, c)
    },
    transportgateway.WithMiddleware(chain), // optional transit Chain over the REST handler
)

The gateway handler is a plain http.Handler, so serve it through transport/http like any other — the whole stack composes.

When the gRPC server requires client certificates, WithDialOptions is the escape hatch for the gateway's outbound dial: pass grpc.WithTransportCredentials(credentials.NewTLS(cfg)) with a client *tls.Config carrying the gateway's client certificate and the CA pool that signed the server's certificate — an explicit transport-credentials option overrides the automatically selected transport security.

Where the full option lists live

This page composes the pieces; the exhaustive tables — every option, its default, and what happens when a value is wrong — are in the reference:

  • HTTP server referenceAuthMiddleware options, credential precedence, the exact 401/403 responses, and the security-header defaults.
  • gRPC server referenceAuthInterceptor options, and which methods skip authentication whatever you configure.
  • TLS settings reference — the gtls.Pair fields, the ClientAuth modes, and which TLS setting wins when two disagree.
  • Gateway reference — what New and Register each own, including who closes the gRPC connection.

Two constraints that bite here and are stated in full under What transport does not do: gRPC reflection cannot be authenticated, and an enabled TLS pair must not be combined with your own grpc.Creds.