Skip to content

netbox-oidc-group-sync

A python-social-auth pipeline step that syncs NetBox Django groups and is_superuser status from an OIDC groups claim.

The gap

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 from REMOTE_AUTH_SUPERUSER_GROUPS -- but it's only ever invoked from RemoteUserBackend.authenticate(), Django's HTTP-header remote-auth path (a REMOTE_USER header set by an upstream reverse proxy). It is never reached by a social-auth/OIDC login.
  • user_default_groups_handler, the step actually wired into SOCIAL_AUTH_PIPELINE for social-auth backends (including OIDC), only assigns a static REMOTE_AUTH_DEFAULT_GROUPS list. It never reads a claim and never touches is_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, which OIDC logins never touch.

NetBox Labs' Enterprise product solves this with a proprietary pipeline step (nbc_auth_extensions.azure_authentication.azuread_map_groups), documented as Entra ID-specific and Enterprise-only.

The fix

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, no fork of NetBox itself.

See Installation to get it running, and Configuration for the settings it reads.

A NetBox-specific gotcha

NetBox 4.x doesn't use Django's stock django.contrib.auth.models.Group -- it defines its own users.models.Group, a completely separate model/table, and User.groups points there instead. sync_groups imports from users.models, not django.contrib.auth.models; getting this wrong produces a TypeError: Field 'id' expected a number but got <Group: ...> at login time, since Django's M2M machinery can't resolve a pk from an instance of the wrong model. Also unlike Django's stock auth.Group-based AbstractUser, NetBox's User model has no is_staff field at all -- only is_superuser.