Skip to main content

API Key Authentication (3 Headers)

Overview​

Unlike cms-api and client-api, webhook-go uses no JWT at all. It has exactly one authentication scheme: a three-header api-key designed for B2B integrations.

HeaderMeaning
x-client-idIdentifies the client, keyed against the api_client table
x-client-secretThe client's secret, compared as plaintext directly in SQL
x-api-keyThe key that defines access scope, from the api_key table (line_oa_id plus organization_id)

The design separates "who you are" (client id and secret) from "what you may access" (the api key). A single client can hold multiple api keys, each bound to a different LINE OA.

Only /api/audience and /api/line-oa/verify are protected — four routes in total. Every route that receives events from LINE or Chatwoot is deliberately left public.

Business Flow​

Middleware stage (APIKeyAuth)​

  1. Read all three headers. If any is missing, respond 401 "Missing credentials in headers" and stop immediately.
  2. If the repository is not wired (a boot without a database), respond 500 — failing closed rather than letting the request through.
  3. Call ValidateClient: SELECT ... FROM api_client WHERE client_id=$1 AND client_secret=$2 AND status='active'
    • A genuine database error must return 500; collapsing it into 401 is forbidden by the "never swallow an error" rule.
    • No matching row returns 401 "Invalid client credentials".
  4. On success, store apiClientId (an int64 from api_client.id) and apiKey (the raw header string) in the gin context. Together they stand in for the original NestJS ClsService.
  5. Call c.Next().

Service stage (scope resolution)​

  1. The handler reads the values back with middleware.ApiClientID(c) and middleware.APIKey(c). Missing values mean the guard was bypassed, which returns 401.
  2. The service calls apikey.Repository.GetByClientAndKey: SELECT ... FROM api_key WHERE api_client_id=$1 AND key=$2 AND status='active'
  3. The resulting {id, lineOaId, organizationId} scopes every subsequent query.

When no row is found at this stage, each endpoint behaves differently — inconsistent parity inherited from the original: GET /audience returns 200 null, PUT returns 500, DELETE returns 400 "Step 1: ...", and verify continues on and eventually returns 404.

Security notes worth recording​

  • client_secret is stored and compared as plaintext in the database, deliberately matching the original. The file internal/middleware/apikey.go carries the comment "plaintext compare — match source; no hashing". The internal/middleware/apikey_test.go package and the template's bcrypt helper still exist but are not used on the live path.
  • There is no rate limit specific to authentication failures — only the shared per-IP limit.
  • This codebase has no api-key expiry mechanism; only a status column.

Key Files & Functions​

FileHighlights
internal/middleware/apikey.goAPIKeyAuth(v ClientValidator) gin.HandlerFunc, ApiClientID(c), APIKey(c), const CtxKeyAPIClientID / CtxKeyAPIKey, interface ClientValidator
internal/server/server.go(*Deps).APIKeyAuth() — binds the middleware to deps.APIClient
internal/apiclient/repository.goValidateClient(ctx, clientID, clientSecret)
internal/apiclient/entity.gostruct ApiClient
internal/apikey/repository.goGetByClientAndKey(ctx, apiClientID, key)
internal/apikey/entity.gostruct ApiKey (ID, LineOaID, OrganizationID, …), const StatusActive
cmd/api/main.goConstructs both repositories when a.SQL != nil
cmd/api/wiring_test.goAsserts that four routes are guarded and three remain public

Protection by route​

RouteAuth
GET /api, GET /api/healthNone
POST /api/line/:id, POST /api/Line/:idNone
POST /api/trackingNone
POST /api/mbox/callback/:oaHashNone
GET /api/audienceapi-key
PUT /api/audience/:idapi-key
DELETE /api/audience/:idapi-key
POST /api/line-oa/verifyapi-key

Connections to Other Services​

  • Postgres — the api_client and api_key tables, both queried through the pgx simple protocol, with int64 identifiers, pointers for nullable columns, and the status enum bound as a string.
  • cms-api — the api-client and api-key modules provide the screens that issue and revoke these credentials.
  • Related database domain: api-client-key.
  • Consumers of this middleware: Managing Audience Members and LINE OA Verification.
  • The full middleware chain is documented in Core HTTP, Middleware Pipeline & Database Access.