BETTER-AUTH. UI
Plugins

OAuth Provider

Add an OAuth 2.1 authorization consent screen to Solid and Zaidan.

The OAuth Provider plugin adds the user-facing consent step for Better Auth's OAuth 2.1 Provider. It identifies the requesting application, explains the requested scopes, shows the signed-in account, and lets the user allow or cancel the request.

It contributes:

  • A copied <OAuthConsent /> view at /auth/consent
  • Public OAuth client metadata loading
  • Allow and cancel actions through oauthConsentOptions
  • Default labels for openid, profile, email, and offline_access
  • Custom metadata for application-specific scopes

Setup

Configure the Better Auth server

Install the provider package:

bun add @better-auth/oauth-provider

Add the JWT and OAuth Provider plugins. Point consentPage at the route that renders the consent view:

src/lib/auth.ts
import { oauthProvider } from "@better-auth/oauth-provider"
import { betterAuth } from "better-auth"
import { jwt } from "better-auth/plugins"

export const auth = betterAuth({
  disabledPaths: ["/token"],
  plugins: [
    jwt(),
    oauthProvider({
      loginPage: "/auth/sign-in",
      consentPage: "/auth/consent"
    })
  ]
})

Generate or migrate your Better Auth schema after enabling the server plugin:

bunx auth@latest migrate

Configure the Solid client

Add oauthProviderClient() to the auth client. It preserves Better Auth's signed authorization query when the user responds:

src/lib/auth-client.ts
import { oauthProviderClient } from "@better-auth/oauth-provider/client"
import { createAuthClient } from "@better-auth-ui/solid"

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

Install the Solid and Zaidan view

bunx shadcn@latest add https://better-auth-ui.com/r/solid/oauth-provider.json

This installs:

  • src/lib/auth/oauth-provider-plugin.ts
  • src/components/auth/oauth-provider/oauth-consent.tsx
  • src/components/auth/user/user-avatar.tsx

Register the UI plugin

src/components/providers.tsx
import { AuthProvider } from "@/components/auth/auth-provider"
import { oauthProviderPlugin } from "@/lib/auth/oauth-provider-plugin"

<AuthProvider
  authClient={authClient}
  plugins={[oauthProviderPlugin()]}
>
  {children}
</AuthProvider>

Include the plugin path in the route that renders <Auth />:

src/routes/auth/$path.tsx
import { viewPaths } from "@better-auth-ui/core"
import { oauthProviderPlugin } from "@/lib/auth/oauth-provider-plugin"

const validAuthPathSegments = new Set([
  ...Object.values(viewPaths.auth),
  oauthProviderPlugin().viewPaths.auth.oauthConsent
])

Keep this path aligned with the server's consentPage.

Custom scopes

Add labels and short descriptions for application-specific scopes:

src/components/providers.tsx
oauthProviderPlugin({
  scopeMetadata: {
    calendar: {
      label: "View your calendar",
      description: "Read your calendar events and availability."
    }
  },
  localization: {
    allow: "Authorize"
  }
})

Unknown scopes remain visible using their raw scope value.

This first consent view accepts or denies the complete requested scope set. It does not render per-scope controls. Omitting scope from the consent mutation tells Better Auth to accept the scopes from the original signed request.

Keep the authorization query string on the consent page. Do not strip it, rebuild it from redirect_uri, or navigate to the requested redirect yourself. oauthProviderClient() forwards the signed query to Better Auth, and Better Auth validates and completes the redirect.

The public client endpoint requires a signed-in session. Direct visits with missing request data, no session, or an unknown client render an invalid-request state.

Login reuses the existing signIn view and resumes automatically when Better Auth creates the session. Optional prompt=create, account-selection, and post-login screens require application-specific continuation logic and are not registered by this base plugin.

Solid APIs

Use usePublicOAuthClient to load application metadata and oauthConsentOptions to submit the user's decision.

Last updated on

On this page