Skip to main content

Authentication endpoints

/auth/* group — account creation, login, session management, password reset and organization invitations.

Endpoint table

MethodPathAuthDescription
POST/auth/bootstrapPublic (1x)Bootstrap the first admin when the instance is empty.
GET/auth/bootstrap-statusPublicCheck whether bootstrap is still available.
POST/auth/register-organizationPublicCreate a new organization and its first admin.
POST/auth/loginPublicEmail + password login. Returns session + cookie.
GET/auth/meSessionCurrent session, user and organizations.
PATCH/auth/me/localeSessionUpdate the user's language preference.
POST/auth/logoutSessionEnd the current session.
POST/auth/password-reset/requestPublicRequest a password-reset email.
POST/auth/password-reset/confirmPublicConfirm a new password with the email token.
GET/auth/organizations/{organization_id}/membersSessionList organization members.
POST/auth/organizations/{organization_id}/membersSession (member:manage)Add a member directly (no invitation).
GET/auth/organizations/{organization_id}/invitationsSessionList pending invitations.
POST/auth/organizations/{organization_id}/invitationsSession (member:manage)Create an email invitation.
GET/auth/invitations/{token}PublicLook up an invitation by token (for the accept screen).
POST/auth/invitations/{token}/acceptPublicAccept the invitation and create the user.

POST /auth/bootstrap

Creates the first administrator and the first organization on a blank instance. Runs only once — afterwards it responds 409.

Request body (AuthBootstrapRequest):

{
"organization_name": "Acme Security",
"organization_description": "Internal SOC",
"email": "admin@acme.com",
"display_name": "Initial Admin",
"password": "strong-password-here"
}

Validations:

  • organization_name — 1–120 chars.
  • email — valid RFC, 3–254 chars.
  • display_name — 1–120 chars.
  • password — at least 10 chars.

Response 200 (AuthSessionResponse) — see session structure.

Errors:

  • 409 conflict — bootstrap already done. Use /auth/login.

GET /auth/bootstrap-status

Tells whether bootstrap is still open and whether self-signup is enabled.

Response 200:

{
"bootstrap_available": false,
"registration_enabled": true
}

POST /auth/register-organization

Same schema as bootstrap, but creates a new organization in an instance that already has others. Depends on NATIVE_WAZUH_SELF_SIGNUP_ENABLED=true.

Request body: identical to AuthBootstrapRequest.

Response 200: AuthSessionResponse.

Errors:

  • 403 forbidden — self-signup disabled.
  • 409 conflict — email already registered.
  • 429 rate_limited — more than 10 registrations/hour per IP.

POST /auth/login

Email + password login. Returns the session in the body and sets a cookie.

Request body (LoginRequest):

{
"email": "admin@acme.com",
"password": "strong-password-here",
"organization_id": "org_abc123"
}
  • organization_id is optional — used when the user belongs to multiple organizations and wants to pick one explicitly.

Response 200 (AuthSessionResponse):

{
"access_token": "eyJhbGciOi…",
"token_type": "bearer",
"expires_at": "2026-04-25T03:30:00Z",
"user": {
"id": "usr_...",
"email": "admin@acme.com",
"display_name": "Initial Admin",
"is_active": true,
"is_platform_admin": false,
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-04-24T12:00:00Z"
},
"organizations": [ { "id": "org_abc123", "slug": "acme", "name": "Acme Security", "role": "org_admin", "is_active": true, "...": "..." } ],
"current_organization": { "id": "org_abc123", "...": "..." },
"memberships": [ { "id": "mem_...", "organization_id": "org_abc123", "user_id": "usr_...", "role": "org_admin", "...": "..." } ]
}

Besides the body, the response also sets the cookie:

Set-Cookie: ruleforge_session=<jwt>; HttpOnly; SameSite=Lax

curl example:

curl -X POST "$RF_BASE/auth/login" \
-H "Content-Type: application/json" \
-d '{"email":"admin@acme.com","password":"strong-password-here"}'

Errors:

  • 401 unauthorized — invalid email/password.
  • 403 forbidden — user inactive or organization suspended.
  • 429 rate_limited — more than 5 attempts in 15 min.

GET /auth/me

Returns the active session. Useful for the frontend to rehydrate state after refresh.

Auth: session (cookie or Authorization: Bearer <jwt>).

Response 200: AuthSessionResponse — same shape as login. access_token is cookie-session when coming from a cookie (the real JWT is in the cookie).

PATCH /auth/me/locale

Changes the logged-in user's language preference.

Request body (UserLocaleUpdateRequest):

{ "locale": "pt-BR" }

Accepted values: "pt-BR", "en", or null (clears the preference, uses the browser-detected locale).

Response 200: {"status":"ok"}.

POST /auth/logout

Revokes the current session (invalidates the jti on the server) and clears the cookie.

Auth: session.

Response 200: {"status":"logged_out"}.

POST /auth/password-reset/request

Requests a password-reset email. Always returns 200, even if the email doesn't exist — to avoid leaking registered users.

Request body (PasswordResetRequestInput):

{ "email": "admin@acme.com" }

Response 200: {"status":"ok"}.

POST /auth/password-reset/confirm

Sets a new password using the token that came in the email.

Request body (PasswordResetConfirmInput):

{
"token": "reset-token-from-email",
"new_password": "new-strong-password"
}
  • token — 1–256 chars.
  • new_password — at least 10 chars.

Response 200: {"status":"ok"}.

Errors:

  • 400 bad_request — expired or invalid token.

GET /auth/organizations/{organization_id}/members

Lists every member of the organization.

Auth: session. The user must belong to the organization.

Response 200: array of OrganizationMembershipRecord:

[
{
"id": "mem_...",
"organization_id": "org_abc123",
"user_id": "usr_...",
"email": "analyst@acme.com",
"display_name": "Analyst",
"role": "engineer",
"created_at": "...",
"updated_at": "..."
}
]

POST /auth/organizations/{organization_id}/members

Adds a member without invitation — creates the user with an already-defined password. Useful for automated provisioning.

Auth: session with member:manage permission (role org_admin).

Request body (OrganizationMemberCreateRequest):

{
"email": "analyst@acme.com",
"display_name": "New analyst",
"password": "strong-password",
"role": "engineer"
}
  • roleread_only.

Response 200: OrganizationMembershipRecord.

GET /auth/organizations/{organization_id}/invitations

Lists pending/accepted/expired invitations.

Response 200: array of OrganizationInvitationRecord.

POST /auth/organizations/{organization_id}/invitations

Sends an email invitation for a future member.

Request body (OrganizationInvitationCreateRequest):

{
"email": "guest@acme.com",
"role": "reviewer"
}

Response 200: OrganizationInvitationRecord with token, status, expires_at.

GET /auth/invitations/{token}

Look up an invitation by token (used by the public accept screen).

Auth: public.

Response 200: OrganizationInvitationRecord.

POST /auth/invitations/{token}/accept

Accepts the invitation, creates the user (if necessary) and opens a session.

Request body (OrganizationInvitationAcceptRequest):

{
"display_name": "Display Name",
"password": "strong-password"
}
  • display_name is optional when the user already exists.
  • password — at least 10 chars.

Response 200: AuthSessionResponse.

Shared structures

AuthSessionResponse

{
"access_token": "string (JWT or 'cookie-session')",
"token_type": "bearer",
"expires_at": "ISO 8601 UTC",
"user": "UserRecord",
"organizations": ["OrganizationRecord[]"],
"current_organization": "OrganizationRecord",
"memberships": ["OrganizationMembershipRecord[]"]
}

UserRecord

{
"id": "string",
"email": "string",
"display_name": "string",
"is_active": true,
"is_platform_admin": false,
"created_at": "ISO 8601",
"updated_at": "ISO 8601"
}

OrganizationRecord

{
"id": "string",
"slug": "string",
"name": "string",
"description": "string",
"role": "org_admin | security_content_lead | reviewer | engineer | read_only | null",
"is_active": true,
"created_at": "ISO 8601",
"updated_at": "ISO 8601"
}

OrganizationInvitationRecord

{
"id": "string",
"organization_id": "string",
"email": "string",
"role": "EnterpriseRole",
"token": "string",
"status": "pending | accepted | expired | linked",
"expires_at": "ISO 8601",
"created_at": "ISO 8601"
}