ErtisAuth

Memberships

Create and manage memberships, the isolated tenants of ErtisAuth.

A membership is an isolated tenant: it owns users, roles, applications and every other resource, and it holds the authentication settings of its users. See Core Concepts.

The first membership is created by the setup. Further memberships are created through this API.

The membership object#

json
{
	"_id": "66f1c0d2a4b5c6d7e8f90123",
	"name": "My Company",
	"slug": "my-company",
	"expires_in": 3600,
	"scoped_token_expires_in": 900,
	"refresh_token_expires_in": 86400,
	"reset_password_token_expires_in": 1800,
	"secret_key": "<at least 32 bytes>",
	"hash_algorithm": "ARGON2ID",
	"encoding": "UTF-8",
	"default_language": "en",
	"user_activation": "active",
	"code_policy": "tv-codes",
	"otp_settings": {
		"host": "https://app.example.com/reset-password",
		"policy": {
			"length": 6,
			"contains_letters": false,
			"contains_digits": true,
			"expires_in": 300,
			"max_attempts": 5
		}
	},
	"mail_providers": [
		{
			"type": "SmtpServer",
			"name": "Company SMTP",
			"slug": "company-smtp",
			"host": "smtp.example.com",
			"port": 587,
			"tls_enabled": true,
			"username": "no-reply@example.com",
			"password": "<password>"
		}
	],
	"sys": { "created_at": "2026-01-01T12:00:00Z", "created_by": "system" }
}
FieldRequiredDescription
nameyesDisplay name. Also the iss claim of the tokens.
slugnoURL-friendly name, unique in the installation. Derived from the name when omitted. Also the aud claim of the tokens.
expires_inyesAccess token lifetime, in seconds. Must be greater than 0.
refresh_token_expires_inyesRefresh token lifetime, in seconds. Must be greater than 0.
scoped_token_expires_innoScoped token lifetime, in seconds. 12 hours when 0 or omitted.
reset_password_token_expires_innoLifetime of password reset tokens, in seconds. 2 hours when omitted.
secret_keyyesThe key all tokens of the membership are signed with (HMAC-SHA256). At least 32 bytes in the membership's encoding.
hash_algorithmyesThe password hash algorithm (see below). There is no default.
encodingnoText encoding used to turn passwords and keys into bytes. UTF-8 by default.
default_languagenoISO 639-1 code of a database locale (e.g. en, tr).
user_activationnoactive: new users must activate their account from an email before they can sign in. passive (default): new users are active immediately.
code_policynoSlug of the code policy used by the device code flow.
otp_settingsnoOne-time password settings. host is required when it is set; policy.max_attempts must be at least 1 (5 by default).
mail_providersnoThe mail providers used by mail hooks, see below.
Warning: the secret_key signs every token of the membership. Anyone who knows it can forge tokens for any user. Changing it invalidates all tokens issued so far, so every user has to sign in again.

Password hash algorithms#

ValueRecommendation
ARGON2IDRecommended for new memberships.
PBKDF2-SHA512, PBKDF2-SHA256Good choices where Argon2 is not acceptable (e.g. FIPS environments).
SHA2-224, SHA2-256, SHA2-384, SHA2-512, SHA2-512-224, SHA2-512-256, SHA3-224, SHA3-256, SHA3-384, SHA3-512Fast hashes, supported for importing existing user databases. Not recommended for new memberships.
SHA1, MD5Legacy, only for importing old user databases.

Fast hashes (SHA, MD5) can be brute-forced quickly if your database leaks. Prefer ARGON2ID unless you are importing password hashes from another system.

The values are also accepted with underscores (SHA2_512).

Mail providers#

mail_providers is a list of the email services the membership can send through. A mail hook refers to one by its slug.

typeFieldsDelivery
SmtpServername, slug, host, port, tls_enabled, username, passwordErtisAuth renders the HTML and sends it over SMTP
SendGridname, slug, apiKeyErtisAuth renders the HTML and sends it with the SendGrid API
MailChimpname, slug, apiKeyA template stored in Mailchimp Transactional (Mandrill) is sent; see Mail Hooks
json
"mail_providers": [
	{ "type": "SendGrid", "name": "SendGrid", "slug": "sendgrid", "apiKey": "<api_key>" },
	{ "type": "MailChimp", "name": "Mandrill", "slug": "mandrill", "apiKey": "<api_key>" }
]

type is case-sensitive.

Note: a membership's response contains its secret key and the credentials of its mail providers. Grant memberships.read only to the operators of the installation.

Endpoints#

MethodRoutePermission
GET/memberships/{id}memberships.read.{id}
GET/membershipsmemberships.read
POST/memberships/_querymemberships.read
GET/memberships/search?keyword=memberships.read
POST/membershipsmemberships.create
PUT/memberships/{id}memberships.update.{id}
DELETE/memberships/{id}memberships.delete.{id}
GET/memberships/settingsmemberships.read
GET/memberships/settings/encodingsmemberships.read
GET/memberships/settings/encodings/defaultmemberships.read
GET/memberships/settings/hash-algorithmsmemberships.read
GET/memberships/settings/hash-algorithms/defaultmemberships.read
GET/memberships/settings/db-localesmemberships.read
GET/memberships/settings/db-locales/defaultmemberships.read

The membership routes are installation-wide: they are not bound to the membership of the caller's token.

Get a membership#

http
GET /memberships/{id}
Authorization: Bearer <access_token>

{id} is the id or the slug. Answers 404 MembershipNotFound when it does not exist.

GET /memberships, POST /memberships/_query and GET /memberships/search?keyword= work as described in API Conventions.

Create a membership#

shell
curl -X POST https://auth.example.com/memberships \
	-H 'Authorization: Bearer <access_token>' \
	-H 'Content-Type: application/json' \
	-d '{
		"name": "Mobile App",
		"expires_in": 3600,
		"refresh_token_expires_in": 2592000,
		"secret_key": "<random string of at least 32 bytes>",
		"hash_algorithm": "ARGON2ID",
		"encoding": "UTF-8"
	}'

Generate the secret key with a secure random generator, for example openssl rand -base64 48.

Response 201 Created: the membership.

The new membership is empty. Create an admin role, a user type and the first users through its endpoints with a token that may access it, or use a separate installation and the setup for a completely new tenant.

ErrorWhen
400 ModelValidationErrorA required field is missing or invalid; data lists every problem
409 MembershipAlreadyExistsThe slug is taken

Update a membership#

http
PUT /memberships/{id}

The body has the same fields as the create request; the id in the route is the one updated, an _id in the body is ignored.

These fields keep their current value when they are omitted (or empty, or 0): name, secret_key, hash_algorithm, encoding, expires_in and refresh_token_expires_in. All other fields are replaced by what you send: omitting mail_providers, otp_settings, code_policy, user_activation, default_language, scoped_token_expires_in or reset_password_token_expires_in clears them. Read the membership first and send it back with your changes.

Warning: passwords are always verified with the membership's current hash_algorithm, and existing hashes are not converted. Changing the algorithm of a membership that has users makes all their passwords stop working; they would all have to reset their password. Choose the algorithm when you create the membership.

Delete a membership#

http
DELETE /memberships/{id}

Answers 204 No Content. A membership that still has resources (users, roles, applications…) can't be deleted: 409 MembershipCouldNotDeleted.

Settings#

GET /memberships/settings returns the values a membership can use, in one response:

json
{
	"encodings": [ { "displayName": "Unicode (UTF-8)", "name": "UTF-8" } ],
	"defaultEncoding": "UTF-8",
	"hashAlgorithms": [ "MD5", "SHA1", "SHA2-224", "SHA2-256", "SHA2-384", "SHA2-512", "SHA2-512-224", "SHA2-512-256", "SHA3-224", "SHA3-256", "SHA3-384", "SHA3-512", "ARGON2ID", "PBKDF2-SHA256", "PBKDF2-SHA512" ],
	"defaultHashAlgorithm": "ARGON2ID",
	"dbLocales": [ { "Name": "None", "ISO6391Code": "none" }, { "Name": "Turkish", "ISO6391Code": "tr" } ],
	"defaultDbLocale": "none"
}

The individual lists are also available under /memberships/settings/encodings, /hash-algorithms and /db-locales, each with a /default endpoint. defaultHashAlgorithm is the recommended algorithm; a membership has no default and must always name one.

Found a mistake in the docs? Open an issue