Skip to content

feat: add auth sso command for SSO provider configuration#484

Merged
moranshe-max merged 6 commits intomainfrom
feature/add-sso-command
Apr 30, 2026
Merged

feat: add auth sso command for SSO provider configuration#484
moranshe-max merged 6 commits intomainfrom
feature/add-sso-command

Conversation

@moranshe-max
Copy link
Copy Markdown
Collaborator

@moranshe-max moranshe-max commented Apr 16, 2026

Note

Description

This PR adds the base44 auth sso command, enabling users to configure SSO (Single Sign-On) authentication for their Base44 apps from the CLI. It supports five providers — Google, Microsoft, GitHub, Okta, and custom OIDC — and handles secret management by pushing credentials securely to the backend. The implementation includes both the CLI presentation layer (auth/sso.ts) and the core SDK layer with provider-specific schemas, secret key definitions, and operations.

Related Issue

None

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Other (please describe):

Changes Made

  • Added base44 auth sso enable and base44 auth sso disable subcommands under the existing auth command group
  • Implemented core/resources/auth-config/sso/ module with provider schemas for Google, Microsoft, GitHub, Okta, and custom OIDC providers
  • Added buildSSOSecrets, pushSSOSecrets, deleteSSOSecrets, and updateSSOConfig operations to manage secrets and local auth config
  • Supports multiple secret input methods: --client-secret, --client-secret-stdin, --env-file, and environment variable (SSO_CLIENT_SECRET)
  • Supports loading full SSO config from a JSON/JSONC file via --file, with CLI flags taking precedence
  • Validates that --file and --env-file cannot be used together
  • Enables SSO and automatically disables mutually exclusive social login providers in auth.jsonc
  • Structured MissingSSOFieldsError maps missing API secret keys to user-facing flag names for clear error messages
  • Added comprehensive CLI integration tests (tests/cli/auth_sso.spec.ts) and core unit tests (tests/core/auth-sso.spec.ts) covering validation, all providers, and all secret input methods

Testing

  • I have tested these changes locally
  • I have added/updated tests as needed
  • All tests pass (npm test)

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (if applicable)
  • My changes generate no new warnings
  • I have updated docs/ (AGENTS.md) if I made architectural changes

Additional Notes

SSO and social login (Google, Microsoft, Facebook, Apple) are mutually exclusive in the auth config. Enabling SSO via this command automatically disables any enabled social login providers in the local auth.jsonc. The sso_name secret key intentionally maps to --sso-name (not --name) to avoid future flag collisions.


🤖 Generated by Claude | 2026-04-30 00:00 UTC | 9abd5e2

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 16, 2026

🚀 Package Preview Available!


Install this PR's preview build with npm:

npm i @base44-preview/cli@0.0.51-pr.484.9abd5e2

Prefer not to change any import paths? Install using npm alias so your code still imports base44:

npm i "base44@npm:@base44-preview/cli@0.0.51-pr.484.9abd5e2"

Or add it to your package.json dependencies:

{
  "dependencies": {
    "base44": "npm:@base44-preview/cli@0.0.51-pr.484.9abd5e2"
  }
}

Preview published to npm registry — try new features instantly!

@moranshe-max moranshe-max force-pushed the feature/add-sso-command branch from 9e2128b to aa29b02 Compare April 16, 2026 12:21
Add `base44 auth sso enable/disable` command supporting Google, Microsoft,
GitHub, Okta, and custom OIDC providers. Supports flags, --file JSON input,
stdin, and env-file for secret resolution. SSO and social login are mutually
exclusive — enabling one disables the other in the local config.

Core SSO module is split into per-provider files with SSOSecretKey enum as
single source of truth for API secret keys.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@moranshe-max moranshe-max force-pushed the feature/add-sso-command branch from aa29b02 to 4ecbb50 Compare April 16, 2026 12:22
moranshe-max and others added 2 commits April 16, 2026 16:22
- Provider-specific validation errors now show CLI flag names (--tenant-id)
  instead of API key names (sso_tenant_id)
- All validation errors include example commands with provider-specific
  required flags
- Invalid provider error now includes a hint with example command

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…t validation

Commander now enforces valid provider values early, so the manual
check in validateProvider is no longer needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@moranshe-max moranshe-max force-pushed the feature/add-sso-command branch from 4601bf3 to 7ce86f0 Compare April 16, 2026 13:42
return new Base44Command("sso")
.description(
"Configure SSO identity provider (google, microsoft, github, okta, custom)",
)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

SSO and social login are mutually exclusive — enabling SSO silently flips enableGoogleLogin / enableMicrosoftLogin / enableFacebookLogin / enableAppleLogin to false in auth/config.json (and vice versa for auth google enable etc.). This isn't surfaced anywhere user-facing. Consider mentioning it in the command description so it shows up in --help.

Copy link
Copy Markdown
Collaborator

@Paveltarno Paveltarno left a comment

Choose a reason for hiding this comment

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

nice work, this is shaping up well 👏
left some comments — main ones are around consistency with social-login (no lockout warning on disable, disable silently accepts enable flags) and using SchemaValidationError instead of hand-formatting zod issues. rest are mostly nits / wdyt

const issues = result.error.issues
.map((i) => ` ${i.path.join(".")}: ${i.message}`)
.join("\n");
throw new InvalidInputError(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

all our other config-file loaders throw SchemaValidationError for zod parse failures — see agent/config.ts:25, function/config.ts:21, entity/config.ts:13, auth/config.ts:30. it formats the ZodError for us so we don't need to hand-roll the issues.map(...) block. wdyt about:

if (!result.success) {
  throw new SchemaValidationError(
    "Invalid SSO config file",
    result.error,
    filePath,
  );
}

(also called out in docs/error-handling.md)

};
}

async function ssoDisableAction({
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

social-login.ts:150 warns the user via hasAnyLoginMethod when disabling leaves no login methods enabled, and hasAnyLoginMethod already factors in enableSSOLogin (schema.ts:74). does it make sense to do the same here? auth sso disable can lock users out the same way and right now it's silent.

action: "enable" | "disable",
options: SSOOptions,
): Promise<RunCommandResult> {
if (action === "disable") {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

social-login.ts:73 errors out when enable-only options are passed alongside disable. here auth sso disable --provider google --client-id x --tenant-id y runs fine and silently drops everything. wdyt about doing the same check for parity?

// -- File input schema (CLI concern: user-facing file format) ----------------

const SSOConfigFileSchema = z.object({
provider: z.enum(["google", "microsoft", "github", "okta", "custom"]),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

the provider list here is hardcoded as z.enum(["google", "microsoft", "github", "okta", "custom"]) but KNOWN_SSO_PROVIDERS is already the single source of truth (and Commander uses it on sso.ts:284 via Object.values(KNOWN_SSO_PROVIDERS)). wdyt about deriving it the same way here? something like:

provider: z.enum(Object.values(KNOWN_SSO_PROVIDERS) as [KnownSSOProvider, ...KnownSSOProvider[]]),

} catch (error) {
if (error instanceof InvalidInputError) {
// Re-throw with CLI flag names and an example command
const flagMessage = error.message.replace(/sso_[a-z_]+/g, (key) =>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

the catch block here string-mangles sso_* substrings out of error.message to convert backend keys to flag names. similar to my comment on PR #445 — wdyt about returning structured info from buildSSOSecrets instead, so the CLI doesn't depend on the exact wording? e.g. attach missing: SSOSecretKey[] on the error, or split out a separate validateSSOSecrets() that returns missing keys without throwing. not blocking, just feels brittle.

import { describe, it } from "vitest";
import { fixture, setupCLITests } from "./testkit/index.js";

describe("auth sso command", () => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

a few flows aren't covered — wdyt about adding:

  • --file <path> happy path + invalid JSON / schema error
  • --env-file resolving sso_client_secret (and key-missing case)
  • --client-secret-stdin
  • the flag-name translation in the error (asserting --tenant-id appears, not sso_tenant_id) — this is the entire point of the catch in ssoEnableAction and right now nothing locks it in
  • flag override of provider defaults (e.g. passing --scope / --discovery-url to google and asserting it overrides)

the existing enables google SSO with valid flags test could also assert what got written to disk + what setSecrets was called with — auth_social_login.spec.ts does this for its command.

): Promise<RunCommandResult> {
// Load file config if provided
let merged = options;
if (options.file) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

small thing — when both --file and --env-file are passed, --env-file only kicks in if merged.clientSecret is empty (sso.ts:156), so a clientSecret in the JSON file silently outranks the env file. the precedence isn't documented and a user/agent could reasonably expect either to win. wdyt about either erroring on the combo, or stating the precedence explicitly in --env-file's help text?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

good point, I went with failing when both are passed.

moranshe-max and others added 2 commits April 30, 2026 11:19
- Document SSO/social login mutual exclusivity in command description
- Use SchemaValidationError for --file zod parse failures (matches other config loaders)
- Warn when `auth sso disable` leaves no login methods enabled (parity with social-login)
- Reject enable-only flags when used with `auth sso disable` (parity with social-login)
- Derive --file zod enum from KNOWN_SSO_PROVIDERS instead of hardcoding
- Replace string-mangling in catch block with structured MissingSSOFieldsError
  exposing typed missingKeys for the CLI to map to flag names
- Document --env-file precedence vs --file in help text
- Add test coverage for --file (happy path, invalid JSON, schema error),
  --env-file resolution + key-missing case, --client-secret-stdin,
  flag-name translation in errors, and --scope/--discovery-url override

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Per PR review feedback, prefer failing over silent precedence.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@Paveltarno Paveltarno left a comment

Choose a reason for hiding this comment

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

hit a small bug while looking through the changes — left a comment.


const providerNames = Object.keys(KNOWN_SSO_PROVIDERS);

/** Converts an API secret key like "sso_tenant_id" to a CLI flag like "--tenant-id". */
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

secretKeyToFlag("sso_name") returns "--name" because the regex strips sso_ and then maps underscores to dashes, but the actual CLI flag declared on sso.ts:349 is --sso-name. so a user missing only --sso-name for the custom provider sees Missing required fields for custom: --name, then Commander rejects --name as an unknown option.

the existing flag-translation tests at test:86 / test:108 only cover --tenant-id and --okta-domain, both of which happen to derive correctly via the regex. sso_name is the lone secret key whose flag intentionally diverges from sso_*--*, and there's no test for the custom missing---sso-name case so it slipped through.

…retKey.Name

The regex-based conversion stripped sso_ from all keys, but `sso_name`
intentionally keeps the prefix in its CLI flag (--sso-name) to avoid
ambiguity. A custom provider missing only --sso-name surfaced an
"unknown option" error to users.

Replace the regex with an explicit Record<SSOSecretKey, string> map and
add a regression test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@Paveltarno Paveltarno left a comment

Choose a reason for hiding this comment

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

🔥 nice work, all comments addressed cleanly

@moranshe-max moranshe-max merged commit 2198e24 into main Apr 30, 2026
13 checks passed
@moranshe-max moranshe-max deleted the feature/add-sso-command branch April 30, 2026 10:51
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