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.