This post was most recently updated on January 14th, 2022.
4 min read.Another one in the series of “this should’ve been easy, but alas, you’re the edge case”. “You” being “me”, and the “edge case” being our internal npm feed (registry), for whatever reason. Ah, well – life would be extremely boring if everything always went according to documentation, right?
So, instead of the built-in ways to access npm feeds, this solution includes some PowerShell. Ah – that’s when you know it’s going to be good, right? When someone goes as far as to throw away the Npm and NpmAuthenticate, and even “npm -install –registry”, because none of them worked, we’re getting to seriously desperate levels of irregularity.
This surprisingly painful piece of configuration was something I just figured out for a customer project, where the pipeline I was configuring depended on a number of packages in an “internal” npm feed, that was (unfortunately) published by an internal Team Project in another Azure DevOps organization – still owned and maintained by our customer.
Well, “just figured out” when writing this, not as of publishing the piece. The latter depends on how many typos, misspellings, unnecessary repetition, and irrelevant/incoherent rambling requires fixing. What I’m saying is that you need to be prepared for me to have completely forgotten everything about the topic whenever the post comes out.
That said, let’s take a step back from the void – what was the problem, again?
Problem
Let’s keep this short and sweet.
You have an npm feed in your company’s Azure DevOps organization. You want to access it from a project in another organization. npmAuthenticate, npm login, npm config set registry, npm [whatever] –registry, or any other weird trick won’t work. Verifying you don’t have legacy URLs (like pkgs.visualstudio.com) in your .npmrc or service connection also brought up nothing.
No matter what, You’ll always get a 401 for the feed. Or possibly an error somewhat like below:
npm WARN audit Unable to authenticate, need: Basic realm="https://pkgsprodeus21.pkgs.visualstudio.com/" npm ERR! audit endpoint returned an error
Or:
Error: Unable to authenticate, need: Bearer authorization_uri=https://login.windows.net/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, Basic realm="https://pkgsprodcus1.pkgs.visualstudio.com/", TFS-Federated
npm verb stack at res.buffer.catch.then.body (C:\npm\prefix\node_modules\npm\node_modules\npm-registry-fetch\check-response.js:94:17)
npm verb stack at process._tickCallback (internal/process/next_tick.js:68:7)
npm verb statusCode 401
Or maybe:
npm ERR! code E401 npm ERR! Unable to authenticate, need: Bearer authorization_uri=https://login.windows.net/*********, Basic realm="https://pkgsprodcus1.pkgs.visualstudio.com/", TFS-Federated
What do?
Solution
The solution turned out to be somewhat disgusting, but delightfully simple. Figuring it out pretty much took me a couple of DAYS, but now YOU can implement it for the low cost of 30-ish minutes!
Time needed: 30 minutes
How to authenticate against an npm feed from another Azure DevOps organization?
- Generate a PAT
You will need a Personal Access Token (PAT) for this method. Yes, it’s a bit unwieldy, as it will expire in due time and you will have to refresh it. No, it won’t be stored in cleartext or another easily accessible manner. So, no worries.
First, navigate to https://dev.azure.com/[team project name]/_usersSettings/tokens, and then generate a new token with scope “Packaging” -> “Read & write”.
Note, that this PAT needs to be generated in the team project that publishes the feed, not the one trying to consume it! - Create a .npmrc file
Add a .npmrc file to your project root. This file needs to look somewhat like this:
@myScope:registry=https://pkgs.dev.azure.com/myOtherOrganization/_packaging/feedName/npm/registry/
//https://pkgs.dev.azure.com/myOtherOrganization/_packaging/feedName/npm/registry/:_password={{PAT_TOKEN}}
//https://pkgs.dev.azure.com/myOtherOrganization/_packaging/feedName/npm/registry/:username="whatever"
//https://pkgs.dev.azure.com/myOtherOrganization/_packaging/feedName/npm/registry/:email="whatever@what.ever"
//https://pkgs.dev.azure.com/myOtherOrganization/_packaging/feedName/npm/registry/:always-auth = true
This file defines the scope/namespace of your dependency, the address of the feed, and a token your pipeline can use to authenticate against it. Username and email don’t really matter, but they need to contain some values.
Note the {{PAT_TOKEN}}, though – we’ll use that later. It needs to be in this particular form, though. - Remove any competing authentication methods
Remove npmAuthenticate or any references to –registry from your npm tasks. You won’t be needing them.
- Add a new pipeline variable for your token
Add a new pipeline variable named PAT_TOKEN and set it as secret. Input your Personal Access Token from before in this field and hit save.
- Replace the token value in the .npmrc file
This can be done using the PowerShell task in Azure DevOps Pipeline. See for my example below – don’t rewrite it, though, as there’s a properly copy-pasteable sample further down below!
- Add your npm tasks
Now you can use script-task to add your npm tasks. Technically speaking you could probably also use the Npm-task, but why bother?
And you should be good!
Bonus
npm install
, it returns with ERR! code EINTEGRITY
– what do? If that happens in Azure DevOps, remove your package-lock.json from your repository.
References etc.
- Npm task – we won’t be needing this, but the documentation is very helpful to realize what is and is not possible:
- Npm Authenticate. Maybe it will work for someone.
- None of these solutions helped. But maybe they could help someone else:
- You might also get EINTEGRITY errors for checksums and whatever. See this (or my Bonus FAQ above):
Code sample
Apologies for the line breaks (or lack thereof) – hopefully it’ll paste nicely into a YAML pipeline, though :)
- task: PowerShell@2
displayName: "Replace token in file"
inputs:
targetType: 'inline'
script: |
((Get-Content -path 'projectName/.npmrc' -Raw) -replace '{{PAT_TOKEN}}', "$(PAT_TOKEN)") | Set-Content -Path 'projectName/.npmrc'
- script: npm --prefix '$(Pipeline.Workspace)/s/projectName' ci
displayName: 'npm install'
- script: npm --prefix '$(Pipeline.Workspace)/s/projectName' run build
displayName: 'npm run build'
- publish: '$(Build.ArtifactStagingDirectory)'
displayName: 'Publish the project'
artifact: MyArtifact
- “Performing cleanup” – Excel is stuck with an old, conflicted file and will never recover. - November 12, 2024
- How to add multiple app URIs for your Entra app registration? - November 5, 2024
- How to access Environment Secrets with GitHub Actions? - October 29, 2024