Skip to main content
Condo uses session-based authentication. A successful sign-in mutation returns a session token that is also set as an HTTP cookie. Subsequent requests carry that cookie to identify the authenticated user and scope all data access to their organization memberships.

Authentication methods

Condo provides two primary sign-in mutations:
MutationUse case
authenticateUserWithEmailAndPasswordStaff users and service integrations
authenticateUserWithPhoneAndPasswordStaff and resident users with a phone-based account
1

Sign in with email and password

Send the sign-in mutation. The dv and sender fields are required on all write operations. userType should be staff for human users or service for programmatic integrations.
mutation SignIn {
  authenticateUserWithEmailAndPassword(data: {
    dv: 1
    sender: { dv: 1, fingerprint: "my-integration-v1" }
    userType: staff
    email: "[email protected]"
    password: "s3cr3t"
  }) {
    token
    item {
      id
      name
      email
    }
  }
}
A successful response:
{
  "data": {
    "authenticateUserWithEmailAndPassword": {
      "token": "<session-token>",
      "item": {
        "id": "<user-id>",
        "name": "Jane Smith",
        "email": "[email protected]"
      }
    }
  }
}
2

Attach the session to subsequent requests

The session is maintained in two ways simultaneously:Cookie (browser clients): The server sets a Set-Cookie header on the sign-in response. Browsers and clients configured with credentials: 'include' send the cookie automatically on every subsequent request.Authorization header (server-to-server): Pass the token returned by the mutation in the Authorization header:
curl -X POST https://condo.example.com/api/graphql \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <session-token>' \
  -d '{"query": "{ authenticatedUser { id name } }"}'
3

Verify the authenticated session

Confirm the session is active and retrieve the current user:
query WhoAmI {
  authenticatedUser {
    id
    name
    email
    isAdmin
  }
}
4

Make an authenticated data query

With a valid session, query data scoped to the user’s organization:
query MyOrganizations {
  allOrganizationEmployees(
    where: { user: { id: "<user-id>" }, isRejected: false, isBlocked: false }
  ) {
    organization {
      id
      name
    }
    role {
      name
      canManageTickets
    }
  }
}

Sign in with phone and password

For accounts created with phone numbers, use authenticateUserWithPhoneAndPassword:
mutation SignInWithPhone {
  authenticateUserWithPhoneAndPassword(data: {
    phone: "+79991234567"
    password: "s3cr3t"
  }) {
    token
    item {
      id
      name
      phone
    }
  }
}
Phone numbers must be in E.164 format (e.g. +79991234567). The dv, sender, and userType fields have defaults for the phone mutation and can be omitted.

Service user accounts

For programmatic integrations that need persistent, non-human access, Condo supports service users (type: service). Service users can be created by admins via the registerNewServiceUser mutation:
mutation CreateServiceUser {
  registerNewServiceUser(data: {
    dv: 1
    sender: { dv: 1, fingerprint: "admin-cli" }
    name: "Billing Integration"
    email: "[email protected]"
  }) {
    id
    email
    password
  }
}
The returned password is generated server-side and returned directly in the response. Store it securely — it is not retrievable afterward via the API. Service users authenticate with authenticateUserWithEmailAndPassword using userType: service:
mutation SignInAsService {
  authenticateUserWithEmailAndPassword(data: {
    dv: 1
    sender: { dv: 1, fingerprint: "billing-integration" }
    userType: service
    email: "[email protected]"
    password: "<generated-password>"
  }) {
    token
    item { id name }
  }
}

Access control model

Every authenticated request is evaluated against two layers of access control:

Organization scope

Queries and mutations are automatically filtered to records belonging to organizations where the user is a non-rejected, non-blocked employee.

Role-based permissions

The OrganizationEmployee role controls fine-grained permissions: canManageTickets, canManageProperties, canManageEmployees, and more.

UserRightsSet

Admins can grant specific users elevated rights (e.g. canExecuteRegisterNewServiceUser) without full admin access.

Admin override

Users with isAdmin: true bypass organization-scoped filters and can access all records in the system.

Two-factor authentication

If a user has 2FA enabled (isTwoFactorAuthenticationEnabled: true), the sign-in mutation returns a NOT_ENOUGH_AUTH_FACTORS error containing an authDetails object with availableSecondFactors and maskedData. Submit the secondFactor field with the OTP token on the second call:
mutation SignInWith2FA {
  authenticateUserWithEmailAndPassword(data: {
    dv: 1
    sender: { dv: 1, fingerprint: "my-app" }
    userType: staff
    email: "[email protected]"
    password: "s3cr3t"
    secondFactor: {
      type: CONFIRM_PHONE_TOKEN
      value: "<otp-code>"
    }
  }) {
    token
    item { id }
  }
}

Session configuration

Session secrets are configured via environment variables on the server. The cookie lifetime and secure flags follow standard Keystone 5 session configuration. For production deployments:
  • Set a strong COOKIE_SECRET environment variable
  • Serve the API over HTTPS and set secure: true on the session cookie
  • Configure sameSite appropriately for your frontend origin

Build docs developers (and LLMs) love