Publish NuGet packages with Trusted Publishing and GitHub Actions
Publishing a NuGet package from GitHub Actions has traditionally required a NuGet API key stored as a repository or organization secret.
That works, but it means keeping a reusable publishing credential outside NuGet.org and rotating it whenever it expires.
NuGet Trusted Publishing replaces that stored secret with an identity relationship between NuGet.org and a specific GitHub repository and workflow. The workflow authenticates through OpenID Connect, receives a temporary API key, and uses it for the final dotnet nuget push operation.
Microsoft has announced that newly created NuGet API keys will be limited to 30 days starting August 17, 2026. Existing keys created before that date will expire on November 1, 2026. These dates create some urgency around the migration, but the Trusted Publishing setup described in this article remains useful after those deadlines have passed.
In this article, I will migrate my NuGet package GitHub Actions sample publishing workflow from a stored API key to Trusted Publishing.
The old publishing flow
A typical publishing step retrieves a reusable API key from GitHub Secrets and passes it to dotnet nuget push.
- name: Publish to NuGet.org
run: >-
dotnet nuget push
"./artifacts/packages/*.nupkg"
--source https://api.nuget.org/v3/index.json
--api-key "$"
The workflow does not know how the key was created or who created it. Possession of the secret is enough to publish packages within the permissions assigned to that key.
The setup also creates an operational dependency: somebody has to create the key, copy it into GitHub, and replace it before it expires.
What Trusted Publishing changes
Trusted Publishing changes what NuGet.org trusts.
Instead of trusting any process that possesses a reusable API key, NuGet.org trusts a specific GitHub identity composed of:
- the repository owner;
- the repository;
- the workflow file;
- optionally, a GitHub Actions environment.
When the workflow runs, GitHub issues an OIDC token describing the job identity. NuGet.org validates that token against the configured Trusted Publishing policy.
If the identity matches, NuGet.org returns a temporary API key. The workflow then passes that key to dotnet nuget push just as it did before.
There is still an API key involved in the final publishing operation. The difference is that it is created for the workflow run instead of being stored as a reusable secret.
Create the Trusted Publishing policy
Sign in to NuGet.org and open Trusted Publishing from the account menu.
Create a new policy and select the NuGet owner whose packages the workflow should be allowed to publish. The owner can be an individual account or a NuGet organization.
For the sample repository, the GitHub policy uses these values:
Repository owner: Kralizek
Repository: NuGetPackageGitHubActions
Workflow file: ci.yml
Environment: optional
The workflow value is only the filename. For a workflow stored at:
.github/workflows/ci.yml
enter:
ci.yml
Do not include the .github/workflows/ path.
A policy applies to packages owned by the selected NuGet owner. It is not limited to a single package identifier. This is worth considering when an account or organization owns several packages: the configured repository and workflow receive publishing authority for that owner.
A GitHub environment can narrow the identity further. When the policy specifies an environment, the publishing job must use the same environment for its OIDC identity to match.
Allow the job to request an OIDC token
The publishing job needs permission to request an OIDC token from GitHub:
publish:
permissions:
contents: read
id-token: write
The relevant permission is:
id-token: write
This permission does not allow the workflow to publish NuGet packages by itself. It only allows the job to request an OIDC token.
NuGet.org still decides whether to accept the identity by comparing the token with the Trusted Publishing policy.
The permission can therefore remain limited to the publishing job. Build, test, and package-validation jobs do not need it.
Exchange the GitHub identity for a temporary key
Add the NuGet/login action shortly before publishing:
- name: Log in to NuGet.org
id: nuget-login
uses: nuget/login@v1
with:
user: Kralizek
The action requests an OIDC token from GitHub and exchanges it with NuGet.org.
When the identity matches the policy, the action exposes the temporary API key through its output:
$
The user value is the NuGet.org profile name, not the email address used to sign in.
It is not a secret, although storing it in a GitHub variable can be useful when the same workflow is reused across repositories or package owners.
Use the temporary key to publish
The push command itself changes very little:
- name: Publish to NuGet.org
run: >-
dotnet nuget push
"./artifacts/packages/*.nupkg"
--source https://api.nuget.org/v3/index.json
--api-key "$"
--skip-duplicate
The important change is the source of the key.
Before:
--api-key "$"
After:
--api-key "$"
The workflow now requests the credential when it needs it and discards it when the job ends.
There is no NuGet publishing key to copy into GitHub, preserve between runs, or rotate every month.
Keep authentication close to publishing
The temporary key is valid for approximately one hour, so there is little reason to request it at the beginning of a job and then spend time building and testing the solution.
In the sample workflow, compilation and packaging happen in a separate job. The publishing job downloads the resulting artifacts, obtains the temporary credential, and pushes the package.
A simplified version looks like this:
jobs:
validate-and-pack:
permissions:
contents: read
steps:
- name: Build
run: dotnet build --configuration Release
- name: Test
run: dotnet test --configuration Release --no-build
- name: Pack
run: >-
dotnet pack
--configuration Release
--no-build
--output ./artifacts/packages
- name: Upload packages
uses: actions/upload-artifact@v7
with:
name: nuget-packages
path: ./artifacts/packages/*.nupkg
publish:
needs: validate-and-pack
if: github.event_name == 'release'
permissions:
contents: read
id-token: write
steps:
- name: Download packages
uses: actions/download-artifact@v8
with:
name: nuget-packages
path: ./artifacts/packages
- name: Log in to NuGet.org
id: nuget-login
uses: nuget/login@v1
with:
user: Kralizek
- name: Publish to NuGet.org
run: >-
dotnet nuget push
"./artifacts/packages/*.nupkg"
--source https://api.nuget.org/v3/index.json
--api-key "$"
This keeps authentication close to the operation that needs it.
The separation also means that the job holding publishing permissions does not compile arbitrary repository code. It receives packages already produced by the validation job and only performs the release-specific work.
Remove the old secret
After a successful release through Trusted Publishing, remove the old NuGet API key from the repository or organization secrets.
Keeping it around as an undocumented fallback defeats part of the migration. It remains a reusable publishing credential even though the workflow no longer needs it.
You can also revoke the corresponding key from NuGet.org after confirming that no other release process depends on it.
Check organization-level secrets and reusable workflows as well. A key may be shared by more repositories than the one currently being migrated.
Limitations and operational details
Trusted Publishing authenticates supported CI/CD workloads such as GitHub Actions. A local dotnet nuget push still needs a conventional NuGet API key.
The policy also depends on the configured identity remaining consistent. Renaming the repository, renaming or moving the workflow file, or changing the GitHub environment can cause the exchange to fail until the NuGet policy is updated.
Trusted Publishing authenticates the publishing workflow; it does not replace the rest of the release process. The package should still be built, tested, validated, and produced from a controlled revision before the publishing job starts.
Finally, consider the scope of the selected NuGet owner. The policy is broader than one package, so the repository and workflow should be protected accordingly. A GitHub environment with required reviewers can be useful when publishing needs an explicit approval step.
Recap
Traditional NuGet publishing relies on a reusable API key stored in GitHub Secrets.
Trusted Publishing replaces that secret with an identity relationship between NuGet.org and a specific GitHub repository and workflow.
The migration requires three changes:
- Create a Trusted Publishing policy on NuGet.org.
- Grant the publishing job
id-token: write. - Use
NuGet/loginto obtain a temporary key immediately beforedotnet nuget push.
The push command remains familiar.
The long-lived publishing secret disappears.
Support this blog
If you liked this article, consider supporting this blog by buying me a pizza!