Skip to content

Identity & SSO

Operator guide for OIDC/SSO login and role-based access control.

Two authentication schemes converge on one authorization path. Machines authenticate with an API key (X-NuGet-ApiKey); interactive humans authenticate with OIDC over a session cookie. Both resolve to the same Principal (subject + role), and every protected action is checked by a single Authorization.Authorize(principal, scope). There are three fixed roles:

RoleScopesCan
Readerauthenticate and browse the UI
Publisherpackage:push, package:unlistpush and unlist/relist packages
Adminadmin + publisher scopeseverything, incl. user & key management

NuGetKeep’s database is the source of truth for a user’s role — the IdP authenticates; NuGetKeep authorizes. A user’s first OIDC login provisions a Reader (or Admin if bootstrap-listed); an admin promotes others on the Users page (/admin/users).

VariableMeaningDefault
NUGETKEEP_OIDC_AUTHORITYOIDC issuer / discovery base URLunset → OIDC disabled
NUGETKEEP_OIDC_CLIENT_IDOIDC client idunset → OIDC disabled
NUGETKEEP_OIDC_CLIENT_SECRETOIDC client secret (confidential code flow + PKCE)unset
NUGETKEEP_OIDC_SCOPESextra scopes beyond openid profile email (space/comma list)none
NUGETKEEP_BOOTSTRAP_ADMINScomma-list of emails/subjects auto-granted Admin on first loginempty

OIDC is enabled only when both AUTHORITY and CLIENT_ID are set. When disabled, the server runs API-key-only exactly as before: no /auth/login route, and unauthenticated hits on the Web admin pages redirect home. (The existing API-key bootstrap — NUGETKEEP_BOOTSTRAP_APIKEY or the once-logged generated key — still applies and is always an Admin key.)

Register a confidential client at your IdP (Entra / Okta / Google / Keycloak / any OIDC-compliant provider) and add the redirect URIs:

  • Sign-in callback: https://<your-host>/signin-oidc
  • Sign-out callback: https://<your-host>/signout-callback-oidc

Then set the env vars and set at least one NUGETKEEP_BOOTSTRAP_ADMINS entry (your email) so there is a first administrator. Example:

Terminal window
NUGETKEEP_OIDC_AUTHORITY=https://login.microsoftonline.com/<tenant>/v2.0
NUGETKEEP_OIDC_CLIENT_ID=<client-id>
NUGETKEEP_OIDC_CLIENT_SECRET=<client-secret>
NUGETKEEP_BOOTSTRAP_ADMINS=you@corp.com
  • Sign in / out: navbar Sign in/auth/login (OIDC challenge) → IdP → back. Sign out is an antiforgery-protected POST /auth/logout that clears both the local cookie and the IdP session.
  • First login provisions a UserAccount (Reader, or Admin if bootstrap-listed) and records the login. An admin manages roles at /admin/users (promote/demote, disable).
  • Web admin pages (/admin/api-keys, /admin/quarantine, /admin/audit, /admin/users) require the Admin role; the MCP setup page (/mcp-server) stays public but its enable/disable toggle is admin-only.
  • API keys are issued as a role ({ "name": "...", "role": "Publisher" }); a key’s permissions are that role’s scopes. Keys created on older NuGetKeep versions are migrated automatically to the nearest role (admin → Admin, push/unlist → Publisher, otherwise Reader).
  • Last-admin protection: you cannot demote or disable the last remaining active Admin.
  • Unknown role strings are rejected (never silently defaulted).
  • A disabled user authenticates at the IdP but is denied all protected actions.
  • With OIDC disabled, existing API-key deployments boot and operate unchanged.

An MCP session’s identity comes from its API key: send X-NuGet-ApiKey: <key> on the /mcp requests and the server resolves the key to a role, then filters the tool surface to that role. No key ⇒ anonymous ⇒ read-only.

Role (api-key)MCP tools
(none) / Readerthe 8 read tools (search_packages, get_package, list_versions, get_readme, assess_package, find_secure_version, package_health, get_dependency_graph)
Publisherthe read tools + request_publish
Adminthe read tools + request_publish + list_quarantined
  • request_publish (Publisher/Admin) imports a package from upstream through the SAME supply-chain gate as a push — a vulnerable/disallowed package is quarantined, not served — and can optionally subscribe it for auto-import. It is the first write MCP tool.
  • list_quarantined (Admin) lists the currently quarantined versions.
  • Filtering removes unauthorized tools from tools/list and rejects an unauthorized CallTool; the authoritative scope check still runs inside each Application handler (defense in depth). The named policies (mcp:publish, mcp:admin) assert the role confers the scope via RolePermissions, so Admin (which holds package:push) also sees request_publish.
  • OIDC bearer for MCP remains out of scope; machine auth stays API-key-based.
  • Custom roles, per-feed permissions, and OIDC machine-to-machine bearer tokens for the API are not yet supported.