graphqlAD - a read-only GraphQL API for Microsoft Active Directory
2026-10-25
graphqlAD
graphqlAD is a read-only GraphQL interface over the msad Active Directory / LDAP library, built with Strawberry + FastAPI, managed with uv on Python 3.14.
The schema has a single Query type and no Mutation type, so no write operation (create / modify / delete / password reset / group membership changes) is reachable through the API — only LDAP search operations are exposed.
It is a GraphQL sibling of restAD (REST) and mcpAD (MCP): all three are thin, read-only front ends calling the same typed msad API directly.
Typed objects and virtual fields
This is where graphqlAD earns its keep over a plain JSON API: queries that return users, groups, computers or OUs expose typed GraphQL objects (User, Group, Computer, Ou) instead of bare JSON. Each one carries:
raw— the complete, dynamic LDAP entry as aJSONscalar (nothing is lost).- a few convenience identity scalars (e.g.
samAccountName,displayName,cn). - virtual fields — DN-valued attributes expanded into full objects, resolved lazily (only when selected) through a per-request DataLoader that deduplicates and batches lookups:
User.managerUser,User.memberOfGroups,User.directReportsUsersGroup.managedByUser,Group.memberUsers,Group.memberGroups,Group.memberOfGroupsGroup.members: [GroupMember!]!— every member resolved to its concrete type via aUser | Group | Computerunion, so nested groups and computers are kept (not just flattened to users)Computer.managedByUser,Ou.managedByUser
That last point means you can write a query like this and walk the directory graph in one round trip:
{
group(identifier: "qlik_analyzer_users") {
cn
managedByUser { displayName mail }
members {
__typename
... on User { samAccountName displayName }
... on Group { cn }
... on Computer { dnsHostName operatingSystem }
}
}
}
Queries whose results mix object types (e.g. OU contents) or have no DN worth expanding (e.g. contacts) just return the generic Entries/Entry JSON envelope instead.
Available queries
health- Users (→
User) —users,inactiveUsers,user,userStatus,neverExpiringAccounts,expiredAccounts,passwordNotRequired,kerberosPreauthNotRequired,notDelegated(risk checks) - Computers (→
Computer) —computers(optionallyexcludeDomainControllers: true),staleComputers,domainControllers,osSummary,computer - Groups (→
Group) —groups,emptyGroups,securityGroups(optionallyscope: "global" | "domain_local" | "universal"),group,userGroups,groupMembers(→User),isMember - Contacts —
contacts - OUs (→
Ou) —ous,ou;ouContents(mixed object types) - Audit —
domainInfo,passwordPolicy,passwordPolicyViolations(→User),privilegedGroups(→Group),fsmoRoles,trusts,sites,gpos,gpo,fineGrainedPasswordPolicies
Authentication and configuration
Same model as restAD: the effective connection config is resolved from an optional ~/.msad.toml (or MSAD_CONFIG_FILE), with MSAD_* environment variables (MSAD_HOST, MSAD_BASE, MSAD_PORT, MSAD_USE_SSL, MSAD_USER, MSAD_PASSWORD) always winning. Omitting MSAD_USER/MSAD_PASSWORD falls back to Kerberos/SASL (run kinit first); setting both uses a simple user/password bind.
Server behavior is controlled by GRAPHQLAD_HOST, GRAPHQLAD_PORT, GRAPHQLAD_LAZY_CONNECT and GRAPHQLAD_GRAPHIQL (toggle the in-browser GraphiQL IDE, on by default).
Running
export MSAD_HOST=dc.example.com
export MSAD_BASE='dc=example,dc=com'
export MSAD_PORT=636
export MSAD_USE_SSL=true
export MSAD_USER='svc_reader'
export MSAD_PASSWORD='s3cr3t'
uv run graphqlad
# or: uv run uvicorn graphqlad.app:app --host 0.0.0.0 --port 8000
Then open GraphiQL at http://127.0.0.1:8000/graphql (the same URL serves POST queries).
Run with uvx
No clone or install needed:
MSAD_HOST=dc.example.com MSAD_BASE='dc=example,dc=com' \
MSAD_USER=svc_reader MSAD_PASSWORD='s3cr3t' \
uvx graphqlad
Kerberos (SASL/GSSAPI) authentication is optional: msad’s gssapi dependency is a native extension that needs the system Kerberos dev headers to build (e.g. libkrb5-dev on Debian/Ubuntu, krb5-devel on RHEL/Fedora, krb5 via Homebrew on macOS). If you only authenticate with user/password, skip it entirely; otherwise install the kerberos extra:
uvx --from 'graphqlad[kerberos]' graphqlad
Why it is useful
GraphQL’s single-request, client-driven field selection is a natural fit for directory data: instead of chaining multiple REST calls to resolve a user’s manager or a group’s members, a client asks for exactly the shape it needs — including the DN-expanded objects — in one query, and gets back only what it asked for. Combined with the read-only-by-construction schema, it’s a safe way to let dashboards, internal tools or AI agents explore Active Directory without any risk of mutating it.
The code is available on GitHub and the package is published on PyPI.