DEV Community

MongoDB Guests for MongoDB

Posted on

Guide to Seamless Data Security in Rails With Mongoid’s Automatic Encryption

This tutorial was written by MongoDB Champion Humberto Diaz.

One of Rails' doctrinal pillars puts programmer happiness on a pedestal. Convention over configuration, human-readable code, and smart defaults make the Rails Way feel almost intuitive. With the release of Mongoid 9.0, that same philosophy now extends to data encryption. Tasks that once required multi-step configurations can now be completed through a single model declaration. MongoDB’s Client-Side Field-Level Encryption (CSFLE) can now be seamlessly integrated into your applications, enhancing your data security with the ease and simplicity developers expect from Rails.

Why client-side encryption matters

Traditional encryption occurs on the server or at rest in the database. This means that your data, while protected on disk, is still visible as plain text when traveling over the network. This would be similar to writing a secret note to a friend and simply folding the note in half before sending it in the mail. With little effort, anyone could potentially read your message as it makes its way to your friend.

Client-Side Field Level Encryption changes this completely as your Rails app encrypts data before it ever leaves the application. Not even your MongoDB cluster will see the unencrypted values! Only your app, which holds the encryption key, can decrypt it. Referring back to the previous analogy, instead of your note traveling unprotected, it’s now in a locked box, and your friend is the only one with the matching key to open it.

What this means:

  • Accidental leaks of sensitive information are less likely.
  • Unauthorized users with DB access see nothing but ciphertext.

Ultimately, your database becomes zero-knowledge, storing data without knowing what it is storing.

The problem before Mongoid 9.0

The MongoDB Ruby Driver has supported CSFLE since v2.12, but Ruby on Rails developers don’t work with the driver directly—they use Mongoid.

You had to:

  • Write custom logic for encryption/decryption.
  • Manually manage keys through the driver API.
  • Keep a log of which fields needed encryption.

This is not the Rails Way.

The Mongoid 9.0 solution

Mongoid 9.0 introduces automatic client-side field-level encryption, using a simple declarative way to encrypt data that feels right at home in Rails.

Inspired by Active Record Encryption, it lets you define encrypted
fields directly in your models:

class BankAccount
  include Mongoid::Document
  include Mongoid::Timestamps

  encrypt_with key_id: 'KImyywsTQWi1+cFYIHdtlA=='

  field :account_number, type: String, encrypt: { deterministic: false }
  field :bank_name, type: String, encrypt: { deterministic: false }

  field :name, type: String
  field :account_type, type: String
end
Enter fullscreen mode Exit fullscreen mode

That’s it! There are no service objects, no callbacks, no extra encryption logic. When you save an instance of your model, Mongoid encrypts the appropriate fields. When you load data into your models, Mongoid decrypts the relevant fields.

It’s invisible, automatic, and secure.

Setting it up in Rails

You can get up and running in just a few minutes.

1. Install the required gems

gem 'mongoid', '>= 9.0'
gem 'libmongocrypt-helper'
Enter fullscreen mode Exit fullscreen mode

Then run:

bundle install
Enter fullscreen mode Exit fullscreen mode

Note: The libmongocrypt-helper is great for experimentation but should only be used in development. For production environments, install the official MongoDB shared library (crypt_shared) and ensure you’re using MongoDB Ruby Driver v2.19 or later. Detailed installation instructions can be found in the Mongoid Shared Library.

2. Generate a local master key and add encryption settings to config/mongoid.yml

We will use a local key provider for testing (see “Create a Customer Master Key”), and store in a LOCAL_MASTER_KEY environment variable, which we’ll use to configure our Ruby on Rails application:

export LOCAL_MASTER_KEY=$(ruby -e "require 'securerandom'; puts SecureRandom.hex(48)")
Enter fullscreen mode Exit fullscreen mode

Make sure you save this key somewhere safe, as it is only generated once. Next, we’ll modify the config/mongoid.yml file to include our key:

development:
  clients:
    default:
      uri: mongodb+srv://user:pass@yourcluster.mongodb.net/test
      options:
        auto_encryption_options:
          key_vault_namespace: 'encryption.__keyVault'
          kms_providers:
            local:
              key: "<%= ENV['LOCAL_MASTER_KEY'] %>"
Enter fullscreen mode Exit fullscreen mode

Note: The example above uses a local KMS provider. For production environments, configure a key management system (KMS) such as AWS KMS or Azure Key Vault. For more information, see KMS Providers.

3. Generate a data encryption key (DEK)

$ rake db:mongoid:encryption:create_data_key
Created data key with id: 'KImyywsTQWi1+cFYIHdtlA==' for kms provider: 'local' in key vault: 'encryption.__keyVault'.
Enter fullscreen mode Exit fullscreen mode

Copy the key ID and plug it into your model’s encrypt_with call.

encrypt_with key_id: 'KImyywsTQWi1+cFYIHdtlA=='
Enter fullscreen mode Exit fullscreen mode

With that, your application will now encrypt the selected fields automatically!

Seeing it in action

MongoDB has a sample Mongoid CSFLE application that can be cloned and tested to quickly showcase the flexibility and power of this feature.

Once the repository has been cloned, all you need is a connection string to your Atlas cluster in an environment variable to get up and running:

bundle install

export ATLAS_URI=<your MongoDB Atlas connection string>
export LOCAL_MASTER_KEY=$(ruby -e "require 'securerandom'; puts SecureRandom.hex(48)")
export USER_KEY_ID=$(rails db:mongoid:encryption:create_data_key)

rails db:seed
rails server
Enter fullscreen mode Exit fullscreen mode

This will spawn a Rails server on http://127.0.0.1:3000 that you can begin interacting with.

Screenshot of the Mongoid CSFLE Sample App in the browser. The page header reads ‘FinanceLock,’ with navigation links such as Home, Add Account, and Admin.

Screenshot of the Mongoid CSFLE Sample App displaying sample account tables labeled Main, Savings, and Chequing Account. Each displaying example transactions, dates, and amounts.

Note: Two users are available by default: john@doe.com and jane@doe.com (both with a password of 111111).

Once logged in, new accounts and transactions can be recorded. When you initially seed the application, two accounts and a number of transactions are populated.

Since this data is encrypted client-side, we can interact with it directly from our application, or through model instances from the Rails console as we normally would:

BankAccount.create!(
  name: "Chequing Account",
  account_number: "4982-2848747",
  bank_name: "TD Canada Trust",
  account_type: "Chequing"
)

BankAccount.last.account_number
# => "4982-2848747"
Enter fullscreen mode Exit fullscreen mode

But if you inspect the same document in mongosh

db.bank_accounts.find().sort({ _id: -1 }).limit(1)
[
  {
    _id: ObjectId('66732094a7ea7521fffe9c57'),
    name: 'Chequing Account',
    account_number: Binary.createFromBase64('AiIMmmNzmkjahqJVcfByoSsC8svek5AUPu+OB7IAc92cFfia3ezc+m/d05fji0EGhp/DNF7ng5oO1C5Qe0b8vTDD2U4zjXRsX1e6b+Le0xkIngfTxlKqEiamrUZkenTmlQ8=', 6),
    account_type: 'Chequing',
    bank_name: Binary.createFromBase64('AiIMmmNzmkjahqJVcfByoSsCgGgZ2bENMFyVTeUz1F93+arEbaJIQjBVnnISHMhOo3M1PvRfl6jNiisfK3c6SvepxzfyyGkBNYnhVWKkLAtOkKio9V4ZHOfPMIYyrxgnd1k=', 6),
    user_id: ObjectId('66731cf9a7ea751d4555faf6'),
    updated_at: ISODate('2024-06-19T18:16:52.859Z'),
    created_at: ISODate('2024-06-19T18:16:52.859Z'),
    encryption_key_name: '7c815580ad3844bcb627c74d24eaf700e1a711d9c23e9beb62ab8d28e8cb7954'
  }
]
Enter fullscreen mode Exit fullscreen mode

…you’ll see unreadable binary data—proof that encryption happens before MongoDB ever receives the record.

Deterministic vs. randomized encryption

Mongoid lets you choose how each field is encrypted:

Mongoid lets you choose how each field is encrypted

So you can safely do:

BankAccount.where(account_number: "123456789")
Enter fullscreen mode Exit fullscreen mode

If that field uses deterministic encryption.

For more variations on how our model can be configured, see Mongoid’s automatic client-side field level encryption tutorial.

Seamless Rails integration

Ultimately, Mongoid’s CSFLE support feels native to the Rails ecosystem:

  • It automatically works with Rails validations, associations, and timestamps.
  • It helps keep your codebase clean. Encryption is simply a declaration, not a responsibility. Developers only have to describe what should happen, not write out how it should happen.
  • It supports Rails-style configuration (mongoid.yml, environment variables). No special setup is required, no additional configuration syntax—it all just works exactly how Rails developers expect.

Developers are free to focus on their app’s logic. Meanwhile, Mongoid takes care of security behind the scenes.

What you get out of the box

  • Automatic encryption and decryption on read or write actions
  • Multi-KMS support (AWS, Azure, GCP, KMIP, or local)
  • Transparent key vault management
  • No schema migrations—just annotate fields in your model
  • Compliance-ready architecture without third-party gems

Final thoughts

Client-side encryption was once something only security engineers dealt with, but with Mongoid 9.0, it’s now firmly in the Rails developer toolbox. You can add enhanced security to your app with a few lines of YAML and a single model declaration. There is no custom cryptography and no painful boilerplate.

Your data is always private, your code stays clean, and your app stays fast. That’s truly the Rails Way.

Top comments (0)