A python-social-auth pipeline step that syncs NetBox
Django groups and is_superuser status from an OIDC groups claim.
NetBox Community ships two independent group-sync code paths under netbox.authentication:
RemoteUserBackend.configure_groups()implements dynamic, claim-based group sync (with optional auto-create) plus superuser evaluation fromREMOTE_AUTH_SUPERUSER_GROUPS-- but it's only ever invoked fromRemoteUserBackend.authenticate(), Django's HTTP-header remote-auth path (aREMOTE_USERheader set by an upstream reverse proxy). It is never reached by a social-auth/OIDC login.user_default_groups_handler, the step actually wired intoSOCIAL_AUTH_PIPELINEfor social-auth backends (including OIDC), only assigns a staticREMOTE_AUTH_DEFAULT_GROUPSlist. It never reads a claim and never touchesis_superuser.
So if you're using social_core.backends.open_id_connect.OpenIdConnectAuth (or another social-auth OIDC
backend) as your NetBox login method, setting REMOTE_AUTH_GROUP_SYNC_ENABLED / AUTO_CREATE_GROUPS /
SUPERUSER_GROUPS in your NetBox configuration has no effect whatsoever -- those settings are consumed
exclusively by the header-based backend. NetBox Labs' Enterprise product solves this with a proprietary
pipeline step (nbc_auth_extensions.azure_authentication.azuread_map_groups, Enterprise-only, Entra
ID-specific).
sync_groups closes that gap for Community: dropped into SOCIAL_AUTH_PIPELINE in place of
user_default_groups_handler, it re-implements configure_groups()'s logic against the OIDC response
instead of an HTTP header, reusing the exact same REMOTE_AUTH_* settings NetBox already defines -- no new
configuration surface.
pip install netbox-oidc-group-syncThen, in your NetBox configuration.py (or an extraConfig block if you're deploying via the
netbox-chart Helm chart):
SOCIAL_AUTH_PIPELINE = (
"social_core.pipeline.social_auth.social_details",
"social_core.pipeline.social_auth.social_uid",
"social_core.pipeline.social_auth.social_user",
"social_core.pipeline.user.get_username",
"social_core.pipeline.user.create_user",
"social_core.pipeline.social_auth.associate_user",
"netbox_oidc_group_sync.sync_groups", # replaces netbox.authentication.user_default_groups_handler
"social_core.pipeline.social_auth.load_extra_data",
"social_core.pipeline.user.user_details",
)The package itself needs to be on the NetBox pod's Python path -- if you're running the stock
netboxcommunity/netbox image, that means building a derivative image that pip installs it (see
docs/installation.md for a working Dockerfile example).
Reuses NetBox's own settings -- nothing new to configure beyond what a RemoteUserBackend deployment would
already set:
| Setting | Effect |
|---|---|
REMOTE_AUTH_GROUP_SYNC_ENABLED |
Master on/off switch. sync_groups no-ops entirely when falsy. |
REMOTE_AUTH_GROUP_HEADER |
The key to look up in the OIDC claims/userinfo response dict for the user's group list. Named for its original HTTP-header use case; repurposed here as a claim key, which doesn't conflict with anything since it has no effect on social-auth logins upstream. |
REMOTE_AUTH_AUTO_CREATE_GROUPS |
Create a Django Group for a claimed group name that doesn't exist yet, instead of skipping it with a logged error. |
REMOTE_AUTH_SUPERUSER_GROUPS |
Group names that grant is_superuser when present in the user's synced claim groups. |
REMOTE_AUTH_SUPERUSERS |
Usernames that are always superusers, regardless of group membership. |
Group membership is a full sync, not additive: a user's Django groups are set to exactly what the claim
says on every login (matching configure_groups()'s own semantics), including clearing all groups -- and
revoking superuser status -- if the claim comes back empty or absent.
uv sync
uv run pre-commit install
uv run pytest --cov=src --cov-report=term-missingDocs are built with Zensical:
uvx zensical serve