📖 3 min read (~ 600 words).

Authentication

Client-side authentication is a pure encoding concern: take some credentials, write the right header / query parameter on the outbound request. It is decoupled from the server-side Authenticator / Authorizer interfaces (core / interfaces) — those answer “is this request allowed?”, these answer “how do I sign it?”.

The interface — ClientAuthInfoWriter

package runtime

type ClientAuthInfoWriter interface {
    AuthenticateRequest(ClientRequest, strfmt.Registry) error
}

type ClientAuthInfoWriterFunc func(ClientRequest, strfmt.Registry) error

See runtime.ClientAuthInfoWriter for the authoritative definition. Anything with that signature can be used as auth. The ClientRequest argument exposes SetHeaderParam, SetQueryParam, SetBodyParam — i.e. the same surface generated parameter types use to encode themselves.

Where to attach it

Two places, with predictable precedence:

// 1. Per operation — overrides the runtime default
op.AuthInfo = client.BearerToken(token)

// 2. Per runtime — used when the operation does not set its own
rt.DefaultAuthentication = client.BasicAuth("alice", "s3cret")

Full source: docs/examples/client/auth/main.go

The runtime calls the operation’s AuthInfo if set, otherwise the runtime’s DefaultAuthentication. Either may be nil for unauthenticated endpoints.

Built-in helpers

All four return a ready-to-use ClientAuthInfoWriter.

BasicAuth(user, password) — RFC 7617

rt.DefaultAuthentication = client.BasicAuth("alice", "s3cret")

Full source: docs/examples/client/auth/main.go

Sets Authorization: Basic <base64(user:password)>.

APIKeyAuth(name, in, value) — RFC-undefined but ubiquitous

// As an HTTP header
rt.DefaultAuthentication = client.APIKeyAuth("X-Api-Key", "header", apiKey)

// Or as a query parameter
rt.DefaultAuthentication = client.APIKeyAuth("api_key", "query", apiKey)

Full source: docs/examples/client/auth/main.go

in must be "header" or "query". Anything else returns nil — at which point you’ll silently send the request unauthenticated, so check your spelling.

BearerToken(token) — RFC 6750 OAuth2 access tokens

rt.DefaultAuthentication = client.BearerToken(accessToken)

Full source: docs/examples/client/auth/main.go

Sets Authorization: Bearer <token>. For OAuth2 client flows that need to acquire and refresh the token, build the writer around an oauth2.TokenSource from golang.org/x/oauth2 and re-attach it on every call (or use a custom writer that calls Token()).

Compose(auths…) — combine multiple writers

For APIs that require more than one credential header on the same request — say an API key plus a bearer token — chain them:

rt.DefaultAuthentication = client.Compose(
	client.APIKeyAuth("X-Api-Key", "header", apiKey),
	client.BearerToken(accessToken),
)

Full source: docs/examples/client/auth/main.go

Nil writers in the list are skipped silently. The first non-nil writer that returns an error short-circuits the chain.

PassThroughAuth — explicit “no auth”

A no-op writer. Use it when the operation requires some writer (for instance because it’s defined as security: [[]] in the spec) but no actual credential should be attached.

op.AuthInfo = client.PassThroughAuth

Full source: docs/examples/client/auth/main.go

Writing your own

A common case: an HMAC-signed request that needs to compute the signature over the body. Implement ClientAuthInfoWriter directly:

// HMACSignature returns a ClientAuthInfoWriter that signs the request
// body with the given HMAC-SHA256 key and attaches the signature plus
// key ID as headers.
func HMACSignature(keyID string, key []byte) runtime.ClientAuthInfoWriter {
	return runtime.ClientAuthInfoWriterFunc(func(r runtime.ClientRequest, _ strfmt.Registry) error {
		body := r.GetBody()
		mac := hmac.New(sha256.New, key)
		mac.Write(body)
		sig := hex.EncodeToString(mac.Sum(nil))
		if err := r.SetHeaderParam("X-Sig-Key", keyID); err != nil {
			return err
		}
		return r.SetHeaderParam("X-Sig", sig)
	})
}

Full source: docs/examples/client/auth/main.go

The runtime calls AuthenticateRequest after the operation’s parameters have been bound but before the request is sent — so r.GetBody() returns the encoded body for buffered payloads. For streaming bodies (multipart, raw streams) the runtime arranges a body-copy closure so the signer sees the bytes that will go on the wire; see BuildHTTPContext in client/internal/request for the gory details.