npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

jira-oauth-client

v0.1.2

Published

Jira OAuth 2.0 (3LO) client — authenticate, refresh, and store tokens for use in VS Code extensions, scripts, and CLI tools

Readme

🔑 jira-oauth-client

Jira OAuth 2.0 for humans — one call to authenticate, works everywhere.

Opens the browser, spins a local callback server, exchanges the code for tokens, and saves them to disk — so you never have to implement the 3LO flow yourself.

npm version CI license node types

InstallQuick StartPrerequisitesAPIVS CodeFAQ


✨ Features

  • 🚀 Full 3LO flow in one callauthenticate() handles the entire browser-based OAuth loop
  • 🔄 Token refresh — keep tokens alive without re-authenticating
  • 💾 Automatic persistence — tokens saved to ~/.jira-oauth/tokens.json with chmod 0600
  • 📦 Dual CJS + ESM — works in VS Code extensions, bundlers, and plain Node.js scripts
  • 🔒 CSRF protection — UUID state token validated on every callback
  • ⏱️ Auto-timeout — authentication flow cancels after 5 minutes
  • 🌍 Env-var friendly — falls back to JIRA_CLIENT_ID / JIRA_CLIENT_SECRET when no options passed
  • 🔷 Fully typed — complete TypeScript definitions included

📦 Install

npm install jira-oauth-client
# pnpm add jira-oauth-client
# yarn add jira-oauth-client

[!NOTE] Requires Node.js ≥ 20.


🚀 Quick Start

import { JiraOAuthClient } from 'jira-oauth-client'

const client = new JiraOAuthClient()

// Opens browser → user logs in → tokens returned and saved automatically
const tokens = await client.authenticate()

// Use tokens.accessToken as a Bearer token with Jira REST API.
// Do not log or expose OAuth tokens.

Set credentials from your shell or secrets manager before running:

# JIRA_CLIENT_ID
# JIRA_CLIENT_SECRET

📋 Prerequisites

  1. Go to Atlassian Developer Console and create an OAuth 2.0 (3LO) integration

  2. Add callback URL: http://localhost:30129/callback

  3. Under Permissions, configure each API:

    | API | Scopes | |-----|--------| | Jira API | read:jira-user, read:jira-work, manage:jira-project, write:jira-work, read:board-scope:jira-software, read:sprint:jira-software, write:sprint:jira-software | | User Identity API | read:me | | Jira API (offline) | offline_access |

  4. Copy your Client ID and Client Secret


🧑‍💻 API Reference

new JiraOAuthClient(options?)

const client = new JiraOAuthClient({
  clientId?: string,        // fallback: process.env.JIRA_CLIENT_ID
  clientSecret?: string,    // fallback: process.env.JIRA_CLIENT_SECRET
  port?: number,            // default: 30129
  tokenStorePath?: string,  // default: ~/.jira-oauth/tokens.json
  scopes?: string[],        // default: full Jira scope set
  openBrowser?: (authorizationUrl: string) => unknown | Promise<unknown>
  urls?: {
    authUrl?: string,
    tokenUrl?: string,
    resourcesUrl?: string,
  }
})

Throws JiraOAuthConfigError immediately if clientId or clientSecret cannot be resolved.

Methods

| Method | Returns | Description | |--------|---------|-------------| | authenticate() | Promise<JiraTokens> | Run full OAuth flow — opens browser, waits for callback, saves and returns tokens | | refresh(refreshToken) | Promise<JiraTokens> | Exchange a refresh token for a new access token | | getStoredTokens() | JiraTokens \| null | Read persisted tokens from disk synchronously | | saveTokens(tokens) | Promise<void> | Write tokens to disk with chmod 0600 |

JiraTokens

interface JiraTokens {
  accessToken: string    // Bearer token for Jira REST API
  refreshToken: string   // use with refresh() to get new access tokens
  expiresIn: number      // seconds until access token expires
  scope: string          // space-separated granted scopes
  tokenType: string      // always "Bearer"
  cloudId: string        // Atlassian cloud instance ID (needed for REST calls)
  cloudName: string      // workspace display name
  issuedAt: number       // Date.now() when tokens were saved
}

Error types

| Class | Code | When thrown | |-------|------|-------------| | JiraOAuthConfigError | CONFIG_ERROR | Missing clientId / clientSecret, or refresh() called before first authenticate | | JiraOAuthTimeoutError | AUTH_TIMEOUT | Browser flow not completed within 5 minutes | | JiraOAuthCallbackError | CALLBACK_ERROR | Atlassian returned an error, state mismatch (CSRF), or token exchange failed |

All error classes extend JiraOAuthError, so you can catch the base class:

import { JiraOAuthError, JiraOAuthTimeoutError } from 'jira-oauth-client'

try {
  const tokens = await client.authenticate()
} catch (error) {
  if (error instanceof JiraOAuthTimeoutError) {
    // user didn't complete login in time
  } else if (error instanceof JiraOAuthError) {
    console.error(error.code, error.message)
  }
}

🔄 Token Lifecycle

// 1. First-time auth — opens browser
const tokens = await client.authenticate()

// 2. Later — check stored tokens before re-authenticating
const stored = client.getStoredTokens()
if (stored) {
  const ageMs = Date.now() - stored.issuedAt
  const expiredMs = stored.expiresIn * 1000

  if (ageMs < expiredMs) {
    // still valid — use stored.accessToken directly
  } else {
    // expired — refresh silently
    const fresh = await client.refresh(stored.refreshToken)
  }
}

// 3. Save externally-obtained tokens
await client.saveTokens(tokens)

🛠️ VS Code Extension

The package ships both CJS and ESM. VS Code extensions run in a CJS host and automatically resolve dist/index.cjs:

// extension.ts
import { JiraOAuthClient, JiraOAuthTimeoutError } from 'jira-oauth-client'

export async function activate(context: vscode.ExtensionContext): Promise<void> {
  const client = new JiraOAuthClient({
    clientId: context.globalState.get<string>('jiraClientId'),
    clientSecret: context.globalState.get<string>('jiraClientSecret'),
  })

  context.subscriptions.push(
    vscode.commands.registerCommand('myext.login', async () => {
      try {
        const tokens = await client.authenticate()
        await context.secrets.store('jiraAccessToken', tokens.accessToken)
        void vscode.window.showInformationMessage('Jira connected ✓')
      } catch (error) {
        if (error instanceof JiraOAuthTimeoutError) {
          void vscode.window.showWarningMessage('Login timed out — please try again')
        }
      }
    }),
  )
}

🔒 Security

[!WARNING] Tokens are sensitive OAuth credentials. Follow these rules:

  • Token file ~/.jira-oauth/tokens.json is written with chmod 0600 — owner read/write only
  • Never log, expose, or commit accessToken, refreshToken, or clientSecret
  • Add ~/.jira-oauth/ to your global .gitignore
  • For distributed integrations, use one Atlassian 3LO app that you own and distribute; do not ask each customer to create and share their own app credentials.

❓ FAQ

The redirect URI must be registered in your Atlassian OAuth app. Using a fixed port means you only register it once and never change it. Port 30129 was chosen to avoid conflicts with common dev ports (3000, 8080, etc.).

authenticate() will throw a Node.js EADDRINUSE error. Stop whatever is using the port, or pass a different port option (and update your Atlassian app's callback URL to match).

No — the 3LO flow requires a human to log in. For CI use cases, generate tokens locally first with authenticate(), persist them, and then use refresh() to keep them alive in your pipeline.

The first resource returned by /oauth/token/accessible-resources is used. This is typically the primary workspace. If you need a specific one, pass a custom scopes array or handle workspace selection manually using the returned cloudId.

No. It contains live OAuth tokens. It is written with chmod 0600 and should be treated like a private key. Add it to your .gitignore.


🛠️ Development

git clone https://github.com/dongitran/jira-oauth-client.git
cd jira-oauth-client
pnpm install

pnpm build        # tsup — produces dist/index.cjs + dist/index.mjs
pnpm typecheck    # tsc --noEmit
pnpm lint         # eslint src tests
pnpm test:unit    # vitest run --coverage
pnpm test:e2e:mock  # playwright e2e against a local mock Atlassian server
pnpm check        # lint + typecheck + test:unit

Made with ❤️ by dongtran

License · MIT