-
Notifications
You must be signed in to change notification settings - Fork 252
Add RFC: Credential Provider Plugin Protocol for Secure NPM Authentication #850
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add RFC: Credential Provider Plugin Protocol for Secure NPM Authentication #850
Conversation
|
|
||
| ## Unresolved Questions and Bikeshedding | ||
|
|
||
| - How will credential providers be registered? (e.g. bundled with npm-cli, well-known globally installed node_module, well-known and dynamically installed by npm-cli, etc.). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it seems pretty important to be configurable both per-project and also globally; additionally, pretty critical that anybody be able to be a credential provider with no action taken by npm.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll update this to allow project-level and user-level configuration. Noted that credential providers should be able to be provided without any action taken by NPM.
| ## Unresolved Questions and Bikeshedding | ||
|
|
||
| - How will credential providers be registered? (e.g. bundled with npm-cli, well-known globally installed node_module, well-known and dynamically installed by npm-cli, etc.). | ||
| - Can custom credential providers be registered via a new setting in `.npmrc`? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
most all npm config can be set up with env vars and npmrc, so this seems like a "yes"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.npmrc will have a configuration to register credential providers then. This is something that pnpm has implemented: https://pnpm.io/settings#urltokenhelper
However, pnpm's implementation has limitations that are not desirable:
The configuration for the path to the helper must be an absolute path, with no arguments. In order to be secure, it is only permitted to set this value in the user .npmrc. Otherwise a project could place a value in a project's local .npmrc and run arbitrary executables.
Setting a token helper for the default registry:
tokenHelper=/home/ivan/token-generator
Setting a token helper for the specified registry:
//registry.corp.com:tokenHelper=/home/ivan/token-generator
The registry context isn't passed to the Credential Provider in pnpm's case, which is a challenge for generating credentials scoped to the registry. Ideally, the registry uri would be sent to the provider for context. Otherwise, a wrapper script would need to be created to supply the context, which is not a great solution.
| - Can custom credential providers be registered via a new setting in `.npmrc`? | ||
| - What is the expected output format (e.g., JSON, plain text)? | ||
| - How should errors be surfaced to the user? | ||
| - Should NPM CLI support caching of tokens returned by the provider or is this the provider's responsibility? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
presumably, like cookies, the provider should be able to provide a lifetime that the client obeys?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then the NPM cli will expect the credential provider to supply an expiresAt timestamp and npm cli will provide in-memory caching for the duration of the current NPM operation (it will call the provider again if the current timestamp is near the expiration)
|
It seems to me that before any of the items under "bikeshedding" are discussed we need to figure out
Without this, this RFC is just a very high level wish. What, exactly, is a "credential provider" here? Is it oauth? Is it just another layer of auth? Is it, like OIDC, another trust layer? Where is the security/trust model in this approach? One of the concerns folks have had w/ OIDC is that it simply moves trust to another domain. How is this different? Put another way how do I authenticate with a credential provider? Is that authentication model secure? Are we just moving the bubble under tape here or does this provide a real improvement against current models? If it helps we can identify specifically the problems this solves, be honest about the ones it doesn't solve, and go from there. From what I see this is intended to solve the "Bearer token authentication has to have the bearer token stored somewhere" problem. If the bearer token can never live anywhere outside of runtime memory in npm itself then why not implement a "prompt for login/2fa to get a bearer token directly from the npm registry" workflow? Can this be solved by having an npm cli mode where it is never logged in, and makes you log in on invocation? As far as what this does not solve, this will likely have the same shortcomings as something like OIDC. Unless this credential provider is exclusively an npm registry feature it is equivalent to outsourcing the trust model to a third party. Finally, is there prior art in the Node.js ecosystem? If this does not exist yet it represents quite a significant development and maintenance burden. Who will own this? |
A Credential Provider is an interface to acquire a token for authenticating with an NPM registry. It is not a new auth protocol, likely the Credential Provider uses OAuth/OIDC under the hood or whatever the registry requires. The Credential Provider is an integration mechanism between the NPM CLI and an authentication helper.
There are some new trust boundaries in this approach:
NPM executes the provider for a registry uri (e.g.
Security depends on the Credential Provider's implementation and token handling. NPM cli should restrict the execution of arbitrary executables without consent from the user. The big improvement is removing plaintext token persistence from developer machines, encouraging shorter token lifetimes, and encouraging secure enterprise-grade auth flows implemented by credential providers. For npmjs.com itself, perhaps NPM would ship its own credential provider that would behave similarly to
We are solving:
We are not solving:
I'm not saying that a token can never live anywhere outside of runtime memory for NPM. But it cannot live in plaintext or in environment variables. Token storage should be ephemeral or locked in encrypted OS-level storage like Windows Credential Manager or MacOS Keychain Access. I'm proposing that from NPM's should treat tokens ephemerally and the choice between ephemeral or secure storage is up to the Credential Provider.
The prompt login would solve the problem for npmjs.com registry, but not for 3rd party registries like:
I think this is accurate. This is essentially a feature for NPM registries. For NPM-owned registries (npmjs and github packages) NPM can ship/maintain the provider. For external registries, the enterprise should ship their own provider. And NPM should enforce guardrails to avoid using 3rd party providers for 1st party registries, require used consent for project-level and user-level configs, and potentially an allowlist for certain providers.
|
This RFC proposes the implementation of a credential provider plugin protocol for the NPM CLI to enable secure, dynamic retrieval of authentication tokens. The protocol will allow external credential providers to supply short-lived tokens at runtime, eliminating the need to store secrets in plaintext within
.npmrcfiles or environment variables.