---
title: Forge Integration
description: Using Keysmith with the Forge framework.
---

Keysmith ships with a Forge extension adapter that handles DI registration, REST API route mounting, and database migration on startup.

## Mounting the extension

```go
import (
    "github.com/xraph/forge"
    "github.com/xraph/keysmith"
    "github.com/xraph/keysmith/extension"
    "github.com/xraph/keysmith/store/postgres"
    audithook "github.com/xraph/keysmith/audit_hook"
)

app := forge.New(
    forge.WithExtension(
        extension.New(
            extension.WithEngineOptions(
                keysmith.WithStore(pgStore),
            ),
            extension.WithHookExtension(audithook.New(recorder)),
        ),
    ),
)
```

## What the extension does

### On registration

1. Registers `keysmith.Engine` in the Forge DI container via `vessel.Provide`
2. Registers the API route handler

### On start

1. Runs database migrations if a PostgreSQL store is configured
2. Starts any background processes (e.g., expired key cleanup)

### On stop

1. Fires the `Shutdown` plugin hook
2. Closes the store connection

## REST API routes

When mounted, the extension exposes the full Keysmith REST API:

| Method | Path | Description |
| ------ | ---- | ----------- |
| `POST` | `/v1/keys` | Create API key |
| `GET` | `/v1/keys` | List API keys |
| `GET` | `/v1/keys/:keyId` | Get API key |
| `DELETE` | `/v1/keys/:keyId` | Delete API key |
| `POST` | `/v1/keys/:keyId/rotate` | Rotate API key |
| `POST` | `/v1/keys/:keyId/revoke` | Revoke API key |
| `POST` | `/v1/keys/:keyId/suspend` | Suspend API key |
| `POST` | `/v1/keys/:keyId/reactivate` | Reactivate API key |
| `POST` | `/v1/keys/validate` | Validate raw API key |
| `POST` | `/v1/policies` | Create policy |
| `GET` | `/v1/policies` | List policies |
| `GET` | `/v1/policies/:policyId` | Get policy |
| `PUT` | `/v1/policies/:policyId` | Update policy |
| `DELETE` | `/v1/policies/:policyId` | Delete policy |
| `POST` | `/v1/scopes` | Create scope |
| `GET` | `/v1/scopes` | List scopes |
| `DELETE` | `/v1/scopes/:scopeId` | Delete scope |
| `POST` | `/v1/keys/:keyId/scopes` | Assign scopes to key |
| `DELETE` | `/v1/keys/:keyId/scopes` | Remove scopes from key |
| `GET` | `/v1/keys/:keyId/usage` | Get key usage |
| `GET` | `/v1/keys/:keyId/usage/aggregate` | Get usage aggregation |
| `GET` | `/v1/usage` | List tenant usage |
| `GET` | `/v1/keys/:keyId/rotations` | List key rotations |

## Automatic tenant scoping

When running inside Forge, Keysmith automatically extracts the app ID and tenant ID from `forge.Scope`. No manual `WithTenant` call is needed.

## DI integration

Access the engine from the Forge DI container:

```go
// In a Forge handler
func myHandler(ctx forge.Context) error {
    eng := vessel.MustResolve[*keysmith.Engine](ctx.Vessel())
    // use eng...
}
```

## Grove database integration

When your Forge app uses the [Grove extension](https://github.com/xraph/grove) to manage database connections, Keysmith can automatically resolve a `grove.DB` from the DI container and construct the correct store backend based on the driver type.

### Using the default grove database

If the Grove extension registers a single database (or a default in multi-DB mode), use `WithGroveDatabase` with an empty name:

```go
ext := extension.New(
    extension.WithGroveDatabase(""),
)
```

### Using a named grove database

In multi-database setups, reference a specific database by name:

```go
ext := extension.New(
    extension.WithGroveDatabase("keysmith"),
)
```

This resolves the grove.DB named `"keysmith"` from the DI container and auto-constructs the matching store. The driver type is detected automatically -- you do not need to import individual store packages.

### Store resolution order

The extension resolves its store in this order:

1. **Explicit store** -- if `WithEngineOptions(keysmith.WithStore(s))` was called, it is used directly and grove is ignored.
2. **Grove database** -- if `WithGroveDatabase(name)` was called, the named or default `grove.DB` is resolved from DI.
3. **In-memory fallback** -- if neither is configured, an in-memory store is used.

## Extension options

| Option | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| `WithEngineOptions(opts...)` | `...keysmith.Option` | -- | Engine configuration options |
| `WithHookExtension(x)` | `plugin.Plugin` | -- | Register a lifecycle plugin (repeatable) |
| `WithConfig(cfg)` | `Config` | defaults | Override default extension configuration |
| `WithLogger(l)` | `*slog.Logger` | `slog.Default()` | Set the logger |
| `WithDisableRoutes()` | -- | `false` | Skip HTTP route registration |
| `WithDisableMigrate()` | -- | `false` | Skip migrations on Start |
| `WithBasePath(path)` | `string` | `""` | URL prefix for keysmith routes |
| `WithGroveDatabase(name)` | `string` | `""` | Named grove.DB to resolve from DI |
| `WithRequireConfig(b)` | `bool` | `false` | Require config in YAML files |

## File-based configuration (YAML)

When running as a Forge extension, Keysmith automatically loads configuration from YAML config files. The extension looks for config under the following keys (in order):

1. `extensions.keysmith` -- standard Forge extension config namespace
2. `keysmith` -- top-level shorthand

### Example

```yaml
# forge.yaml
extensions:
  keysmith:
    disable_routes: false
    disable_migrate: false
    base_path: "/keysmith"
    grove_database: ""
```

### Config fields

| YAML Key | Type | Default | Description |
|----------|------|---------|-------------|
| `disable_routes` | `bool` | `false` | Skip HTTP route registration |
| `disable_migrate` | `bool` | `false` | Skip migrations on Start |
| `base_path` | `string` | `""` | URL prefix for all routes |
| `grove_database` | `string` | `""` | Named grove.DB from DI |

### Merge behaviour

File-based configuration is merged with programmatic options. Programmatic boolean flags (`DisableRoutes`, `DisableMigrate`) always win when set to `true`. For other fields, YAML values take precedence, then programmatic values, then defaults.
