Azure DevOps - Always Be Shipping!

How to authenticate against npm registries in Azure DevOps?

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?

  1. 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!

  2. 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.

  3. Remove any competing authentication methods

    Remove npmAuthenticate or any references to –registry from your npm tasks. You won’t be needing them.

  4. 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.

  5. 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!

    This image has an empty alt attribute; its file name is image-4-1024x132.png

  6. 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

When I run 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.

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
mm
5 3 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments