📖 2 min read (~ 300 words).

API key (single scheme)

The shortest path to a secured endpoint. Mirrors the go-swagger/examples/authentication example, in untyped form.

Spec

securityDefinitions:
  key:
    type: apiKey
    in: header
    name: X-Token

# default: every operation requires the key
security:
  - key: []

Wiring

doc, _ := loads.Spec("swagger.yml")
api := untyped.NewAPI(doc).WithJSONDefaults()

// 1. Authenticator: token → principal
api.RegisterAuth("key", security.APIKeyAuth(
	"X-Token", "header",
	func(token string) (any, error) {
		// Use subtle.ConstantTimeCompare to avoid leaking the
		// expected token byte-by-byte via response timing.
		if subtle.ConstantTimeCompare([]byte(token), []byte("abcdefuvwxyz")) == 1 {
			return "alice", nil
		}
		return nil, errors.New(http.StatusUnauthorized, "invalid api key")
	},
))

// 2. Authorizer: every authenticated principal allowed.
//    (Skip this line if you have no business-rule gating.)
api.RegisterAuthorizer(security.Authorized())

// 3. Operation handlers (one per spec operation)
api.RegisterOperation("get", "/customers/{id}", runtime.OperationHandlerFunc(
	func(_ any) (any, error) {
		// params is the bound parameter struct;
		// principal is on r.Context() via middleware.SecurityPrincipalFrom
		return map[string]string{"id": "42"}, nil
	},
))

handler := middleware.Serve(doc, api)
log.Fatal(http.ListenAndServe(":35307", handler))

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

The scheme name passed to RegisterAuth ("key") must match the key under securityDefinitions in the spec.

Exercise

# Valid token → 200
curl -i -H 'X-Token: abcdefuvwxyz' http://127.0.0.1:35307/customers/42

# Wrong / missing token → 401
curl -i -H 'X-Token: nope' http://127.0.0.1:35307/customers/42
# {"code":401,"message":"invalid api key"}

Variations

  • Query param instead of header: change in: to query in the spec and the second arg of APIKeyAuth to "query". The token comes from ?api_key=….
  • Context-aware lookup (DB call honouring request cancellation): use security.APIKeyAuthCtx instead — same idea, the callback gets the request context.Context.
  • Per-operation override: a route can opt out by setting security: []; opt into a different scheme by replacing the list.