Authenticated Feeds and Credential Providers

This section documents MSBuild 18.9, the .NET SDK 10.0.402 (with its dotnet CLI) and NuGet 7.9, as published in the MSBuild, .NET and NuGet documentation on learn.microsoft.com, which is the reference these pages are written and verified against. Material about .NET 11 / NuGet 8.0 is still in preview and is always flagged as such — never presented as baseline.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The C# Reference bibliography lists the reference material consulted while preparing these pages.

Private feeds — Azure Artifacts, GitHub Packages, AWS CodeArtifact, a self-hosted BaGetter or ProGet — usually require credentials to read as well as to push. NuGet first sends an anonymous request and, on HTTP 401, looks for credentials in a fixed order. This page walks that order against a BaGetter 1.6.5 instance on localhost:5556 configured for basic authentication, with NuGet 7.9.0 (.NET SDK 10.0.402). No real token or host appears; the password used was a random value held in an environment variable. Hosting such a feed is covered on Private Feeds.

Never commit a token or password in nuget.config, a project file or a CI script. Use a credential provider, or keep the secret in the CI system’s secret store and reference it as an environment variable (%LEDGER_FEED_PASSWORD%). A leaked feed token lets anyone read — and often publish — your private packages; rotate it immediately if it is ever committed, even to a branch that is later deleted.

Without credentials

<configuration>
  <packageSources>
    <clear />
    <add key="ledger-private" value="http://localhost:5556/v3/index.json" allowInsecureConnections="true" />
  </packageSources>
</configuration>
~/authfeed/C.csproj : error NU1301: Failed to retrieve information about 'Ledgerline' from remote source 'http://localhost:5556/v3/package/ledgerline/index.json'.
~/authfeed/C.csproj : error NU1301:   Response status code does not indicate success: 401 (Unauthorized).

Pushing fails the same way, before the API key is even used — push first reads the service index:

error: Unable to load the service index for source http://localhost:5556/v3/index.json.
error:   Response status code does not indicate success: 401 (Unauthorized).

1. An environment variable

NuGet first looks for NuGetPackageSourceCredentials_<source name> with the value Username=<user>;Password=<secret> (optionally ;ValidAuthenticationTypes=basic):

env NuGetPackageSourceCredentials_ledger-private="Username=reader;Password=$FEED_PASSWORD" dotnet restore
env NuGetPackageSourceCredentials_ledger-private="Username=reader;Password=$FEED_PASSWORD" \
  dotnet nuget push Ledgerline.1.0.0.nupkg -s ledger-private -k "$BAGETTER_API_KEY"
  Restored ~/authfeed/C.csproj (in 663 ms).
  PUT http://localhost:5556/api/v2/package/
  Created http://localhost:5556/api/v2/package/ 691ms
Your package was pushed.

The name is the source’s key — here ledger-private, with a dash. A POSIX shell cannot export such a name (export NuGetPackageSourceCredentials_ledger-private=… failed with exit code 1), so the variable was passed with env; CI systems that set variables directly do not have that limitation, but a source key without dashes avoids the problem. NuGet ignores a malformed variable silently and logs nothing about which credential it used.

2. packageSourceCredentials in nuget.config

<packageSourceCredentials>
  <ledger-private>
    <add key="Username" value="reader" />
    <add key="ClearTextPassword" value="%LEDGER_FEED_PASSWORD%" />
  </ledger-private>
</packageSourceCredentials>

With LEDGER_FEED_PASSWORD set the restore succeeded (Restored ~/authfeed/C.csproj (in 242 ms)); with it unset, %LEDGER_FEED_PASSWORD% was sent literally and the restore failed with the same 401. The element name is the source key (characters invalid in XML names are encoded). Password instead of ClearTextPassword holds a value encrypted with dotnet nuget update source --password … — Windows only, and decryptable only by the same user on the same machine, so not usable in a committed file.

The section does not have to be written by hand. With the macro quoted so that the shell passes it unchanged, dotnet nuget add source wrote exactly the credentials above:

dotnet nuget add source http://localhost:5556/v3/index.json --name ledger-private \
  --username reader --password '%LEDGER_FEED_PASSWORD%' --store-password-in-clear-text \
  --allow-insecure-connections --configfile nuget.config
Package source with Name: ledger-private added successfully.
<packageSourceCredentials>
  <ledger-private>
      <add key="Username" value="reader" />
      <add key="ClearTextPassword" value="%LEDGER_FEED_PASSWORD%" />
    </ledger-private>
</packageSourceCredentials>

The nuget.exe equivalent (not run here; nuget.exe’s sources command takes the same values as -Name, -Source, -UserName, -Password and -StorePasswordInClearText, plus -AllowInsecureConnections (nuget.exe 6.12+) for this HTTP source; without -StorePasswordInClearText the password is stored encrypted, which again works only on Windows):

nuget sources add -Name ledger-private -Source http://localhost:5556/v3/index.json -UserName reader -Password "%LEDGER_FEED_PASSWORD%" -StorePasswordInClearText -AllowInsecureConnections -ConfigFile nuget.config

In cmd.exe a defined %LEDGER_FEED_PASSWORD% is expanded before nuget.exe sees it, which would write the secret itself into the file; write the macro by hand in that case.

A trap while testing: right after a successful authenticated restore, a restore without credentials also succeeded, because the HTTP cache answered for 30 minutes. Test authentication with an empty global-packages folder and --no-http-cache.

3. Credential providers

A credential provider is a plugin that obtains a token itself — from a developer’s interactive sign-in, a managed identity, or the CI system — so no secret is stored in configuration. The .NET SDK and MSBuild use the cross-platform plugin protocol (V2); nuget.exe also supports the older V1 providers, and Visual Studio has its own interface and ships the Azure Artifacts provider for itself (not for the dotnet CLI).

  • Discovery — by convention under ~/.nuget/plugins/netcore/<name>/<name>.dll (and netfx for .NET Framework hosts), or from paths in NUGET_PLUGIN_PATHS (NUGET_NETCORE_PLUGIN_PATHS for the dotnet CLI only). From NuGet 6.13 (SDK 9.0.200) NuGet also searches PATH for executables named nuget-plugin-*, which is how a provider installed as a .NET (global) tool is found.

  • Known providers — the Azure Artifacts Credential Provider (microsoft/artifacts-credprovider, also installed by the NuGetAuthenticate@1 pipeline task), the AWS CodeArtifact provider, and MyGet’s for Visual Studio. GitHub Packages has none; use a personal access token through one of the first two methods.

  • Interactive or not — a provider may need a browser or device-code sign-in. dotnet restore --interactive (or -p:NuGetInteractive=true for MSBuild) allows it to prompt; without the flag, as in CI, it must obtain a token silently or fail.

Which method where

Situation Use

Developer machine, Azure Artifacts or CodeArtifact

The credential provider, --interactive once.

CI

The CI’s secret store, exposed as NuGetPackageSourceCredentials_<name> or as a %VARIABLE% in the committed nuget.config; or the provider’s CI mode (NuGetAuthenticate@1).

Personal feeds with tokens (GitHub Packages)

A token in the user-level nuget.config (not the repository’s), or an environment variable.

The documentation ranks them: credential provider first, then encrypted credentials (Windows), then %VARIABLE% macros, then raw environment variables, and clear-text values last.

Common mistakes

  • Testing credentials with a warm HTTP cache — success that proves nothing.

  • An unset %VARIABLE% — sent literally; 401.

  • Source keys with dashes and export — the variable is never set.

  • Expecting Visual Studio’s Azure Artifacts provider to work for dotnet — install the CLI provider separately.

  • Credentials in the repository’s nuget.config — see the warning above.