Skip to content

feat(auth): add auth config discovery endpoint for dynamic SSO UI rendering#1141

Merged
hotlong merged 3 commits intomainfrom
claude/add-third-party-sso-support
Apr 14, 2026
Merged

feat(auth): add auth config discovery endpoint for dynamic SSO UI rendering#1141
hotlong merged 3 commits intomainfrom
claude/add-third-party-sso-support

Conversation

@Claude
Copy link
Copy Markdown
Contributor

@Claude Claude AI commented Apr 14, 2026

Frontend applications need to discover server-side auth configuration to dynamically render appropriate login buttons (email/password, Google, GitHub, etc.) without hardcoding provider lists.

Changes

Protocol Layer (packages/spec/src/api/auth-endpoints.zod.ts)

  • Added AuthProviderInfoSchema - public OAuth provider metadata (id, name, enabled)
  • Added EmailPasswordConfigPublicSchema - email/password auth settings
  • Added AuthFeaturesConfigSchema - feature flags (2FA, passkeys, magic link, organization)
  • Added GetAuthConfigResponseSchema - complete config discovery response
  • All sensitive fields (clientSecret, secret) excluded by design

Backend (packages/plugins/plugin-auth/)

  • Added AuthManager.getPublicConfig() - extracts safe config from internal state
    • Filters disabled providers
    • Maps provider IDs to friendly names (google → Google)
    • Returns only non-sensitive configuration
  • Registered GET /api/v1/auth/config endpoint in AuthPlugin
    • Returns standard JSON envelope format
    • Includes error handling

Client SDK (packages/client/src/index.ts)

  • Added client.auth.getConfig() method
  • Exported config types: GetAuthConfigResponse, AuthProviderInfo, etc.

Tests

  • Comprehensive test coverage for getPublicConfig()
  • Validates sensitive data exclusion
  • Tests provider filtering and name mapping
  • Verifies default values

Usage Example

// Frontend discovers available auth methods
const client = new ObjectStackClient({ baseUrl: window.location.origin });
const config = await client.auth.getConfig();

// Dynamically render social login buttons
config.socialProviders.forEach(provider => {
  renderButton({
    label: `Login with ${provider.name}`,
    onClick: () => window.location.href = `/api/v1/auth/authorize/${provider.id}`
  });
});

Response Format

{
  "success": true,
  "data": {
    "emailPassword": {
      "enabled": true,
      "disableSignUp": false,
      "requireEmailVerification": false
    },
    "socialProviders": [
      { "id": "google", "name": "Google", "enabled": true },
      { "id": "github", "name": "GitHub", "enabled": true }
    ],
    "features": {
      "twoFactor": true,
      "passkeys": false,
      "magicLink": false,
      "organization": true
    }
  }
}

- Add GetAuthConfigResponse schema to auth-endpoints.zod.ts
- Add getPublicConfig() method to AuthManager
- Add GET /api/v1/auth/config endpoint in AuthPlugin
- Add auth.getConfig() method to client SDK
- Add comprehensive tests for getPublicConfig()
- Export auth config types from client

Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/51ee5fa2-1949-4db5-aba9-7011af6f329a

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 14, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
objectstack-demo Error Error Apr 14, 2026 0:18am
spec Ready Ready Preview, Comment Apr 14, 2026 0:18am

Request Review

@hotlong
Copy link
Copy Markdown
Contributor

hotlong commented Apr 14, 2026

@claude[agent] 从主分支更新最新代码

Claude AI and others added 2 commits April 14, 2026 12:09
…to claude/add-third-party-sso-support

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
- Remove unused AuthManager import from auth-plugin.test.ts
- Merge latest changes from main branch

Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/021c7504-f94b-4009-bc35-fb44d8348988

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
@Claude
Copy link
Copy Markdown
Contributor Author

Claude AI commented Apr 14, 2026

@claude[agent] 从主分支更新最新代码

已完成主分支更新和代码修复。更改包括:

  • 移除了未使用的 AuthManager 导入(修复 CodeQL 警告)
  • 成功合并主分支最新代码

Commit: 15ad086

@Claude Claude AI requested a review from hotlong April 14, 2026 12:10
@hotlong hotlong marked this pull request as ready for review April 14, 2026 12:11
Copilot AI review requested due to automatic review settings April 14, 2026 12:11
@hotlong hotlong merged commit 9e6650e into main Apr 14, 2026
9 of 13 checks passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an authentication configuration discovery capability so frontends can render login UI dynamically based on server-side auth settings, spanning spec (protocol), server plugin (endpoint + config extraction), client SDK, and tests.

Changes:

  • Added public auth config discovery schemas/types to the API spec.
  • Implemented AuthManager.getPublicConfig() and exposed it via GET /api/v1/auth/config in the auth plugin.
  • Added client.auth.getConfig() plus new exported SDK types, with tests validating safe output.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/spec/src/api/auth-endpoints.zod.ts Adds Zod schemas/types for the public auth config discovery response payload.
packages/plugins/plugin-auth/src/auth-plugin.ts Registers GET {basePath}/config endpoint returning a standard JSON envelope.
packages/plugins/plugin-auth/src/auth-manager.ts Implements getPublicConfig() to derive non-sensitive, frontend-safe auth configuration.
packages/plugins/plugin-auth/src/auth-manager.test.ts Adds tests for provider filtering, name mapping, defaults, and sensitive data exclusion.
packages/client/src/index.ts Adds auth.getConfig() and exports the new config discovery types from spec.

Comment on lines +160 to +205
// ==========================================
// Auth Configuration Discovery
// ==========================================

/**
* Auth Provider Information (Public)
*
* Public-facing information about an OAuth/social provider.
* Does NOT include sensitive configuration (clientSecret, etc.)
*/
export const AuthProviderInfoSchema = z.object({
id: z.string().describe('Provider ID (e.g., google, github, microsoft)'),
name: z.string().describe('Display name (e.g., Google, GitHub)'),
enabled: z.boolean().describe('Whether this provider is enabled'),
});

/**
* Email/Password Configuration (Public)
*/
export const EmailPasswordConfigPublicSchema = z.object({
enabled: z.boolean().describe('Whether email/password auth is enabled'),
disableSignUp: z.boolean().optional().describe('Whether new user registration is disabled'),
requireEmailVerification: z.boolean().optional().describe('Whether email verification is required'),
});

/**
* Auth Features Configuration (Public)
*/
export const AuthFeaturesConfigSchema = z.object({
twoFactor: z.boolean().default(false).describe('Two-factor authentication enabled'),
passkeys: z.boolean().default(false).describe('Passkey/WebAuthn support enabled'),
magicLink: z.boolean().default(false).describe('Magic link login enabled'),
organization: z.boolean().default(false).describe('Multi-tenant organization support enabled'),
});

/**
* Get Auth Config Response
*
* Returns the public authentication configuration that the frontend
* can use to render appropriate login UI (social provider buttons, etc.)
*/
export const GetAuthConfigResponseSchema = z.object({
emailPassword: EmailPasswordConfigPublicSchema.describe('Email/password authentication config'),
socialProviders: z.array(AuthProviderInfoSchema).describe('Available social/OAuth providers'),
features: AuthFeaturesConfigSchema.describe('Enabled authentication features'),
});
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

The protocol file adds schemas for the auth config discovery response, but it does not add the actual /config endpoint to AuthEndpointPaths / AuthEndpointSchema. This leaves the documented auth endpoint contract incomplete and forces downstream callers (e.g. the client SDK) to hardcode the path. Please add a getConfig (GET) entry and path constant (and any alias/mapping if you want it discoverable via existing helpers).

Copilot uses AI. Check for mistakes.
Comment on lines 234 to +250
const basePath = this.options.basePath || '/api/v1/auth';

// Get raw Hono app to use native wildcard routing
// Type assertion is safe here because we explicitly require Hono server as a dependency
if (!('getRawApp' in httpServer) || typeof (httpServer as any).getRawApp !== 'function') {
ctx.logger.error('HTTP server does not support getRawApp() - wildcard routing requires Hono server');
throw new Error(
'AuthPlugin requires HonoServerPlugin for wildcard routing support. ' +
'Please ensure HonoServerPlugin is loaded before AuthPlugin.'
);
}

const rawApp = (httpServer as any).getRawApp();

// Register auth config endpoint - public endpoint for frontend discovery
rawApp.get(`${basePath}/config`, async (c: any) => {
try {
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

basePath is concatenated directly into route strings. If a caller configures basePath with a trailing slash (e.g. /api/v1/auth/), the registered routes become //config and //*, and may also diverge from the basePath passed into better-auth. Consider normalizing once (e.g. trim a trailing /) before registering routes and before passing it to AuthManager/better-auth.

Copilot uses AI. Check for mistakes.
Comment on lines +256 to +266
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
ctx.logger.error('Auth config error:', err);

return c.json({
success: false,
error: {
code: 'auth_config_error',
message: err.message,
},
}, 500);
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

This is a public endpoint; returning err.message directly can leak internal details to unauthenticated callers. Prefer logging the full error server-side, but returning a generic client-facing message (while keeping a stable code) unless you have a vetted, user-safe error message.

Copilot uses AI. Check for mistakes.
Comment on lines +348 to +371
getPublicConfig() {
// Extract social providers info (without sensitive data)
const socialProviders = [];
if (this.config.socialProviders) {
for (const [id, providerConfig] of Object.entries(this.config.socialProviders)) {
if (providerConfig.enabled !== false) {
// Map provider ID to friendly name
const nameMap: Record<string, string> = {
google: 'Google',
github: 'GitHub',
microsoft: 'Microsoft',
apple: 'Apple',
facebook: 'Facebook',
twitter: 'Twitter',
discord: 'Discord',
gitlab: 'GitLab',
linkedin: 'LinkedIn',
};

socialProviders.push({
id,
name: nameMap[id] || id.charAt(0).toUpperCase() + id.slice(1),
enabled: true,
});
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

nameMap is recreated on every loop iteration. Since this method is called per request, move the provider display-name map outside the loop (or to a module-level constant) to avoid repeated allocations, and consider typing getPublicConfig()'s return type to the spec contract (e.g. GetAuthConfigResponse) so the server implementation can’t drift from the protocol.

Copilot uses AI. Check for mistakes.
Comment on lines +585 to +589
getConfig: async () => {
const route = this.getRoute('auth');
const res = await this.fetch(`${this.baseUrl}${route}/config`);
return this.unwrapResponse(res);
},
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

auth.getConfig() currently returns an untyped unwrapResponse(res) and hardcodes '/config'. Since the PR introduces GetAuthConfigResponse (and ideally an endpoint path constant in the spec), please type this as Promise<GetAuthConfigResponse> and call unwrapResponse<GetAuthConfigResponse>(res) (and use the spec’s path constant once it exists) to keep the SDK aligned with the protocol.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants