@bettercone/ui
ComponentsEnterprise SSO

OIDC Provider Card

OAuth2/OIDC provider client application management with RFC7591 compliance

Overview

Manage OAuth2/OIDC client applications for your authorization server. Supports RFC7591 dynamic client registration with multiple client types (web, SPA, native, m2m).

Features

  • RFC7591 Compliant - OAuth2 Dynamic Client Registration Protocol
  • Auto-Fetch Mode - Fetches clients from Better Auth OIDC Provider plugin
  • Client Types - Web, SPA, native, machine-to-machine
  • Security - One-time secret display, password masking
  • Metadata - Application URLs, logos, terms, privacy policies

Installation

npm install @bettercone/ui

Usage

import { OIDCProviderCard } from "@bettercone/ui"

export default function ProviderDashboard() {
  return <OIDCProviderCard allowRegistration={true} />
}
import { OIDCProviderCard } from "@bettercone/ui"

const clients = [
  {
    id: "client_1",
    clientId: "web_app_abc123",
    name: "My Web App",
    type: "web",
    redirectURLs: ["https://myapp.com/callback"],
    disabled: false,
    createdAt: new Date(),
    metadata: JSON.stringify({
      client_uri: "https://myapp.com",
      grant_types: ["authorization_code", "refresh_token"]
    })
  }
]

export default function Dashboard() {
  return (
    <OIDCProviderCard
      clients={clients}
      onClientRegistered={(client) => {
        console.log("Registered:", client)
      }}
    />
  )
}
<OIDCProviderCard
  clients={myClients}
  allowRegistration={false}
/>

Client Types

TypeDescriptionAuth Method
webServer-side web applicationsclient_secret_basic
spaSingle-page applicationsnone (PKCE)
nativeMobile/desktop appsnone (PKCE)
m2mService accountsclient_secret_post

Props

interface OIDCProviderCardProps {
  clients?: OAuth2Client[]
  authClient?: AnyAuthClient
  onClientRegistered?: (client: OAuth2Client) => void
  onClientUpdated?: (client: OAuth2Client) => void
  onClientDeleted?: (clientId: string) => void
  onError?: (error: Error) => void
  showActiveTokens?: boolean
  showTrustedClients?: boolean
  allowRegistration?: boolean
  className?: string
  classNames?: {
    base?: string
    header?: string
    clientCard?: string
  }
  localization?: Partial<AuthLocalization>
}

OAuth2Client Schema

Aligned with Better Auth oauthApplication table schema.

interface OAuth2Client {
  id: string
  clientId: string
  clientSecret?: string
  name: string
  redirectURLs: string[]  // CSV in DB
  type: string  // 'web' | 'spa' | 'native' | 'm2m'
  disabled: boolean
  userId?: string
  metadata?: string | Record<string, any>  // JSON string in DB
  createdAt: Date
  updatedAt?: Date
}

Registration Example

<OIDCProviderCard
  onClientRegistered={(client) => {
    // ⚠️ Secret shown ONLY ONCE - save immediately
    if (client.clientSecret) {
      saveToVault(client.clientSecret)
    }
  }}
  onError={(error) => {
    if (error.message.includes("duplicate")) {
      toast.error("Client name already exists")
    }
  }}
/>

Metadata Fields

Component parses metadata JSON to display:

  • client_uri - Application homepage (Website button)
  • logo_uri - Application logo
  • tos_uri - Terms of service (Terms button)
  • policy_uri - Privacy policy (Privacy button)
  • contacts - Developer emails
  • grant_types - Allowed OAuth2 grant types
  • response_types - Allowed response types
  • scope - Default scopes

Better Auth Setup

Server

import { betterAuth } from "better-auth"
import { oidcProvider } from "better-auth/plugins"

export const auth = betterAuth({
  plugins: [
    oidcProvider({
      allowDynamicClientRegistration: true,
    }),
  ],
})

Client

import { createAuthClient } from "better-auth/react"
import { oidcClient } from "better-auth/client/plugins"

export const authClient = createAuthClient({
  plugins: [oidcClient()],
})

Security Notes

Important:

  • Client secrets shown only once during registration
  • Store secrets in environment variables or secret managers
  • Use PKCE for public clients (SPAs, mobile apps)
  • Rotate secrets regularly for web applications
  • Deleting a client revokes access for all users

Common Patterns

With Organization Scoping

const orgId = useCurrentOrg()

<OIDCProviderCard
  clients={clients.filter(c => c.userId === orgId)}
/>

Custom Styling

<OIDCProviderCard
  className="max-w-4xl mx-auto"
  classNames={{
    header: "bg-gradient-to-r from-blue-500 to-purple-600",
    clientCard: "border-2 border-blue-200",
  }}
/>

Resources