Skip to content

feat(auth0): Add My Organization API skill#91

Open
brittany-okta wants to merge 1 commit into
auth0:mainfrom
brittany-okta:feat/my-organizaton-api-skill
Open

feat(auth0): Add My Organization API skill#91
brittany-okta wants to merge 1 commit into
auth0:mainfrom
brittany-okta:feat/my-organizaton-api-skill

Conversation

@brittany-okta
Copy link
Copy Markdown

@brittany-okta brittany-okta commented May 14, 2026

Summary

  • auth0-my-organization-api — A single-skill plugin containing using-my-organization-api, which guides agents through building organization-scoped admin portals for B2B SaaS using Auth0's My Organization API. Covers three implementation paths:
    • bootstrapping from the SaaStart reference app
    • porting existing Management API calls to the My Org API
    • adding pre-built UI components from @auth0/universal-components-react.
      Also covers backend validation, organization context middleware, and permission enforcement.

The skill ships with five supporting scripts under scripts/:

  • detect-stack.mjs — reads framework, package manager, Tailwind config, and existing Auth0 env vars in one pass; output feeds all subsequent steps so the agent never greps package.json manually
  • validate-auth0.mjs — checks CLI auth state against the tenant before bootstrap
  • bootstrap.mjs — idempotently configures all required Auth0 resources (resource server, application, callback URLs, client grants, database connection, admin role, demo org) and outputs env vars ready to write to .env.local
  • extract-theme.mjs — reads the project's CSS variables and generates an Auth0ComponentProvider theme override block
  • verify-setup.mjs — pre-flight checker that runs before npm run dev; any pass: false
    check is auto-fixed by the agent

Notable details

  1. No-database principle enforced at the skill level. The SKILL.md contains an explicit agent instruction: Auth0 is the source of organizations and users. Agents must never create a database (Prisma, Supabase, etc.) to store or sync org/user data unless the user explicitly asks. This was added after repeated cases where agents introduced databases unnecessarily.
  2. Path selection is agent-driven, not user-driven. A Choose Your Path decision tree in SKILL.md instructs the agent to detect project state and follow the tree silently. The agent only asks the user when detection is genuinely ambiguous (e.g. multiple orgs exist). For an empty codebase with no specified stack, the agent defaults to Next.js + SaaStart + Universal Components and informs the user rather than asking.
  3. Tenant detection is fully automatic. The agent checks env files → Auth0 CLI → CLI config → mcp__auth0_mcp__auth0_get_tenant_name() before ever asking the user for their domain. The MCP tool call is always available in the environment and returns the tenant domain directly.
  4. PATH forwarding for Node.js scripts. Scripts invoke the auth0 CLI binary via execFileSync, which does not inherit the full shell PATH. The skill instructs agents to pass PATH="$PATH" explicitly when calling the scripts, and to verify auth0 is in PATH via which auth0 before any script run.
  5. Next.js 15.5.x compatibility. A new workUnitAsyncStorage invariant error appears on 15.5.x when auth0.getSession() is called from a statically-rendered server component. The skill adds export const dynamic = 'force-dynamic' to every auth-gated page as a blanket fix, plus a peer dep check to catch version mismatches before the dev server starts.
  6. Pre-flight checks are active agent steps, not passive documentation. The verify-setup.mjs script runs before npm run dev and the agent applies all fixes automatically. This prevents a class of "it starts but immediately crashes" errors caused by missing env vars or misconfigured callback URLs.
  7. EAGAIN is documented as an OS-level limit. When the file descriptor error appears (common during npm install in resource-constrained environments), the agent is instructed to explain the cause and tell the user to retry in their terminal — not retry the command from inside the agent environment.
  8. Common Mistakes uses Cause → Fix → Verify format. 14 documented errors, each with a root cause, numbered fix steps, and a checkpoint confirming success. Errors include: wrong audience URL, missing AUTH0_SECRET, "client not found", "not authorized after login", callback URL mismatch, and the workUnitAsyncStorage invariant error.

Test Plan

No automated regression harness in this PR. The skill has been manually exercised against the SaaStart path (empty repo → running dashboard), the porting path (existing Management API app), and the UI components path. A test harness is tracked as follow-up work.

Notes for Reviewers

  • The scripts/ folder was adapted from auth0-universal-components-web — the utility helpers under scripts/utils/ (auth0-api.mjs, clients.mjs, discovery.mjs, resources.mjs) are shared patterns; happy to consolidate these into a shared location if the repo has a convention for that.
  • The skill description is intentionally verbose — it's the selection signal for the LLM. Open to trimming if it crosses a length threshold.
  • Member management and security policies are documented as "not yet supported by My Organization API" with Management API fallback patterns included inline. Happy to move these to a separate reference file if the main skill file feels cluttered.

By submitting a PR to this repository, you agree to the terms within the Auth0 Code of Conduct. Please see the contributing guidelines for how to create and submit a high-quality PR for this repo.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 14, 2026

📝 Walkthrough

Walkthrough

This PR introduces a complete Auth0 skill plugin for building B2B delegated admin portals using the My Organization API. It includes comprehensive documentation (setup guide, API reference, backend patterns, advanced features), working code examples across five frameworks, and CLI automation tools for tenant configuration, project introspection, and setup verification.

Changes

Auth0 My Organization API Skill Plugin

Layer / File(s) Summary
Plugin manifest and overview
.claude-plugin/plugin.json, README.md
Plugin registration metadata and user-facing README introducing the skill, its purpose, installation, and basic usage.
Main skill walkthrough and implementation guide
skills/using-my-organization-api/SKILL.md
Complete step-by-step skill guide covering stack detection, Auth0 CLI validation, resource bootstrapping, environment variable configuration, UI implementation choices (SaaStart, porting, Universal Components), backend validation requirements, common troubleshooting, and quick reference information.
Framework-specific code examples and patterns
skills/using-my-organization-api/references/examples.md
End-to-end working examples for Next.js, React, Express, Python, and Java showing Auth0 client initialization, organization context handling, member invitation flows, and admin operations.
Backend validation and security patterns
skills/using-my-organization-api/references/backend.md
Backend implementation patterns for org context validation via Next.js middleware and Express, role-based access control, MFA policy enforcement, session management, invitation validation, domain verification, and centralized error handling.
Advanced features and optional integrations
skills/using-my-organization-api/references/advanced.md
Advanced implementation patterns including Auth0 Actions integration, adaptive MFA policies, session lifecycle management, SCIM provisioning, multi-organization account switching, organization audit logging, and circuit breaker error resilience.
Management API and CLI reference documentation
skills/using-my-organization-api/references/api.md
Complete API reference covering Auth0 Management API v2 endpoints for organization management, member/invitation/role operations, CLI commands, SDK support matrix, error codes, rate limits, configuration examples, and troubleshooting.
Automated Auth0 tenant provisioning and configuration
scripts/bootstrap.mjs, scripts/utils/discovery.mjs, scripts/utils/resources.mjs, scripts/utils/clients.mjs, scripts/utils/auth0-api.mjs
Idempotent bootstrap orchestration and utility modules that discover existing resources, ensure tenant and prompt settings, provision connection profiles and resource servers, create Auth0 clients and grants, set up admin roles, create demo organizations, and generate environment variables.
Stack detection, theme extraction, and verification scripts
scripts/detect-stack.mjs, scripts/extract-theme.mjs, scripts/validate-auth0.mjs, scripts/verify-setup.mjs
Project introspection and validation tools: stack detection infers framework/build-tool/package-manager, theme extraction parses CSS variables into Auth0 configuration, Auth0 CLI validation performs pre-flight checks, and post-setup verification confirms environment variables, packages, CSS imports, provider hierarchy, and theme overrides.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A skill to delegate with grace,
My Organization gives its space,
Five frameworks shown, from Node to Java's run,
Auto-bootstrap makes setup fun!
Auth0 admins, now go and build. 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.63% 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 'feat(auth0): Add My Organization API skill' clearly and directly summarizes the main change: adding a new skill for the My Organization API within the Auth0 plugin system.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

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

⚠️ Outside diff range comments (1)
plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/verify-setup.mjs (1)

161-172: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Verification should fail process exit code when checks fail.

Line 162 and Line 171 always report success/exit 0, even when checks fail. That breaks CI gating and downstream automation.

💡 Suggested fix
 const result = {
-  status: "success",
+  status: allPassed ? "success" : "error",
   data: {
@@
 console.log(JSON.stringify(result, null, 2));
-process.exit(0);
+process.exit(allPassed ? 0 : 1);
🤖 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
`@plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/verify-setup.mjs`
around lines 161 - 172, The script currently always sets result.status to
"success" and calls process.exit(0) even when checks fail; change the logic that
builds the result and exits so that if allPassed is false you set result.status
to "failure" (or "failed") and call process.exit(1) (or another non-zero code),
otherwise keep status "success" and exit 0; update the block that constructs
result (including the summary) and replace the unconditional process.exit(0)
with a conditional exit based on allPassed.
🤖 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
`@plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/references/advanced.md`:
- Around line 99-114: The token customization currently implemented in
onExecutePostLogin runs only for interactive user logins; move this logic into a
credentials-exchange rule/trigger so it executes for Client Credentials (M2M)
flows: create a credentials-exchange handler that extracts
accessToken.getCustomClaim('org_id'), calls getOrganizationPermissions(orgId)
and sets the same claims via api.accessToken.setCustomClaim('organization_id',
orgId) and api.accessToken.setCustomClaim('org_permissions', ...); remove or
disable the onExecutePostLogin usage for M2M and ensure the new
credentials-exchange trigger references the same getOrganizationPermissions
helper and uses the provided api object for setting access token claims.

In
`@plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/references/backend.md`:
- Around line 22-27: The middleware uses NextResponse.redirect with plain string
paths; change both redirects to construct absolute URLs using new URL(...,
request.url) so they resolve correctly in middleware: replace
NextResponse.redirect('/api/auth/login') and
NextResponse.redirect('/onboarding') with NextResponse.redirect(new
URL('/api/auth/login', request.url)) and NextResponse.redirect(new
URL('/onboarding', request.url)) respectively, keeping the existing session
check (if (!session.user.org_id)) and request variable names intact.

In
`@plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/detect-stack.mjs`:
- Around line 76-77: The script currently reads AUTH0_CLIENT_SECRET into
config.clientSecret and later serializes the whole config to stdout (via
JSON.stringify or similar), which leaks secrets; instead, do not populate
config.clientSecret from process.env.AUTH0_CLIENT_SECRET (or if it must be read,
ensure you remove or replace config.clientSecret with a redacted placeholder
like "<redacted>" before any output) and update the serialization step that
writes to stdout so it only emits non-secret fields (e.g., domain and clientId)
or a redacted config; refer to the config object and any code that assigns from
process.env.AUTH0_CLIENT_SECRET and the
JSON.stringify/console.log/process.stdout.write call that prints the config and
ensure the secret is never included in that output.

In
`@plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/extract-theme.mjs`:
- Around line 228-241: The generated CSS always emits the hardcoded ".dark { ...
}" block; change it to use the detected dark selector variable (darkSelector)
instead so dark-mode overrides are emitted as `${darkSelector} { ... }`; update
all occurrences where ".dark {" is emitted (the block that checks hasDarkMode &&
Object.keys(darkColors).length > 0 and any other places emitting dark-mode
overrides) and keep the same inner logic that switches on cssPath and prefixes
(--name vs --auth0-name).

In
`@plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/utils/clients.mjs`:
- Around line 103-105: The condition and read use the wrong field name: replace
references to client.allowed_web_origins with client.web_origins so the SPA
check uses the actual Auth0 response property; when building updates.web_origins
(the symbol already used), merge existing client.web_origins (or empty array)
with baseUrl instead of reading the undefined allowed_web_origins to avoid
clobbering existing origins in the update.

In
`@plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/utils/discovery.mjs`:
- Around line 60-63: The discovery code currently calls auth0ApiCall for key
resources (e.g., the call assigned to profiles and stored into
resources.connectionProfiles) and silently proceeds when the call fails; update
each discovery block that calls auth0ApiCall (for "connection-profiles" and the
two other similar calls) to await the promise, check the response.ok flag and
presence of response.data, and if the call failed log the error details (include
the response or thrown error) and either throw or return an early failure
instead of treating the resource as simply empty; specifically modify the blocks
that reference auth0ApiCall and assign to resources.connectionProfiles,
resources.identityProviders, and resources.roles so failures are surfaced with
context rather than swallowed.

In
`@plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/utils/resources.mjs`:
- Around line 191-196: The org-connection enablement calls currently ignore the
auth0ApiCall response so failures are swallowed; update the code around the
auth0ApiCall invocations that post to
`organizations/${org.id}/enabled_connections` (both the block calling
auth0ApiCall at the earlier location and the later block around lines 204-209)
to inspect the returned result and propagate errors: await the auth0ApiCall,
check that the response indicates success (e.g., result === "created" || result
=== "updated" or HTTP success), and if not throw or return a descriptive error
so callers can detect failure instead of assuming success; ensure you preserve
existing behavior on success and surface the underlying API error message when
available.
- Line 157: The code calls JSON.parse on CLI output directly (const role =
JSON.parse(createResult.stdout); and the similar parse at line 189) which can
throw on non-JSON output; wrap the JSON.parse calls for createResult.stdout (and
the other stdout parse) in a try/catch (or use a safe-parse helper) to handle
malformed output gracefully, log the parse error and the raw stdout (using the
same process/logger used elsewhere) and then either fail the operation with a
clear error or return a safe fallback so the bootstrap does not hard-crash;
update references to role and the second parsed variable accordingly so callers
handle the possible undefined/error path.
- Around line 98-102: The update check in the needsUpdate logic omits comparing
enabled_features so resources with drift there are skipped; update the boolean
expression in needsUpdate (the variable used around existing.organization,
desiredConfig.organization and connection_name_prefix_template) to also compare
existing.enabled_features against desiredConfig.enabled_features (or a
normalized deep-equality check) so differences in enabled_features trigger an
update action instead of returning skip.

In
`@plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/verify-setup.mjs`:
- Around line 37-47: The script calls .includes() on the value returned by
readFile without guarding against a failed or undefined read; update the logic
around readFile and content so you first validate that content is a non-empty
string (or coerce to "") before running the required/filter checks (the
variables/functions to update are readFile, content, required, and the missing
filter), and if the file could not be read return a clear error or exit early
rather than performing .includes() on null/undefined.

In
`@plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/SKILL.md`:
- Line 78: Several fenced code blocks in SKILL.md are missing language
identifiers (violating MD040); update each triple-backtick block (the anonymous
``` fences) to include an appropriate language tag (e.g., ```bash, ```json,
```text) so markdownlint passes and readability improves—search for the unnamed
``` blocks in the SKILL.md content and replace them with language-tagged fences
that match the block contents.

---

Outside diff comments:
In
`@plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/verify-setup.mjs`:
- Around line 161-172: The script currently always sets result.status to
"success" and calls process.exit(0) even when checks fail; change the logic that
builds the result and exits so that if allPassed is false you set result.status
to "failure" (or "failed") and call process.exit(1) (or another non-zero code),
otherwise keep status "success" and exit 0; update the block that constructs
result (including the summary) and replace the unconditional process.exit(0)
with a conditional exit based on allPassed.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 423e9152-973a-4c6a-9255-a32351fd0f63

📥 Commits

Reviewing files that changed from the base of the PR and between b420ff5 and e92c72b.

📒 Files selected for processing (16)
  • plugins/auth0/skills/auth0-my-organization-api/.claude-plugin/plugin.json
  • plugins/auth0/skills/auth0-my-organization-api/README.md
  • plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/SKILL.md
  • plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/references/advanced.md
  • plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/references/api.md
  • plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/references/backend.md
  • plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/references/examples.md
  • plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/bootstrap.mjs
  • plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/detect-stack.mjs
  • plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/extract-theme.mjs
  • plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/utils/auth0-api.mjs
  • plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/utils/clients.mjs
  • plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/utils/discovery.mjs
  • plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/utils/resources.mjs
  • plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/validate-auth0.mjs
  • plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/verify-setup.mjs

Comment on lines +99 to +114
exports.onExecutePostLogin = async (event, api) => {
const { client, accessToken } = event

// Only apply to M2M clients with organization scope
if (client.client_id !== process.env.MY_ORG_API_CLIENT_ID) {
return
}

// Add organization context to M2M tokens
// This allows API calls to be scoped to specific organizations
const orgId = accessToken.getCustomClaim('org_id')
if (orgId) {
api.accessToken.setCustomClaim('organization_id', orgId)
api.accessToken.setCustomClaim('org_permissions', await getOrganizationPermissions(orgId))
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Which Auth0 Action trigger is used to customize access tokens issued via client credentials (M2M): post-login or credentials-exchange?

💡 Result:

The Auth0 Action trigger used to customize access tokens issued via client credentials (M2M) is credentials-exchange [1][2][3]. This trigger runs as part of the Machine to Machine Flow when an access token is issued via the Client Credentials Flow, allowing customization such as adding custom claims or denying the token [1][4][2]. The post-login trigger is for user login flows, not M2M [3].

Citations:


Use credentials-exchange trigger for M2M token customization, not onExecutePostLogin.

The onExecutePostLogin trigger only fires for user authentication flows. For Machine-to-Machine (M2M) flows using the Client Credentials grant, the credentials-exchange trigger must be used instead. Move this token customization logic to a credentials-exchange trigger so it executes when M2M access tokens are issued.

🤖 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
`@plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/references/advanced.md`
around lines 99 - 114, The token customization currently implemented in
onExecutePostLogin runs only for interactive user logins; move this logic into a
credentials-exchange rule/trigger so it executes for Client Credentials (M2M)
flows: create a credentials-exchange handler that extracts
accessToken.getCustomClaim('org_id'), calls getOrganizationPermissions(orgId)
and sets the same claims via api.accessToken.setCustomClaim('organization_id',
orgId) and api.accessToken.setCustomClaim('org_permissions', ...); remove or
disable the onExecutePostLogin usage for M2M and ensure the new
credentials-exchange trigger references the same getOrganizationPermissions
helper and uses the provided api object for setting access token claims.

Comment on lines +22 to +27
return NextResponse.redirect('/api/auth/login')
}

// Check if user has organization context
if (!session.user.org_id) {
return NextResponse.redirect('/onboarding')
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

For current Next.js middleware, what is the recommended NextResponse.redirectusage: string path ornew URL(path, request.url)?

💡 Result:

For current Next.js middleware (as of 2026, Next.js 15+), the recommended usage of NextResponse.redirect is new URL(path, request.url) rather than a plain string path.[1][2][3] Official Next.js documentation consistently shows examples using new URL('/path', request.url) in middleware and NextResponse.redirect sections.[4][1][2][3] This approach ensures the redirect URL is properly resolved relative to the current request's base URL (origin and protocol), avoiding issues with relative paths in middleware contexts where the base might not be assumed as the app's root.[1] While NextResponse.redirect technically accepts a string (as it extends Response.redirect, which takes string|URL), using a plain relative string like NextResponse.redirect('/home') risks incorrect resolution if the request base differs (e.g., proxies, custom domains). The docs emphasize constructing a full URL object for reliability, especially when modifying search params: const url = new URL('/login', request.url); url.searchParams.set('from', request.nextUrl.pathname); return NextResponse.redirect(url);[1][2] No official sources recommend plain strings for middleware redirects; all examples use new URL.[4][3]

Citations:


Use new URL() construction for middleware redirects.

Lines 22 and 27 use plain string paths with NextResponse.redirect(). In Next.js middleware, these must use new URL(path, request.url) to properly resolve the full URL relative to the request base.

Suggested fix
-    return NextResponse.redirect('/api/auth/login')
+    return NextResponse.redirect(new URL('/api/auth/login', request.url))
...
-    return NextResponse.redirect('/onboarding')
+    return NextResponse.redirect(new URL('/onboarding', request.url))
📝 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
return NextResponse.redirect('/api/auth/login')
}
// Check if user has organization context
if (!session.user.org_id) {
return NextResponse.redirect('/onboarding')
return NextResponse.redirect(new URL('/api/auth/login', request.url))
}
// Check if user has organization context
if (!session.user.org_id) {
return NextResponse.redirect(new URL('/onboarding', request.url))
🤖 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
`@plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/references/backend.md`
around lines 22 - 27, The middleware uses NextResponse.redirect with plain
string paths; change both redirects to construct absolute URLs using new
URL(..., request.url) so they resolve correctly in middleware: replace
NextResponse.redirect('/api/auth/login') and
NextResponse.redirect('/onboarding') with NextResponse.redirect(new
URL('/api/auth/login', request.url)) and NextResponse.redirect(new
URL('/onboarding', request.url)) respectively, keeping the existing session
check (if (!session.user.org_id)) and request variable names intact.

Comment on lines +76 to +77
const config = { domain: null, clientId: null, clientSecret: null };
if (envFile) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not emit AUTH0_CLIENT_SECRET in detection output.

Line 93-Line 95 read the raw secret and Line 183 serializes it to stdout. This can leak credentials in terminal history and CI logs.

💡 Suggested fix
-  const config = { domain: null, clientId: null, clientSecret: null };
+  const config = { domain: null, clientId: null, hasClientSecret: false };
@@
-        } else if (key === "AUTH0_CLIENT_SECRET") {
-          config.clientSecret = val;
+        } else if (key === "AUTH0_CLIENT_SECRET") {
+          config.hasClientSecret = true;
         }

Also applies to: 93-95, 101-101, 183-183

🤖 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
`@plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/detect-stack.mjs`
around lines 76 - 77, The script currently reads AUTH0_CLIENT_SECRET into
config.clientSecret and later serializes the whole config to stdout (via
JSON.stringify or similar), which leaks secrets; instead, do not populate
config.clientSecret from process.env.AUTH0_CLIENT_SECRET (or if it must be read,
ensure you remove or replace config.clientSecret with a redacted placeholder
like "<redacted>" before any output) and update the serialization step that
writes to stdout so it only emits non-secret fields (e.g., domain and clientId)
or a redacted config; refer to the config object and any code that assigns from
process.env.AUTH0_CLIENT_SECRET and the
JSON.stringify/console.log/process.stdout.write call that prints the config and
ensure the secret is never included in that output.

Comment on lines +228 to +241
if (hasDarkMode && Object.keys(darkColors).length > 0) {
lines.push("");
lines.push(".dark {");
if (cssPath === "tailwind") {
for (const [name, value] of Object.entries(darkColors)) {
lines.push(` --${name}: ${value};`);
}
} else {
for (const [name, value] of Object.entries(darkColors)) {
lines.push(` --auth0-${name}: ${value};`);
}
}
lines.push("}");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the detected dark selector when emitting dark-mode overrides.

Line 303-Line 305 correctly detect whether dark mode is .dark or [data-theme="dark"], but Line 230 always emits .dark { ... }. That makes generated CSS incorrect for projects using data-attribute dark mode.

💡 Suggested fix
-function generateOverrideBlock(colors, cssPath, darkColors, hasDarkMode) {
+function generateOverrideBlock(colors, cssPath, darkColors, hasDarkMode, darkSelector = ".dark") {
   const lines = ["/* Auth0 Universal Components — apply this entire block verbatim */", ":root {"];
@@
   if (hasDarkMode && Object.keys(darkColors).length > 0) {
     lines.push("");
-    lines.push(".dark {");
+    lines.push(`${darkSelector} {`);
@@
-const generatedOverrideBlock = generateOverrideBlock(colors, opts.cssPath, darkColors, hasDarkMode);
+const generatedOverrideBlock = generateOverrideBlock(colors, opts.cssPath, darkColors, hasDarkMode, darkSelector || ".dark");

Also applies to: 303-305, 318-318

🤖 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
`@plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/extract-theme.mjs`
around lines 228 - 241, The generated CSS always emits the hardcoded ".dark {
... }" block; change it to use the detected dark selector variable
(darkSelector) instead so dark-mode overrides are emitted as `${darkSelector} {
... }`; update all occurrences where ".dark {" is emitted (the block that checks
hasDarkMode && Object.keys(darkColors).length > 0 and any other places emitting
dark-mode overrides) and keep the same inner logic that switches on cssPath and
prefixes (--name vs --auth0-name).

Comment on lines +103 to +105
if (appType === "spa" && !client.allowed_web_origins?.includes(baseUrl)) {
updates.web_origins = [...(client.allowed_web_origins || []), baseUrl];
needsUpdate = true;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Auth0 Management API for clients: what is the correct property name for SPA web origins in GET/PATCH payloads (web_originsvsallowed_web_origins)?

💡 Result:

The correct property name for SPA web origins in Auth0 Management API GET and PATCH payloads for clients is "web_origins". This is confirmed across multiple sources, including official Auth0.NET SDK models which map [JsonProperty("web_origins")] to WebOrigins, describing it as "A set of allowed origins for use with Cross-Origin Authentication and web message response mode" [1][2]. SDK examples for creating and updating SPA clients explicitly use WebOrigins: []string{...} [3]. Community discussions refer to setting web_origins via the API [4]. Note that "allowed_origins" (or allowedOrigins) is a separate property for CORS origins [1][5][6][3]. No sources mention "allowed_web_origins"; "web_origins" is the consistent JSON property name for responses (GET) and requests (POST/PATCH) [1][5].

Citations:


🏁 Script executed:

cat -n plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/utils/clients.mjs | sed -n '90,115p'

Repository: auth0/agent-skills

Length of output: 1254


🏁 Script executed:

rg "allowed_web_origins|web_origins" plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/utils/clients.mjs -n -A 2 -B 2

Repository: auth0/agent-skills

Length of output: 492


Use the correct origins field to avoid clobbering SPA config.

At lines 103-104, the code checks/reads allowed_web_origins, but Auth0 API responses use web_origins and the payload is already correctly set to web_origins at line 38. This mismatch causes the condition to evaluate against an undefined property and loses existing origins when updating.

Suggested fix
-  if (appType === "spa" && !client.allowed_web_origins?.includes(baseUrl)) {
-    updates.web_origins = [...(client.allowed_web_origins || []), baseUrl];
+  if (appType === "spa" && !client.web_origins?.includes(baseUrl)) {
+    updates.web_origins = [...(client.web_origins || []), baseUrl];
     needsUpdate = true;
   }
📝 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
if (appType === "spa" && !client.allowed_web_origins?.includes(baseUrl)) {
updates.web_origins = [...(client.allowed_web_origins || []), baseUrl];
needsUpdate = true;
if (appType === "spa" && !client.web_origins?.includes(baseUrl)) {
updates.web_origins = [...(client.web_origins || []), baseUrl];
needsUpdate = true;
🤖 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
`@plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/utils/clients.mjs`
around lines 103 - 105, The condition and read use the wrong field name: replace
references to client.allowed_web_origins with client.web_origins so the SPA
check uses the actual Auth0 response property; when building updates.web_origins
(the symbol already used), merge existing client.web_origins (or empty array)
with baseUrl instead of reading the undefined allowed_web_origins to avoid
clobbering existing origins in the update.

Comment on lines +98 to +102
const needsUpdate =
existing.organization?.show_as_button !== desiredConfig.organization.show_as_button ||
existing.organization?.assign_membership_on_login !== desiredConfig.organization.assign_membership_on_login ||
existing.connection_name_prefix_template !== desiredConfig.connection_name_prefix_template;
if (!needsUpdate) return { action: "skip", data: existing };
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

enabled_features drift is not part of update check.

Line 98-Line 102 skip update decisions without validating enabled_features, so stale profiles can be incorrectly treated as up-to-date.

💡 Suggested fix
     const needsUpdate =
       existing.organization?.show_as_button !== desiredConfig.organization.show_as_button ||
       existing.organization?.assign_membership_on_login !== desiredConfig.organization.assign_membership_on_login ||
-      existing.connection_name_prefix_template !== desiredConfig.connection_name_prefix_template;
+      existing.connection_name_prefix_template !== desiredConfig.connection_name_prefix_template ||
+      JSON.stringify([...(existing.enabled_features || [])].sort()) !==
+        JSON.stringify([...(desiredConfig.enabled_features || [])].sort());
📝 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
const needsUpdate =
existing.organization?.show_as_button !== desiredConfig.organization.show_as_button ||
existing.organization?.assign_membership_on_login !== desiredConfig.organization.assign_membership_on_login ||
existing.connection_name_prefix_template !== desiredConfig.connection_name_prefix_template;
if (!needsUpdate) return { action: "skip", data: existing };
const needsUpdate =
existing.organization?.show_as_button !== desiredConfig.organization.show_as_button ||
existing.organization?.assign_membership_on_login !== desiredConfig.organization.assign_membership_on_login ||
existing.connection_name_prefix_template !== desiredConfig.connection_name_prefix_template ||
JSON.stringify([...(existing.enabled_features || [])].sort()) !==
JSON.stringify([...(desiredConfig.enabled_features || [])].sort());
if (!needsUpdate) return { action: "skip", data: existing };
🤖 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
`@plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/utils/resources.mjs`
around lines 98 - 102, The update check in the needsUpdate logic omits comparing
enabled_features so resources with drift there are skipped; update the boolean
expression in needsUpdate (the variable used around existing.organization,
desiredConfig.organization and connection_name_prefix_template) to also compare
existing.enabled_features against desiredConfig.enabled_features (or a
normalized deep-equality check) so differences in enabled_features trigger an
update action instead of returning skip.

{ timeout: 15000 }
);
if (!createResult.ok) return { action: "error", error: createResult.stderr };
const role = JSON.parse(createResult.stdout);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard JSON parsing of CLI output to avoid hard crashes.

Line 157 and Line 189 parse CLI stdout without a try/catch. Any unexpected non-JSON output will throw and terminate bootstrap.

💡 Suggested fix
-    const role = JSON.parse(createResult.stdout);
+    let role;
+    try {
+      role = JSON.parse(createResult.stdout);
+    } catch {
+      return { action: "error", error: "Unable to parse role creation response" };
+    }
@@
-    const org = JSON.parse(createResult.stdout);
+    let org;
+    try {
+      org = JSON.parse(createResult.stdout);
+    } catch {
+      return { action: "error", error: "Unable to parse organization creation response" };
+    }

Also applies to: 189-189

🤖 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
`@plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/utils/resources.mjs`
at line 157, The code calls JSON.parse on CLI output directly (const role =
JSON.parse(createResult.stdout); and the similar parse at line 189) which can
throw on non-JSON output; wrap the JSON.parse calls for createResult.stdout (and
the other stdout parse) in a try/catch (or use a safe-parse helper) to handle
malformed output gracefully, log the parse error and the raw stdout (using the
same process/logger used elsewhere) and then either fail the operation with a
clear error or return a safe fallback so the bootstrap does not hard-crash;
update references to role and the second parsed variable accordingly so callers
handle the possible undefined/error path.

Comment on lines +191 to +196
auth0ApiCall("post", `organizations/${org.id}/enabled_connections`, {
connection_id: connectionId,
assign_membership_on_login: false,
is_signup_enabled: false,
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Propagate failures when enabling organization connections.

Line 191-Line 196 and Line 204-Line 209 ignore the API response. The function can return "created"/"updated" even if connection enablement fails.

💡 Suggested fix
     if (connectionId) {
-      auth0ApiCall("post", `organizations/${org.id}/enabled_connections`, {
+      const enableResult = auth0ApiCall("post", `organizations/${org.id}/enabled_connections`, {
         connection_id: connectionId,
         assign_membership_on_login: false,
         is_signup_enabled: false,
       });
+      if (!enableResult.ok) return { action: "error", error: enableResult.error };
     }
@@
     if (!connCheck.ok || !connCheck.data?.connection) {
-      auth0ApiCall("post", `organizations/${existing.id}/enabled_connections`, {
+      const enableResult = auth0ApiCall("post", `organizations/${existing.id}/enabled_connections`, {
         connection_id: connectionId,
         assign_membership_on_login: false,
         is_signup_enabled: false,
       });
+      if (!enableResult.ok) return { action: "error", error: enableResult.error };
       return { action: "updated", data: existing };
     }

Also applies to: 204-209

🤖 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
`@plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/utils/resources.mjs`
around lines 191 - 196, The org-connection enablement calls currently ignore the
auth0ApiCall response so failures are swallowed; update the code around the
auth0ApiCall invocations that post to
`organizations/${org.id}/enabled_connections` (both the block calling
auth0ApiCall at the earlier location and the later block around lines 204-209)
to inspect the returned result and propagate errors: await the auth0ApiCall,
check that the response indicates success (e.g., result === "created" || result
=== "updated" or HTTP success), and if not throw or return a descriptive error
so callers can detect failure instead of assuming success; ensure you preserve
existing behavior on success and surface the underlying API error message when
available.

Comment on lines +37 to +47
const content = readFile(join(root, envPath));
const required = framework === "nextjs"
? ["AUTH0_DOMAIN", "AUTH0_CLIENT_ID", "AUTH0_CLIENT_SECRET", "AUTH0_SECRET"]
: ["AUTH0_DOMAIN", "AUTH0_CLIENT_ID"];
// Also check VITE_ prefixed vars for react-spa
const missing = required.filter((v) => {
if (content.includes(v + "=")) return false;
if (framework === "react-spa" && content.includes("VITE_" + v.replace("AUTH0_", "AUTH0_") + "=")) return false;
if (framework === "react-spa" && content.includes(`VITE_${v}=`)) return false;
return true;
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard against unreadable env files before string checks.

Line 43-Line 46 call .includes() on content without null guard. If file read fails (permissions/encoding issues), this throws and aborts the script.

💡 Suggested fix
   const content = readFile(join(root, envPath));
+  if (content == null) {
+    return {
+      name: "env_vars_present",
+      pass: false,
+      details: `Cannot read ${envPath}`,
+      fix: `Ensure ${envPath} is readable and contains required Auth0 variables`,
+    };
+  }
   const required = framework === "nextjs"
📝 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
const content = readFile(join(root, envPath));
const required = framework === "nextjs"
? ["AUTH0_DOMAIN", "AUTH0_CLIENT_ID", "AUTH0_CLIENT_SECRET", "AUTH0_SECRET"]
: ["AUTH0_DOMAIN", "AUTH0_CLIENT_ID"];
// Also check VITE_ prefixed vars for react-spa
const missing = required.filter((v) => {
if (content.includes(v + "=")) return false;
if (framework === "react-spa" && content.includes("VITE_" + v.replace("AUTH0_", "AUTH0_") + "=")) return false;
if (framework === "react-spa" && content.includes(`VITE_${v}=`)) return false;
return true;
});
const content = readFile(join(root, envPath));
if (content == null) {
return {
name: "env_vars_present",
pass: false,
details: `Cannot read ${envPath}`,
fix: `Ensure ${envPath} is readable and contains required Auth0 variables`,
};
}
const required = framework === "nextjs"
? ["AUTH0_DOMAIN", "AUTH0_CLIENT_ID", "AUTH0_CLIENT_SECRET", "AUTH0_SECRET"]
: ["AUTH0_DOMAIN", "AUTH0_CLIENT_ID"];
// Also check VITE_ prefixed vars for react-spa
const missing = required.filter((v) => {
if (content.includes(v + "=")) return false;
if (framework === "react-spa" && content.includes("VITE_" + v.replace("AUTH0_", "AUTH0_") + "=")) return false;
if (framework === "react-spa" && content.includes(`VITE_${v}=`)) return false;
return true;
});
🤖 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
`@plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/scripts/verify-setup.mjs`
around lines 37 - 47, The script calls .includes() on the value returned by
readFile without guarding against a failed or undefined read; update the logic
around readFile and content so you first validate that content is a non-empty
string (or coerce to "") before running the required/filter checks (the
variables/functions to update are readFile, content, required, and the missing
filter), and if the file could not be read return a clear error or exit early
rather than performing .includes() on null/undefined.


Detect the project state and follow this decision tree. Do not ask the user to choose a path.

```
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add fence languages to satisfy markdownlint and improve readability.

Several fenced blocks are missing a language identifier (MD040). Please add text, bash, json, etc., as appropriate at the listed lines.

Also applies to: 132-132, 260-260, 292-292, 623-623, 748-748, 759-759, 764-764

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 78-78: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 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
`@plugins/auth0/skills/auth0-my-organization-api/skills/using-my-organization-api/SKILL.md`
at line 78, Several fenced code blocks in SKILL.md are missing language
identifiers (violating MD040); update each triple-backtick block (the anonymous
``` fences) to include an appropriate language tag (e.g., ```bash, ```json,
```text) so markdownlint passes and readability improves—search for the unnamed
``` blocks in the SKILL.md content and replace them with language-tagged fences
that match the block contents.

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.

1 participant