Secrets
master secrets is the dotnet user-secrets equivalent: it stores per-project development secrets in a JSON file outsidethe repo, and MasterController’s layered configuration reads them automatically — so Auth:JwtSecret works on your laptop without ever being committed.
The commands#
$ master secrets set Auth:JwtSecret "$(openssl rand -hex 32)" # write (creates the file)$ master secrets get Auth:JwtSecret # print the raw value (exit 1 if unset)$ master secrets list # every key, values masked$ master secrets list --reveal # ... values shown$ master secrets remove Auth:JwtSecret # delete one key$ master secrets clear # delete the whole file$ master secrets path # where the file lives
| Action | What it does |
|---|---|
set <Section:Key> <value> | Create or update a key. Nested sections are written as nested JSON; existing keys match case-insensitively. |
get <Section:Key> | Write the value to stdout (script-friendly). Exits with code 1 when the key is not set. |
list [--reveal] | Flatten every key to Section:Key = value. Values are masked unless --reveal is passed. |
remove <Section:Key> | Delete one key (warns if it was not set). |
clear | Delete the secrets file for this project. |
path | Print the absolute path of the secrets file. |
Keys use : to separate sections — exactly the keys you read with master.configuration.get('Auth:JwtSecret'), and the same keys you set as Auth__JwtSecret environment variables in production.
Where the file lives#
Secrets are stored in ~/.master/usersecrets/<id>/secrets.json, created with 0700/0600 permissions. The id is the backend package name (backend/package.json → name, e.g. my-app-backend), falling back to the root package name, then the folder name.
{
"Auth": {
"JwtSecret": "2f6c9d7e...a1b4"
}
}--id <id>targets a different secrets id (useful when several apps share one file).MASTER_USER_SECRETS_PATHoverrides the base directory (~/.master/usersecrets).
How the backend reads them#
The scaffolded backend/server.js sets master.userSecretsId to the backend package name before building the server, so MasterController adds the secrets file as a configuration source — no extra code in your controllers:
const pkg = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf8'));
process.env.NODE_ENV ??= 'development';
master.root = __dirname;
master.environmentType = process.env.NODE_ENV;
master.userSecretsId = pkg.name; // -> ~/.master/usersecrets/<pkg.name>/secrets.json
const server = master.setupServer('http');
const config = master.configuration;
// Opt-in JWT auth: present only when the secret is configured.
const jwtSecret = config.get('Auth:JwtSecret') || process.env.JWT_SECRET;
if (jwtSecret) {
master.auth.addJwtBearer('bearer', {
secret: jwtSecret,
issuer: config.get('Auth:Issuer', pkg.name),
expiresIn: config.get('Auth:ExpiresIn', '1h'),
});
master.auth.addPolicy('AdminOnly', { roles: ['admin'] });
}Inside a controller the same values are available as this.configuration.get('Auth:Issuer') or, through the options pattern, this.options.<Name>.
Configuration precedence#
Later sources win. User secrets sit above every file in the repo and below real environment variables:
backend/config/appsettings.jsonbackend/config/appsettings.<NODE_ENV>.jsonbackend/config/environments/env.<NODE_ENV>.json(the MasterRecord database config)- User secrets —
~/.master/usersecrets/<id>/secrets.json - Environment variables —
Section__Key(__becomes:) - Command line —
--Section:Key=value
See Configuration for the whole picture.
Production: environment variables#
The secrets file exists only on developer machines. On a server, set the same keys as environment variables with __ in place of : — they land on the same configuration keys, so application code never changes:
NODE_ENV=production
Auth__JwtSecret=<a long random value, 32+ characters>
Auth__Issuer=my-app
Logging__LogLevel__Default=warnmaster secrets stores values in plain JSON protected only by file permissions. It keeps secrets out of git and out of .envfiles — it is not a vault. In production use environment variables or your platform’s secret store.Auth:JwtSecret must be at least 32 characters. openssl rand -hex 32 produces 64 — plenty.