# Quickstart: KMS in a Phoenix app

This guide assumes you are an Elixir/Phoenix developer who wants field-level encryption in a normal Phoenix application.

It starts with a vanilla Phoenix app in `example/phoenix_example`, adds `:kms` as a dependency, configures KMS to start with the app, and encrypts one Ecto schema field through owner-scoped KMS aliases and AAD.

KMS uses SQLite by default. The Phoenix app can keep its normal PostgreSQL app database while KMS stores its own key tables in `tmp/kms/kms_dev.sqlite3`.

Working example in this repo:

```sh
example/phoenix_example
```

Evidence this example works:

```sh
cd example/phoenix_example
mix ecto.reset
mix test
# 7 tests, 0 failures
```

## 1. Start with Phoenix

Generate a normal Phoenix application:

```sh
mkdir -p example
cd example
mix phx.new phoenix_example --no-install
cd phoenix_example
```

The committed example was generated this way, then patched to act like a standalone app inside this repository.

## 2. Add KMS as a dependency

Add KMS to your application's `mix.exs`:

```elixir
defp deps do
  [
    {:kms, "~> 0.1.0"},
    {:phoenix, "~> 1.8.1"},
    # ... normal Phoenix deps
  ]
end
```

The committed `example/phoenix_example` application uses `{:kms, path: "../.."}` so it exercises the repository checkout directly.

Start the KMS OTP application with the Phoenix app:

```elixir
def application do
  [
    mod: {PhoenixExample.Application, []},
    extra_applications: [:logger, :runtime_tools, :kms]
  ]
end
```

The KMS OTP application starts:

- the configured KMS repo, delegating to SQLite by default
- `KMS.Cache`
- session cleanup
- optional fallback bootstrap

## 3. Configure Phoenix and KMS repos

The example uses two persistence stores:

- `PhoenixExample.Repo` for application tables.
- KMS SQLite repo for KMS key tables.

In `config/config.exs`:

```elixir
config :phoenix_example,
  ecto_repos: [PhoenixExample.Repo, KMS.Repo.SQLite]

config :kms, ecto_repos: [KMS.Repo.SQLite]
config :kms, KMS.Repo, repo: KMS.Repo.SQLite
config :kms, :client, KMS.Client.Local
config :kms, :rmk_provider, KMS.RMK.Local
config :kms, KMS.RMK.Local, key_path: Path.expand("../priv/kms_rmk_#{config_env()}.key", __DIR__)
```

In `config/dev.exs`, keep Phoenix on PostgreSQL and KMS on SQLite:

```elixir
config :phoenix_example, PhoenixExample.Repo,
  username: "postgres",
  password: "postgres",
  hostname: "localhost",
  database: "phoenix_example_dev"

config :kms, KMS.Repo.SQLite,
  database: Path.expand("../tmp/kms/kms_dev.sqlite3", __DIR__),
  journal_mode: :wal,
  foreign_keys: :on,
  busy_timeout: 5_000,
  pool_size: 10,
  priv: "priv/repo",
  migration_primary_key: false,
  migration_foreign_key: [type: :text],
  migration_timestamps: [type: :utc_datetime_usec]
```

KMS can use PostgreSQL too, but SQLite is the simplest embedded/default path. See [Database configuration](database.md).

For production, configure a high-entropy `KMS_SESSION_HASH_KEY`, explicit RMK key path or external RMK provider, SQLite database backups, TLS, and crash dump policy. See [Security](security.md) and [Root master keys](rmk.md).

## 4. Create an encrypted Ecto field

The example stores documents with encrypted body text:

```elixir
defmodule PhoenixExample.Documents.Document do
  use Ecto.Schema
  import Ecto.Changeset

  @primary_key {:id, :binary_id, autogenerate: false}
  @foreign_key_type :binary_id

  schema "documents" do
    field :owner_id, :string
    field :title, :string
    field :encrypted_body, :string, redact: true
    field :body, :string, virtual: true, redact: true

    timestamps(type: :utc_datetime)
  end

  def changeset(document, attrs) do
    document
    |> cast(attrs, [:id, :owner_id, :title, :body])
    |> maybe_put_id()
    |> validate_required([:id, :owner_id, :title, :body])
    |> validate_length(:owner_id, min: 1, max: 255)
    |> validate_length(:title, min: 1, max: 255)
    |> encrypt_body()
    |> validate_required([:encrypted_body])
  end

  defp maybe_put_id(changeset) do
    case get_field(changeset, :id) do
      nil -> put_change(changeset, :id, Ecto.UUID.generate())
      _id -> changeset
    end
  end

  defp encrypt_body(%Ecto.Changeset{valid?: true} = changeset) do
    owner_id = get_field(changeset, :owner_id)
    document_id = get_field(changeset, :id)
    body = get_field(changeset, :body)

    with _kek <- ensure_alias!(owner_id),
         {:ok, encrypted_body} <- encrypt_body(body, owner_id, document_id) do
      put_change(changeset, :encrypted_body, encrypted_body)
    else
      {:error, reason} -> add_error(changeset, :body, "encryption failed: #{inspect(reason)}")
    end
  end

  defp encrypt_body(changeset), do: changeset

  def decrypt_body(%__MODULE__{encrypted_body: encrypted_body, owner_id: owner_id, id: document_id}) do
    KMS.decrypt_alias(encrypted_body, alias_name(owner_id), aad: aad(document_id))
  end

  def alias_name(owner_id), do: "owner:#{owner_id}"
  def aad(document_id), do: "document:#{document_id}/body"

  defp ensure_alias!(owner_id) do
    case KMS.create_alias(alias_name(owner_id)) do
      {:ok, kek} -> kek
      {:error, reason} -> raise "failed to ensure KMS alias: #{inspect(reason)}"
    end
  end

  defp encrypt_body(body, owner_id, document_id) do
    KMS.encrypt_alias(body, alias_name(owner_id), aad: aad(document_id))
  end
end
```

Migration:

```elixir
def change do
  create table(:documents, primary_key: false) do
    add :id, :binary_id, primary_key: true
    add :owner_id, :string, null: false
    add :title, :string, null: false
    add :encrypted_body, :text, null: false

    timestamps(type: :utc_datetime)
  end
end
```

The plaintext `body` field is virtual. The changeset encrypts it into `encrypted_body`; `body` is never persisted in the Phoenix application table.

## 5. Understand alias and AAD policy

For this small example, KMS policy lives directly in the `Document` module.

Each owner gets one KEK alias, e.g. `owner:alice`. Many documents for the same owner share that KEK.

AAD stays field-specific: `document:#{document_id}/body`. It binds encryption/decryption to the `body` field of that document row. Copying `encrypted_body` to another document id does not decrypt.

See [Additional authenticated data](aad.md).

## 6. Encrypt transparently in the changeset

The changeset owns encryption. The Phoenix context stays boring:

```elixir
def create_document(attrs) do
  Repo.transaction(fn ->
    case %Document{} |> Document.changeset(attrs) |> Repo.insert() do
      {:ok, document} -> document
      {:error, changeset} -> Repo.rollback(changeset)
    end
  end)
end
```

Reads decrypt before returning to caller:

```elixir
def get_document!(id) do
  Document
  |> Repo.get!(id)
  |> with_body!()
end
```

Application code passes `body` to the changeset and gets `body` back from the context. The database stores only `encrypted_body`.

## 7. Prove plaintext stays out of the app table

The example test proves encryption, owner-scoped aliasing, and AAD binding:

```elixir
test "encrypts document body through KMS and decrypts transparently" do
  assert {:ok, document} =
           Documents.create_document(%{
             owner_id: "alice",
             title: "Launch notes",
             body: "Keep this plaintext out of the application table"
           })

  stored = Repo.get!(Document, document.id)

  assert stored.body == nil
  refute stored.encrypted_body =~ "Keep this plaintext"
  assert Documents.get_document!(document.id).body == document.body

  assert %KMS.Kek{} = KMS.Keks.get_by_alias(Document.alias_name("alice"))
end
```

Run it:

```sh
cd example/phoenix_example
mix deps.get
mix ecto.reset
mix test
```

Current evidence from this worktree:

```text
7 tests, 0 failures
```

## 8. Use KMS directly from IEx

After setup:

```sh
iex -S mix
```

Create and read an encrypted document:

```elixir
{:ok, document} =
  PhoenixExample.Documents.create_document(%{
    owner_id: "alice",
    title: "Private note",
    body: "only plaintext in app memory"
  })

PhoenixExample.Documents.get_document!(document.id).body
```

Inspect stored ciphertext:

```elixir
stored = PhoenixExample.Repo.get!(PhoenixExample.Documents.Document, document.id)
stored.encrypted_body
stored.body
```

`stored.body` is `nil`; `stored.encrypted_body` is ciphertext.

## Next steps

- Use [AAD](aad.md) consistently for encrypted fields.
- Choose and back up an [RMK provider](rmk.md).
- Read [Database configuration](database.md) for SQLite/PostgreSQL options.
- Read [Security](security.md) before production.
- Explore [use cases](use_cases.md), including [standalone KMS](use_cases/standalone.md).
