Skip to content

Add Node.js Quickstart - #32

Merged
brionmario merged 1 commit into
thunder-id:mainfrom
brionmario:flexible-vendor-config
Jul 31, 2026
Merged

Add Node.js Quickstart#32
brionmario merged 1 commit into
thunder-id:mainfrom
brionmario:flexible-vendor-config

Conversation

@brionmario

@brionmario brionmario commented Jul 16, 2026

Copy link
Copy Markdown
Member

Purpose

  • Added AuthService and InventoryService for machine-to-machine authentication.
  • Implemented client_credentials flow in the Node.js quickstart sample.
  • Updated environment variable handling in quickstart samples.
  • Enhanced UI for configuration prompts and inventory display.
  • Added .env.example for environment variable setup in Node.js quickstart.
  • Updated package.json scripts to use --env-file-if-exists for better handling of environment variables.

Approach

N/A

Related Issues

Related PRs

  • N/A

Checklist

  • Followed the contribution guidelines.
  • Manual test round performed and verified.
  • Documentation provided. (Add links if there are any)
  • Tests provided. (Add links if there are any)
    • Unit Tests
    • Integration Tests
  • Breaking changes. (Fill if applicable)
    • Breaking changes section filled.
    • breaking change label added.

Security checks

  • Followed secure coding standards in WSO2 Secure Coding Guidelines
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets.

Summary by CodeRabbit

  • New Features

    • Added OAuth 2.0 client_credentials authentication support with automatic access-token caching and refresh.
    • Added configuration for selecting the authentication grant type.
  • Documentation

    • Added a Node.js service-to-service quickstart covering credentials, token handling, and inventory retrieval.
    • Added environment configuration guidance and sample output.
  • Style

    • Improved Express quickstart configuration screens with updated branding, layout, and setup guidance.
    • Enhanced terminal output for authentication status, inventory results, and errors.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The JavaScript SDK adds OAuth 2.0 client_credentials token acquisition, caching, refresh margins, and error handling. A Node quickstart demonstrates authenticated inventory access. The Express quickstart updates optional environment loading and configuration guidance.

Changes

Client credentials flow

Layer / File(s) Summary
SDK configuration and token lifecycle
packages/javascript/src/models/config.ts, packages/javascript/src/constants/TokenConstants.ts, packages/javascript/src/ThunderIDJavaScriptClient.ts
The SDK accepts authorization_code and client_credentials grant types. It reuses valid cached tokens and refreshes tokens within a 30-second margin.
Client-credentials token request
packages/javascript/src/ThunderIDJavaScriptClient.ts
The SDK builds authenticated token requests, validates endpoints, handles network and HTTP errors, persists response metadata, and returns normalized token responses.
Node service-to-service quickstart
samples/node/quickstart/*
The quickstart configures client credentials, retrieves and decodes an access token, performs inventory lookups, handles per-SKU errors, and renders terminal output. Its README and environment template document setup and execution.

Express quickstart configuration update

Layer / File(s) Summary
Express configuration guidance
samples/express/quickstart/index.mjs, samples/express/quickstart/package.json, samples/express/quickstart/public/styles.css
The quickstart uses optional .env loading. The configuration-needed page and its styles now use the updated hero layout, branding, hints, and restart command.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Quickstart
  participant AuthService
  participant ThunderIDNodeClient
  participant TokenEndpoint
  participant InventoryService
  Quickstart->>AuthService: initialize client credentials
  AuthService->>ThunderIDNodeClient: request cached access token
  ThunderIDNodeClient->>TokenEndpoint: request client_credentials token when cache is stale
  TokenEndpoint-->>ThunderIDNodeClient: return access token
  ThunderIDNodeClient-->>AuthService: return access token
  Quickstart->>InventoryService: request SKU stock
  InventoryService->>AuthService: obtain access token
  InventoryService-->>Quickstart: return inventory result or error
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary change: adding a Node.js quickstart.
Description check ✅ Passed The description covers the purpose, related issue, implementation scope, and template sections, but checklist items remain unchecked.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@brionmario brionmario changed the title Introduce client_credentials grant support in ThunderID Node.js SDK Add Node.js Quickstart Jul 30, 2026
@brionmario
brionmario force-pushed the flexible-vendor-config branch from b59aa83 to 543f85d Compare July 31, 2026 05:56

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
samples/node/quickstart/.env.example (1)

1-8: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Comment out NODE_TLS_REJECT_UNAUTHORIZED=0 by default.

The variable is active by default when a developer copies this file to .env, even though the preceding comment warns against it. Make it opt-in by commenting it out, so a developer must consciously uncomment it after reading the warning.

🛡️ Proposed fix to make the TLS bypass opt-in
 # DANGER: Disables ALL TLS verification. Only for local development with self-signed certs. NEVER use in production.
-NODE_TLS_REJECT_UNAUTHORIZED=0
+#NODE_TLS_REJECT_UNAUTHORIZED=0
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@samples/node/quickstart/.env.example` around lines 1 - 8, Comment out the
NODE_TLS_REJECT_UNAUTHORIZED=0 entry in the environment template while
preserving its warning, making TLS verification bypass opt-in when developers
copy the file to .env.
🧹 Nitpick comments (2)
packages/javascript/src/ThunderIDJavaScriptClient.ts (1)

797-897: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Fix the any-typed catch and align the fetch config with sibling methods.

Static analysis flags line 865 (catch (error: any), @typescript-eslint/no-explicit-any) and line 869 (unsafe any argument passed where a string is expected, @typescript-eslint/no-unsafe-argument). Type the caught error and coerce it before use, as the sibling exception messages expect a string.

Separately, the fetch call at lines 860-864 omits credentials, unlike requestAccessToken, exchangeToken, and refreshAccessToken, which all set credentials: configData.sendCookiesInRequests ? 'include' : 'same-origin'. If this omission is intentional for machine-to-machine requests, consider a short comment explaining the divergence; otherwise, align it with the other token-request methods for consistency.

🔧 Proposed fix for the `any`-typed catch block
-    } catch (error: any) {
+    } catch (error: unknown) {
       throw new ThunderIDAuthException(
         'JS-AUTH_CORE-RCCT-NE02',
         'Requesting client credentials token failed.',
-        error ?? 'The request to get the client credentials access token failed.',
+        error instanceof Error ? error.message : 'The request to get the client credentials access token failed.',
       );
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/javascript/src/ThunderIDJavaScriptClient.ts` around lines 797 - 897,
Update requestClientCredentialsToken to use an unknown-typed catch variable and
convert the caught value to a string before passing it to
ThunderIDAuthException. Also align its fetch configuration with
requestAccessToken, exchangeToken, and refreshAccessToken by setting credentials
from configData.sendCookiesInRequests, or document the intentional
machine-to-machine divergence with a concise comment.

Source: Linters/SAST tools

samples/node/quickstart/package.json (1)

7-9: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Require Node.js 22.9+ for the quickstart scripts.

npm start and npm run dev use --env-file-if-exists, but samples/node/quickstart/README.md documents Node.js 18+. Either require Node.js 22.9+ in samples/node/quickstart/README.md and package.json, or replace the flag with a Node 18-compatible mechanism for optional env loading.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@samples/node/quickstart/package.json` around lines 7 - 9, Align the
quickstart’s documented and declared Node.js requirement with the
--env-file-if-exists usage in the start and dev scripts: update the README.md
prerequisite and package.json engines metadata to require Node.js 22.9 or newer.
Keep both scripts unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/javascript/src/ThunderIDJavaScriptClient.ts`:
- Around line 186-214: Add an in-flight token-request promise around
getAccessToken’s client_credentials path so concurrent callers share one
requestClientCredentialsToken invocation when the cached token is missing or
stale. Reuse the shared promise’s result for all callers and clear it after
completion, including rejection, so later calls can retry; preserve existing
cached-token and non-client_credentials behavior.

In `@samples/express/quickstart/package.json`:
- Around line 7-8: Update the quickstart runtime compatibility by either
replacing --env-file-if-exists in the start and dev scripts with an
environment-file approach supported by the documented Node.js 18+ range, or
raise the README’s required Node.js version to 22.9+ so it matches both scripts.

---

Outside diff comments:
In `@samples/node/quickstart/.env.example`:
- Around line 1-8: Comment out the NODE_TLS_REJECT_UNAUTHORIZED=0 entry in the
environment template while preserving its warning, making TLS verification
bypass opt-in when developers copy the file to .env.

---

Nitpick comments:
In `@packages/javascript/src/ThunderIDJavaScriptClient.ts`:
- Around line 797-897: Update requestClientCredentialsToken to use an
unknown-typed catch variable and convert the caught value to a string before
passing it to ThunderIDAuthException. Also align its fetch configuration with
requestAccessToken, exchangeToken, and refreshAccessToken by setting credentials
from configData.sendCookiesInRequests, or document the intentional
machine-to-machine divergence with a concise comment.

In `@samples/node/quickstart/package.json`:
- Around line 7-9: Align the quickstart’s documented and declared Node.js
requirement with the --env-file-if-exists usage in the start and dev scripts:
update the README.md prerequisite and package.json engines metadata to require
Node.js 22.9 or newer. Keep both scripts unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d15428fd-ec74-42f6-a234-867d812935c9

📥 Commits

Reviewing files that changed from the base of the PR and between ed5d367 and 543f85d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (13)
  • packages/javascript/src/ThunderIDJavaScriptClient.ts
  • packages/javascript/src/constants/TokenConstants.ts
  • packages/javascript/src/models/config.ts
  • samples/express/quickstart/index.mjs
  • samples/express/quickstart/package.json
  • samples/express/quickstart/public/styles.css
  • samples/node/quickstart/.env.example
  • samples/node/quickstart/README.md
  • samples/node/quickstart/index.mjs
  • samples/node/quickstart/lib/AuthService.mjs
  • samples/node/quickstart/lib/InventoryService.mjs
  • samples/node/quickstart/lib/ui.mjs
  • samples/node/quickstart/package.json

Comment on lines 186 to +214
public async getAccessToken(sessionId?: string): Promise<string> {
const configData = await this.configProvider();

if (configData.grantType === 'client_credentials') {
const sessionData = await this.storageManager.getSessionData(sessionId);

if (sessionData?.access_token && this.isCachedTokenStillValid(sessionData)) {
return sessionData.access_token;
}

const tokenResponse = await this.requestClientCredentialsToken(sessionId);

return tokenResponse.accessToken;
}

return (await this.storageManager.getSessionData(sessionId))?.access_token;
}

private isCachedTokenStillValid(sessionData: SessionData): boolean {
if (!sessionData.created_at || !sessionData.expires_in) {
return false;
}

const expiresInMs = parseInt(sessionData.expires_in, 10) * 1000;

return (
sessionData.created_at + expiresInMs - TokenConstants.Lifecycle.CLIENT_CREDENTIALS_REFRESH_MARGIN_MS > Date.now()
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add request coalescing for concurrent client_credentials token fetches.

When the cached token is missing or stale, every concurrent caller of getAccessToken() independently calls requestClientCredentialsToken. Under concurrent load (the typical case for a machine-to-machine client backing a busy service), this sends multiple simultaneous requests to the token endpoint for the same client. This can trigger rate limiting on the authorization server or add avoidable load during a stampede.

Cache the in-flight request promise so concurrent callers await the same request instead of issuing separate ones.

🔧 Proposed fix to coalesce concurrent client-credentials requests
+  private clientCredentialsTokenPromise?: Promise<TokenResponse>;
+
   public async getAccessToken(sessionId?: string): Promise<string> {
     const configData = await this.configProvider();

     if (configData.grantType === 'client_credentials') {
       const sessionData = await this.storageManager.getSessionData(sessionId);

       if (sessionData?.access_token && this.isCachedTokenStillValid(sessionData)) {
         return sessionData.access_token;
       }

-      const tokenResponse = await this.requestClientCredentialsToken(sessionId);
+      if (!this.clientCredentialsTokenPromise) {
+        this.clientCredentialsTokenPromise = this.requestClientCredentialsToken(sessionId).finally(() => {
+          this.clientCredentialsTokenPromise = undefined;
+        });
+      }
+
+      const tokenResponse = await this.clientCredentialsTokenPromise;

       return tokenResponse.accessToken;
     }

     return (await this.storageManager.getSessionData(sessionId))?.access_token;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public async getAccessToken(sessionId?: string): Promise<string> {
const configData = await this.configProvider();
if (configData.grantType === 'client_credentials') {
const sessionData = await this.storageManager.getSessionData(sessionId);
if (sessionData?.access_token && this.isCachedTokenStillValid(sessionData)) {
return sessionData.access_token;
}
const tokenResponse = await this.requestClientCredentialsToken(sessionId);
return tokenResponse.accessToken;
}
return (await this.storageManager.getSessionData(sessionId))?.access_token;
}
private isCachedTokenStillValid(sessionData: SessionData): boolean {
if (!sessionData.created_at || !sessionData.expires_in) {
return false;
}
const expiresInMs = parseInt(sessionData.expires_in, 10) * 1000;
return (
sessionData.created_at + expiresInMs - TokenConstants.Lifecycle.CLIENT_CREDENTIALS_REFRESH_MARGIN_MS > Date.now()
);
}
private clientCredentialsTokenPromise?: Promise<TokenResponse>;
public async getAccessToken(sessionId?: string): Promise<string> {
const configData = await this.configProvider();
if (configData.grantType === 'client_credentials') {
const sessionData = await this.storageManager.getSessionData(sessionId);
if (sessionData?.access_token && this.isCachedTokenStillValid(sessionData)) {
return sessionData.access_token;
}
if (!this.clientCredentialsTokenPromise) {
this.clientCredentialsTokenPromise = this.requestClientCredentialsToken(sessionId).finally(() => {
this.clientCredentialsTokenPromise = undefined;
});
}
const tokenResponse = await this.clientCredentialsTokenPromise;
return tokenResponse.accessToken;
}
return (await this.storageManager.getSessionData(sessionId))?.access_token;
}
private isCachedTokenStillValid(sessionData: SessionData): boolean {
if (!sessionData.created_at || !sessionData.expires_in) {
return false;
}
const expiresInMs = parseInt(sessionData.expires_in, 10) * 1000;
return (
sessionData.created_at + expiresInMs - TokenConstants.Lifecycle.CLIENT_CREDENTIALS_REFRESH_MARGIN_MS > Date.now()
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/javascript/src/ThunderIDJavaScriptClient.ts` around lines 186 - 214,
Add an in-flight token-request promise around getAccessToken’s
client_credentials path so concurrent callers share one
requestClientCredentialsToken invocation when the cached token is missing or
stale. Reuse the shared promise’s result for all callers and clear it after
completion, including rejection, so later calls can retry; preserve existing
cached-token and non-client_credentials behavior.

Comment on lines +7 to +8
"start": "node --env-file-if-exists=.env index.mjs",
"dev": "node --env-file-if-exists=.env --watch index.mjs"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n --glob 'package.json' --glob '*.md' \
  '"engines"|Node\.js|nodejs|--env-file-if-exists|22\.9' .

Repository: thunder-id/javascript-sdks

Length of output: 1997


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Express quickstart package:\n'
cat -n samples/express/quickstart/package.json

printf '\nExpress quickstart README runtime line:\n'
sed -n '1,45p' samples/express/quickstart/README.md | cat -n

printf '\nAll express quickstart node scripts in package.json:\n'
python3 - <<'PY'
import json
from pathlib import Path
p=Path('samples/express/quickstart/package.json')
data=json.loads(p.read_text())
for k,v in data.get('scripts',{}).items():
    print(k, v)
PY

Repository: thunder-id/javascript-sdks

Length of output: 3358


🌐 Web query:

Node.js CLI change log --env-file-if-exists added version

💡 Result:

The Node.js CLI flag --env-file-if-exists was added in version v22.9.0 [1][2][3]. This flag functions similarly to --env-file by loading environment variables from a specified file into process.env, but it differs in that it does not throw an error if the specified file does not exist [1][4]. The --env-file-if-exists flag was originally introduced as an experimental feature and was later marked as stable in Node.js versions v24.10.0 and v22.21.0 [1][4][5].

Citations:


Require Node.js 22.9+ or make the env-file loader compatible.

The README documents Node.js 18+, but --env-file-if-exists is only available from Node.js v22.9.0. Users on 18–21 will get an invalid option before index.mjs starts. Raise the supported runtime to 22.9+ or use a compatible optional-environment-file loader.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@samples/express/quickstart/package.json` around lines 7 - 8, Update the
quickstart runtime compatibility by either replacing --env-file-if-exists in the
start and dev scripts with an environment-file approach supported by the
documented Node.js 18+ range, or raise the README’s required Node.js version to
22.9+ so it matches both scripts.

@brionmario
brionmario merged commit 0f22744 into thunder-id:main Jul 31, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants