API Keys

The API-key plugin provides machine-to-machine authentication for CLIs, background workers, cron jobs, partner integrations, webhook clients, and service calls.

API keys are not browser sessions. They are long-lived credentials owned by a user and should be scoped, revocable, and optionally expiring.

Install

go get github.com/ragokan/limen/plugins/api-key
import apikey "github.com/ragokan/limen/plugins/api-key"

auth, err := limen.New(&limen.Config{
    Database: adapter,
    Secret:   secret,
    Plugins: []limen.Plugin{
        apikey.New(apikey.WithAllowedScopes("jobs:read", "jobs:write")),
    },
})

Management Routes

The plugin mounts under /api-keys relative to Limen's HTTP base path.

POST   /auth/api-keys
GET    /auth/api-keys
DELETE /auth/api-keys/:id

These routes require a normal user session.

Create request:

{
  "name": "CLI",
  "scopes": ["jobs:read", "jobs:write"],
  "expires_at": "2026-12-31T00:00:00Z"
}

The plaintext key is returned only once in the create response. Limen stores a short lookup prefix plus an HMAC-SHA256 hash using the Limen secret.

Configure every scope that clients may request with WithAllowedScopes. Unconfigured or syntactically invalid scopes are rejected at creation time. Scopes may contain letters, numbers, :, ., _, -, and /.

Authenticate Requests

Use the plugin middleware on your own service routes:

apiKeys := apikey.Use(auth)

mux.Handle("/jobs", apiKeys.MiddlewareRequireAPIKey("jobs:write")(
    http.HandlerFunc(handleJobs),
))

Clients can send either:

X-Limen-API-Key: limen_sk_...
Authorization: ApiKey limen_sk_...

Authorization: Bearer is reserved for Limen session bearer tokens, so API keys do not use Bearer by default.

Inside handlers:

key, ok := apikey.GetAPIKeyFromContext(r.Context())

Validation

Validation checks:

  • key hash with constant-time comparison
  • revocation state
  • expiry
  • required scopes

Successful validation updates last_used_at.