ErtisAuth

Applications

Machine clients and their secrets.

An application is a machine client of ErtisAuth: a backend service, a worker, a server-side web application. Applications don't sign in; they send a Basic token with every request:

http
Authorization: Basic <application_id>:<secret>

Like users, applications have a role and may have permissions of their own (permissions, forbidden), evaluated as described in Authorization.

Typical uses:

  • a backend that manages users on behalf of your product (sign-up, profile pages, admin screens),
  • the server side of your password reset and activation pages, which call ErtisAuth with the application's token,
  • services protected by the SDK that call each other.

The application object#

json
{
	"_id": "66f1c0d2a4b5c6d7e8f90126",
	"name": "Backend",
	"slug": "backend",
	"role": "backend-service",
	"permissions": [ "users.create" ],
	"forbidden": [],
	"membership_id": "66f1c0d2a4b5c6d7e8f90123",
	"sys": { "created_at": "2026-01-01T12:00:00Z", "created_by": "admin" }
}
FieldRequiredDescription
nameyesDisplay name.
slugnoUnique in the membership; derived from the name when omitted.
roleyesThe slug of an existing role.
permissions, forbiddennoUBAC entries of the application (resource.action.object).

The secret is never part of the application object.

The secret#

  • A secret is generated when the application is created and returned only in that response.
  • ErtisAuth stores only a hash of it. Nobody, administrators included, can read it back.
  • It doesn't expire. Replace it with rotate the secret when it may have leaked, or as a routine.

Store the secret like a password: in a secret store or an environment variable, never in source control or in a browser.

Endpoints#

All routes are under /memberships/{membershipId}.

MethodRouteDescriptionPermission
GET/applications/{id}Get an application (id or slug)applications.read.{id}
GET/applicationsList applicationsapplications.read
POST/applications/_queryQuery applicationsapplications.read
GET/applications/search?keyword=Search applicationsapplications.read
POST/applicationsCreate an applicationapplications.create
PUT/applications/{id}Update an applicationapplications.update.{id}
POST/applications/{id}/secretRotate the secretapplications.update.{id}
DELETE/applications/{id}Delete an applicationapplications.delete.{id}
DELETE/applicationsDelete several applicationsapplications.delete

An application can always read and update its own record, unless its role forbids it.

Create an application#

shell
curl -X POST https://auth.example.com/memberships/<membership_id>/applications \
	-H 'Authorization: Bearer <access_token>' \
	-H 'Content-Type: application/json' \
	-d '{
		"name": "Billing Service",
		"role": "billing"
	}'

Response 201 Created

json
{
	"_id": "66f1c0d2a4b5c6d7e8f90140",
	"name": "Billing Service",
	"slug": "billing-service",
	"role": "billing",
	"membership_id": "66f1c0d2a4b5c6d7e8f90123",
	"secret": "<application_secret>",
	"sys": { "created_at": "2026-01-01T12:00:00Z", "created_by": "admin" }
}

Save secret now: it can't be read again.

ErrorWhen
400 ModelValidationErrorMissing name or role, unknown role, invalid slug, or the same expression in both lists
404 MembershipNotFoundThe membership does not exist
409 ApplicationAlreadyExistsThe slug is taken

Update an application#

http
PUT /memberships/{membershipId}/applications/{id}

Updates name, slug, role, permissions and forbidden. The secret is not changed here. An update without any change answers 409 IdenticalDocumentError.

Rotate the secret#

http
POST /memberships/{membershipId}/applications/{id}/secret
Authorization: Bearer <access_token>

Response 200 OK: the application with its new secret.

Warning: the old secret stops working immediately. Deploy the new secret to the application right away; services that cache Basic tokens through the SDK may keep accepting the old one for up to their BasicTokenCacheTTL.

Delete an application#

http
DELETE /memberships/{membershipId}/applications/{id}

Response 204 No Content. Its Basic token stops working.

Using the Basic token#

shell
curl https://auth.example.com/memberships/<membership_id>/users?limit=10 \
	-H 'Authorization: Basic 66f1c0d2a4b5c6d7e8f90140:<application_secret>'
  • The token is id:secret as plain text, not base64-encoded.
  • GET /me with a Basic token returns the application.
  • An unknown application, a wrong secret and an unknown membership are all answered with 401 InvalidToken.

Migrating legacy applications#

Applications created before per-application secrets existed authenticated with the membership's secret key as their secret. To keep them working during a migration, a membership can temporarily accept the membership secret for applications that don't have a secret of their own yet:

json
{ "allow_membership_secret_for_applications": true }

This field is part of the membership update request. Migrate each application by rotating its secret and deploying the new one; from then on only its own secret works. When all applications are migrated, set the switch to false.

Note: this switch is temporary and will be removed in a future version. New memberships have it turned off.

Events#

ApplicationCreated, ApplicationUpdated and ApplicationDeleted. See Events.

Found a mistake in the docs? Open an issue