You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

开发全局NPM CLI包:如何保存用户登录凭证?

How to Store Login Credentials for Your Authenticated NPM CLI Tool

Great question! Building a CLI that requires authenticated actions (just like npm publish) is a common pattern, and integrating it with your Firebase Auth-backed Express backend is totally manageable. Let’s walk through the steps to handle credential storage securely and smoothly.

1. First: Implement the Login Handshake (CLI + Backend)

Before worrying about storage, you need to get the login flow working between your CLI and backend:

  • When a user runs myPackage login, use a library like inquirer to prompt them for their pre-created email and password.
  • Send these credentials via a POST request to your Express login endpoint. Your backend will use Firebase Auth’s tools to verify the credentials, then return a Firebase ID Token (or a custom JWT if you prefer) to the CLI.

Example CLI login code snippet:

const inquirer = require('inquirer');
const axios = require('axios');

async function login() {
  const { email, password } = await inquirer.prompt([
    { type: 'input', name: 'email', message: 'Enter your email:' },
    { type: 'password', name: 'password', message: 'Enter your password:' }
  ]);

  try {
    const response = await axios.post('https://your-backend.com/api/login', { email, password });
    const { idToken } = response.data;
    // We'll store this idToken next
    await saveCredentials(idToken);
    console.log('Login successful!');
  } catch (err) {
    console.error('Login failed:', err.response?.data?.message || err.message);
  }
}

Simplified Express backend login endpoint:

const admin = require('firebase-admin');

app.post('/api/login', async (req, res) => {
  const { email, password } = req.body;
  try {
    // Verify credentials via Firebase Auth (note: Admin SDK doesn't directly check passwords, so you can use Firebase's REST sign-in endpoint here)
    const authResponse = await axios.post('https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=YOUR_FIREBASE_API_KEY', {
      email,
      password,
      returnSecureToken: true
    });
    const idToken = authResponse.data.idToken;
    res.json({ idToken });
  } catch (err) {
    res.status(401).json({ message: 'Invalid credentials' });
  }
});

2. Securely Store the Credentials

Never store tokens in plaintext files! The safest approach is to leverage the system’s native secure storage:

  • Use the keytar library: It integrates with macOS Keychain, Windows Credential Manager, and Linux’s libsecret. This is exactly what npm uses for storing publish credentials.
  • Alternative (simpler but less secure): configstore with encryption, but keytar is the gold standard for sensitive data.

Example code to save/retrieve tokens with keytar:

const keytar = require('keytar');

// Use a unique service name for your CLI to avoid conflicts
const SERVICE_NAME = 'myPackage-cli';
const ACCOUNT_NAME = 'auth-token';

async function saveCredentials(idToken) {
  await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, idToken);
}

async function getCredentials() {
  return keytar.getPassword(SERVICE_NAME, ACCOUNT_NAME);
}

async function logout() {
  await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
  console.log('Logged out successfully!');
}

3. Validate Credentials for Authenticated Commands

For commands like myPackage publish [args], you’ll need to:

  1. Fetch the stored token using getCredentials().
  2. Send the token to your backend (via an Authorization: Bearer <token> header) to verify its validity.
  3. If the token is valid, proceed with the publish action; if invalid (or missing), prompt the user to log in again.

Example publish command logic:

async function publish(args) {
  const idToken = await getCredentials();
  if (!idToken) {
    console.error('You need to log in first. Run `myPackage login`');
    process.exit(1);
  }

  try {
    // Verify token with backend first
    await axios.post('https://your-backend.com/api/verify-token', {}, {
      headers: { Authorization: `Bearer ${idToken}` }
    });

    // Proceed with your publish logic
    console.log('Publishing your content...', args);
    // Add your publish implementation here
  } catch (err) {
    if (err.response?.status === 401) {
      console.error('Session expired. Please log in again.');
      await logout(); // Remove invalid token from storage
      process.exit(1);
    }
    console.error('Publish failed:', err.message);
  }
}

Bonus Tips

  • Handle token expiration: Firebase ID Tokens expire after 1 hour. You can either refresh them using a refresh token (returned alongside the ID token from your login endpoint) or prompt users to log in again when a 401 error is received.
  • Add a logout command: As shown above, let users explicitly remove their credentials from the system keychain.
  • Test cross-platform: keytar has prebuilt binaries for most OSes, but Linux users may need to install system dependencies like libsecret-1-dev.

内容的提问来源于stack exchange,提问作者Guy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 06:43:31