Atom

Overview

Atom is a lightweight Identity and Authorization service for IoT and cloud-native systems.

Atom is a single-binary replacement for Keycloak — an HTTP service that combines identity management and authorization in one deployable unit.

Built for Magistrala, but generic enough for any cloud-native or edge system.

What it does

ConcernWhat Atom provides
IdentityCRUD for any principal type — humans, devices, services, workloads, applications. All are first-class entities.
AuthenticationPassword login returning a JWT, long-lived API keys, session management with revocation.
AuthorizationActions, Action Applicability, Permission Blocks, Roles, Role Assignments, Direct Policies, and ABAC guardrails evaluated at runtime.
GroupingPrincipal Groups define who receives roles; Object Groups define where access applies.
OwnershipParent–child relationships between entities.
Multi-tenancyFirst-class tenants. Magistrala domains map directly to Atom tenants.

Source of truth

This docs site is an operator/developer guide. The product source of truth lives in:

GraphQL

Atom exposes GraphQL at POST /graphql. GraphQL uses the same Bearer token authentication as Auth/OIDC REST endpoints. The schema covers health, login/logout/session lookup, tenants, profiles, profile versions, entities, resources, groups, credentials, ownerships, roles, actions, permission blocks, role assignments, Direct Policies, authz checks, audit logs, and profile-driven entity creation. Non-GraphQL public endpoints are intentionally limited to Auth/OIDC, health, JWKS, and custom endpoint execution.

The Atom Next UI is a separate optional frontend. In Docker Compose it is enabled with the atom-ui profile and uses Atom GraphQL through ATOM_GRAPHQL_URL.

The Atom Next UI contains the GraphQL playground and API Builder. It uses GraphQL introspection and Atom GraphQL operations only, does not inspect raw database tables, and does not provide Magistrala-specific mutations or aliases.

Atom GraphQL is generic. No Magistrala-specific GraphQL aliases exist; use the generic application mappings below.

GraphQL uses typed enums for Atom's fixed vocabularies, including EntityKind, EntityStatus, TenantStatus, Effect, CredentialKind, and AuditOutcome. Inline GraphQL uses enum values without quotes, such as kind: device. When using variables, send the same value as a JSON string, such as "device".

For entity creation, frontends should send profileId when a profile is selected. Atom derives the internal kind from profile.kind; if profileVersionId is omitted, Atom uses the active/latest profile version. Authorization still uses entities.kind, not profileId or profileVersionId.

mutation {
  login(input: {
    identifier: "atom-admin",
    secret: "change-me",
    kind: "password"
  }) {
    token
    entityId
    sessionId
    expiresAt
  }
}
 
mutation {
  createTenant(input: {
    name: "factory-a",
    route: "factory-a"
  }) {
    id
    name
    route
    status
  }
}
 
mutation {
  createEntity(input: {
    profileId: "client-profile-id",
    name: "meter-001",
    attributes: {
      serial_no: "WM-001"
    }
  }) {
    id
    kind
    profileId
    profileVersionId
    attributes
  }
}
 
mutation {
  createResource(input: {
    kind: "channel",
    name: "telemetry",
    attributes: {
      topic: "telemetry"
    }
  }) {
    id
    kind
    name
    attributes
  }
}
 
mutation {
  authzCheck(input: {
    subjectId: "client-entity-id",
    action: "publish",
    resourceId: "channel-resource-id"
  }) {
    allowed
    reason
  }
}

Generic application mapping uses Atom operations directly: a domain-like app calls createTenant, a client-like app calls createEntity with a device/client profile, a channel-like app calls createResource with kind="channel", a role-based app creates Permission Blocks and assigns Roles, and a strict runtime link creates a Direct Policy that references a Permission Block.

Core design principles

Entity-first — everything is an entity. There is no special "user" class. Humans, devices, and services are all modeled identically and treated symmetrically by the authorization engine.

Online authorization — tokens carry no embedded permissions. Every authorization decision calls the PDP at runtime. This means policy changes take effect immediately, without token reissuance.

Deny by default — a matching deny Permission Block overrides matching allows. No matching allow means denied.

Single binary — one Rust binary, one Postgres database. No sidecars, no service mesh required.

Where to start

On this page