Trusted keys now persist in the database

Dynamic key management is coming: the token exchange module can now store trusted keys and their sources in the database instead of holding everything in memory at startup.
Trusted keys used for token validation are now storable in the database. The token exchange module previously loaded keys only from environment variables at startup and kept them in memory — fine for static keys, but limiting for dynamic scenarios.
This change introduces two new tables. The trusted_key_source table tracks where keys come from (with fields for status tracking, configuration, and refresh metadata), while trusted_key stores individual keys with a composite primary key allowing the same key ID across different sources. A JSON column holds key metadata like algorithms, PEM material, issuer, audience, and expiry.
These tables are the first step toward dynamic key management: JWKS endpoints that rotate keys, and eventually keys managed through the UI rather than environment variables. The infrastructure handles status tracking (pending → healthy → error) for observability into refresh cycles, and a new configuration option sets the leader-only refresh interval.
In the n8n CLI package, TrustedKeySourceEntity and TrustedKeyEntity are now lazy-loaded in the TokenExchangeModule.
View Original GitHub Description
Summary
This PR adds the database layer for persisting trusted keys and their sources in the token exchange module. Currently, trusted keys are loaded from environment variables at startup and held only in memory. This change introduces two new tables (trusted_key_source and trusted_key) with TypeORM entities, repositories, and a reversible migration, laying the groundwork for dynamic key management (e.g., JWKS rotation and UI-managed keys).
What's included:
- Migration (
1776000000000-CreateTrustedKeyTables): Createstrusted_key_source(with status tracking, config, and refresh metadata) andtrusted_key(composite PK onsourceId+kid, FK cascade to source) for both PostgreSQL and SQLite. - Entities:
TrustedKeySourceEntityandTrustedKeyEntitywith proper TypeORM decorators and relations. - Repositories:
TrustedKeySourceRepositoryandTrustedKeyRepository(withfindBySourceAndKidandfindAllByKidquery methods). - Schema additions: New
uisource type in the Zod discriminated union,TrustedKeyDatainterface for the serialized JSON column, and exported type aliases (TrustedKeySourceType,TrustedKeySourceStatus). - Config:
keyRefreshIntervalSecondsenv var for future leader-only refresh scheduling. - Module registration: Entities lazy-loaded in
TokenExchangeModule.entities().
Key design decisions:
- Composite primary key (
sourceId,kid) ontrusted_keyallows the samekidacross different sources while enforcing uniqueness within a source. trusted_key.datais a JSON text column storingTrustedKeyData(algorithms, PEM, issuer, audience, roles, expiry) — keeps the schema stable as key metadata evolves.trusted_key_source.statustracks source health (pending→healthy|error) for observability of JWKS refresh cycles.uisource type is recognized in the schema but explicitly skipped during initialization (not yet supported).
How to test manually
- Start n8n with the token exchange feature flag enabled
- Verify the migration runs without errors on a fresh database (check startup logs)
- Confirm both tables exist:
SELECT * FROM trusted_key_source; SELECT * FROM trusted_key;
Related tickets
closes https://linear.app/n8n/issue/IAM-523/2a31-trustedkeystore-db-tables-entities-and-repositories
Review checklist
- PR title and summary are descriptive. (conventions)
- Docs updated or follow-up ticket created.
- Tests included.
- Integration tests cover: CRUD operations, FK cascade deletes, composite PK uniqueness, cross-source kid sharing,
findAllByKidqueries, optional field round-tripping, and orphan key rejection.
- Integration tests cover: CRUD operations, FK cascade deletes, composite PK uniqueness, cross-source kid sharing,