Skip to content

Connected ID

A ConnectedId is an identity badge: who a person is inside one organization. Org-scoped actions, permissions, and audit attribution all attach to it.

Everyone who exists inside your org carries one — an employee, a service account, a team, and every end user of your applications. It is not a “members only” table.

Do not mix these up. Almost every bug in this area comes from asking one axis with the other.

AxisQuestionField
SubjectWhose badge is this?UserId (internal person) · EndUserId (your app’s customer) · both null + Type = Group (team)
GradeWhat is this person’s standing as a member?MembershipTypeOwner, Boardmember, Admin, Member, Guest, ServiceAccount, None

Grade only means something for members. A customer’s badge carries MembershipType.None. Guest is a different thing entirely: an external person you invited (the ORG_GUEST role) — a contractor, an auditor. They are members.

To ask “is this person a member of the org”, ask the subject axis (UserId != null). Asking Type != Group instead means “any human badge”, which includes your customers.

A badge is per organization, not per app.

LayerScopeKey
EndUserone organizationOrganizationId + email
ConnectedId (badge)one organizationissued once per person per org
AppAccessone app groupAppGroupId × EndUserId

So the same human can be a customer of Org A and of Org B — two separate end users, two separate badges. And inside Org B they can have access to app group A but not app group B — that split lives in AppAccess, never in the badge.

If someone signing in to your app already holds a member badge in that org (same email), Axowl links the end user to that existing badge instead of issuing a second one. One person, one badge per org — no duplicate identity, no double billing.

That is what is_employee and the cid claim report:

is_employee = false → the badge's subject is an end user (customer only)
is_employee = true → the badge's subject is an internal User (also an employee)

AxowlPrincipal.ConnectedId is the badge id, and it is set for every end user. It is not the end user’s id — that is EndUserId.

MembershipType is derived, not set directly

Section titled “MembershipType is derived, not set directly”

MembershipType is a projection of the member’s highest RBAC role level, not an independent field:

  • Role.Level Owner → Owner; Admin → Admin; Write/Read → Member; no roles → Member.
  • It is recomputed by MembershipTierService.RecomputeForMemberAsync whenever roles change (MembershipTierService.cs:24).
  • Excluded from derivation (set explicitly): ServiceAccount, Guest, Group, Boardmember.
  • None is not derived either — it is what a customer badge carries, because grade is a membership concept and a customer is not a member.

ConnectedIds participate in org structure via an edge table — member_of, reporting, and reference relationships (teams, reporting lines). Reporting hierarchy is many-to-many and nestable.