Engine mode
In Engine mode, your app mounts masks inside itself. masks keeps its own database, session, and secrets, apart from the app around it.
Setting up
Section titled “Setting up”-
Add the gem to your Gemfile:
gem "masks-server" -
Run the install generator with one of these commands:
Terminal window bin/rails generate masks:server:install --at /authbin/rails generate masks:server:install --host auth.example.com- The first command mounts masks at
/authon your app’s own domain. - The second command serves masks at the root of a subdomain.
Both write
config/initializers/masks_server.rb:Rails.application.configure doconfig.masks.mode = :engineconfig.masks.database = :masksconfig.masks.tenant = "app"end--databaseand--tenantchange the last two values. When the app has aconfig/puma.rb, the generator addsplugin :masks_ldapto it, so Puma also serves LDAP. - The first command mounts masks at
-
Add a database for masks, as described below.
-
Set
config.masks.public_origin_templateto the issuer, such ashttps://app.example.com/authorhttps://auth.example.com. Outside development and test, masks does not boot without it. -
Run
bin/rails db:prepare. -
Run
bin/rails masks:tenants. It creates the tenant thatconfig.masks.tenantnames, and prints its setup token. -
Open
/auth/login, or/loginon the subdomain, and set up the first account with the setup token.
A database of its own
Section titled “A database of its own”masks keeps its tables in a database of its own. Add it to config/database.yml in each
environment:
masks: <<: *default database: myapp_masks_<%= Rails.env %> username: masks migrations_paths: <%= Masks::Server::Engine.root.join("db/migrate") %> schema_dump: falseIts role must hold neither SUPERUSER nor BYPASSRLS.
Row-level security, which limits each query to one tenant’s
rows, keeps tenants apart, and those roles get around it. masks checks the role the first time it
enters a tenant, and refuses to go on while the role holds either one.
Signing your own people in
Section titled “Signing your own people in”When masks is mounted at a path, it shares your app’s origin. Your controllers read the signed-in actor from masks’ session, with no OAuth round trip:
class DashboardController < ApplicationController include Masks::Server::Host
before_action :require_masks_actor!
def show @actor = masks_actor endendrequire_masks_actor! sends a signed-out visitor to masks’ sign-in page, and back again after they
sign in. masks follows a return path only if the path is on your own app.
On a subdomain, masks has its own origin, and its cookies stay there. Sign your app in with
Client mode, and set MASKS_ISSUER to that subdomain.
Client mode against the masks you mount
Section titled “Client mode against the masks you mount”An app can sign in through Client mode against the masks it mounts, so that it holds access tokens
and refresh tokens the same way it would with a separate masks server. Client mode fetches
discovery, keys, and tokens over HTTP, and those calls go to the app’s own public address.
Masks::Client.embed hands them to the engine inside the process, so they work when the app cannot
reach its own public address:
Masks::Client.embed(->(env) { Rails.application.executor.wrap { Masks::Server::Engine.call(env) } }) do |uri| uri.host == "auth.example.com"end- The block names the URLs to keep in the process. Every other URL goes over the network.
- A plain
httpURL is still refused unless its host is a loopback host orMasks::Client.plain_httpis set. Masks::Client.embed(nil)turns it off.
When masks is mounted at a path, set SCRIPT_NAME to the path and remove it from PATH_INFO
before you call the engine.
What stays apart
Section titled “What stays apart”| masks uses | |
|---|---|
| Tables | Its own database, through config.masks.database. |
| Session | Its own cookie, _masks_session. Signing in to masks never resets your app’s session. |
| Secrets | config.masks.secret, or a secret derived from your app’s key under a label of its own. Your app’s cookie jar cannot read masks’ cookies. |
| Encrypted columns | Its own key, derived the same way. From that key, one key for each tenant. |
| Jobs | Its own job and mail classes carry a tenant. Your app’s jobs are left alone. |
Settings
Section titled “Settings”| Setting | |
|---|---|
config.masks.mode |
:server or :engine. MASKS_MODE sets it too. |
config.masks.database |
The database.yml entry masks uses in Engine mode. Required in Engine mode. |
config.masks.tenant |
The tenant your app’s people belong to. MASKS_TENANT sets it too. |
config.masks.secret |
The secret masks signs and encrypts with in Engine mode. Optional. |
config.masks.public_origin_template |
The issuer, including the path it is mounted at. MASKS_PUBLIC_ORIGIN_TEMPLATE sets it too. |
masks reads its other settings from the environment. See Environment variables.