new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Better Auth · Plugins · all subjects

plugin architecture & configuration

149 notes in this subject, read out of this brain and free to use. This is page 1 of 3.

i18n session-based detection with user locale field

To detect locale from session, store the user's locale preference in their profile using additionalFields in the auth config, add 'session' to the detection array, and optionally configure the field name with userLocaleField option (default is 'locale').

i18n overriding specific messages example

Example code: import { betterAuth } from 'better-auth'; import { i18n, locales } from '@better-auth/i18n'; export const auth = betterAuth({ plugins: [ i18n({ translations: { ...locales, fr: { ...locales.fr, USER_NOT_FOUND: 'Membre introuvable', }, }, }), ], })

i18n error response format

When an error occurs and a translation is available, the response includes three fields: code (the error code), message (the translated message in the detected locale), and originalMessage (the original English message).

i18n custom detection callback

For advanced locale detection, provide a custom getLocale function to the plugin that receives the full endpoint context (ctx: GenericEndpointContext) and should return the locale code or null. Add 'callback' to the detection array. The callback can use query parameters, custom headers, or other logic. Note that ctx.request could be undefined for non-HTTP calls.

i18n using subset of locales example

Example code: import { betterAuth } from 'better-auth'; import { i18n, locales } from '@better-auth/i18n'; export const auth = betterAuth({ plugins: [ i18n({ translations: { en: locales.en, fr: locales.fr, }, }), ], })

i18n plugin purpose

The i18n plugin allows you to translate error messages returned by Better Auth based on the user's locale. It supports multiple locale detection strategies including HTTP headers, cookies, session data, and custom callbacks.

i18n plugin installation

Install the i18n plugin using the package @better-auth/i18n

i18n custom detection callback example

Example code: i18n({ translations: { /* ... */ }, detection: ['callback', 'header'], getLocale: (ctx) => { if (!ctx.request) return null; const url = new URL(ctx.request.url); return url.searchParams.get('lang'); }, })

i18n plugin options reference

The i18n plugin accepts the following options: translations (Record<string, Record<string, string>>, required) - dictionary of translations keyed by locale code; defaultLocale (string, default 'en') - fallback locale when no locale detected or detected locale not in translations; detection (Array<'header'|'cookie'|'session'|'callback'>, default ['header']) - detection strategies in priority order; localeCookie (string, default 'locale') - cookie name for cookie-based detection; userLocaleField (string, default 'locale') - user object field name for session-based detection; getLocale ((ctx: GenericEndpointContext) => string | null | Promise<string | null>) - custom function for callback-based detection returning locale code or null.

i18n Accept-Language header parsing

The i18n plugin automatically parses the Accept-Language HTTP header including quality values. For example, 'Accept-Language: fr-CA, fr;q=0.9, en;q=0.8' would first try fr-CA (mapped to fr), then fr, then en.

i18n locale detection strategies

The i18n plugin supports four locale detection strategies that can be configured in priority order: header (uses Accept-Language HTTP header, default), cookie (reads locale from a cookie), session (reads locale from authenticated user's stored preference), and callback (uses a custom function to determine locale).

Built-in locales supported by i18n

The i18n plugin ships with ready-to-use translations for 22 languages: ar (Arabic), bn (Bengali), de (German), en (English), es (Spanish), fa (Persian/Farsi), fr (French), hi (Hindi), id (Indonesian), it (Italian), ja (Japanese), ko (Korean), nl (Dutch), pl (Polish), pt (Portuguese), ru (Russian), sv (Swedish), th (Thai), tr (Turkish), uk (Ukrainian), vi (Vietnamese), zh (Chinese Simplified). Import locales from @better-auth/i18n to use them.

i18n plugin basic setup

To add the i18n plugin to Better Auth, import it from @better-auth/i18n and add it to the plugins array in betterAuth config, passing a configuration object with a translations property containing locale codes mapped to translation dictionaries.

i18n adding custom locale example

Example code: import { betterAuth } from 'better-auth'; import { i18n, locales } from '@better-auth/i18n'; import type { TranslationDictionary } from '@better-auth/i18n'; const myLocale: TranslationDictionary = { USER_NOT_FOUND: '...', INVALID_EMAIL_OR_PASSWORD: '...', }; export const auth = betterAuth({ plugins: [ i18n({ translations: { ...locales, xx: myLocale, }, }), ], })

i18n cookie-based detection configuration

To use cookie-based detection, add 'cookie' to the detection array and optionally configure the cookie name with the localeCookie option (default is 'locale').

i18n fallback behavior

If a translation is not found for a specific error code in the detected locale, the built-in English message is used. If the detected locale is not in the translations dictionary, the plugin falls back to defaultLocale. If no locale can be detected from any strategy, the plugin falls back to defaultLocale (defaults to 'en'). Non-error responses are never modified by the plugin.

i18n English default behavior

Better Auth already provides English error messages by default, so you only need to provide translations for other languages when configuring the i18n plugin.

Captcha plugin for auth flow verification

Better Auth includes a Captcha plugin that provides captcha verification for auth flows.

OAuth 2.1 Provider plugin to turn auth server into provider

Better Auth includes an OAuth 2.1 Provider plugin that allows turning your auth server into an OAuth 2.1 provider.

i18n plugin for error message internationalization

Better Auth includes an i18n plugin that provides internationalization for error messages.

One Tap plugin for Google One Tap sign-in

Better Auth includes a One Tap plugin that provides Google One Tap sign-in functionality.

Bearer plugin for bearer token authentication

Better Auth includes a Bearer plugin that provides bearer token authentication for API requests.

One-Time Token plugin for single-use tokens

Better Auth includes a One-Time Token plugin that enables generating and verifying single-use tokens.

Last Login Method plugin for tracking authentication methods

Better Auth includes a Last Login Method plugin that tracks the last authentication method used.

OAuth Proxy plugin for cross-domain flows

Better Auth includes an OAuth Proxy plugin that provides OAuth proxy functionality for cross-domain flows.

SCIM plugin for identity management and directory sync

Better Auth includes a SCIM plugin that provides cross-domain identity management and directory sync.

MCP plugin for MCP provider authentication

Better Auth includes an MCP plugin that provides MCP provider authentication.

Anonymous plugin for guest sessions

Better Auth includes an Anonymous plugin that provides anonymous/guest session support.

Phone Number plugin for phone authentication

Better Auth includes a Phone Number plugin that provides phone number authentication.

Have I Been Pwned plugin for password breach checking

Better Auth includes a Have I Been Pwned plugin that checks passwords against known breaches.

Better Auth ships with 50+ built-in plugins

Better Auth ships with 50+ plugins that extend the framework with additional auth features, payment integrations, security tools, and more.

Community plugins available for Better Auth

Better Auth community has built additional plugins beyond the 50+ built-in plugins. These community plugins can be found in the community plugins documentation.

SSO plugin for SAML 2.0 single sign-on

Better Auth includes an SSO plugin that provides Single Sign-On with SAML 2.0.

Dub plugin for lead tracking and OAuth linking

Better Auth includes a Dub plugin that provides lead tracking using Dub links and OAuth linking.

Test Utils plugin for integration and E2E testing

Better Auth includes a Test Utils plugin that provides testing utilities for integration and E2E testing.

Open API plugin for OpenAPI reference generation

Better Auth includes an Open API plugin that provides OpenAPI reference generation.

MCP DPoP replay protection

DPoP replay protection uses the auth instance's database adapter by default so it works across server instances. Pass dpop.replayStore option only when a different shared store is needed.

createMcpProtectedRequestHandler for remote MCP server

When the MCP resource server runs separately from the authorization server or uses dynamic baseURL, use createMcpProtectedRequestHandler instead of requireMcpAuth. Pass explicit verification options: issuer, audience, and jwksUrl. It accepts requiredScopes and challengeScopes options similar to requireMcpAuth and returns RFC 9728 WWW-Authenticate response for unauthenticated requests.

MCP refreshTokenReuseInterval default

For every client configured through mcp(), the plugin defaults refreshTokenReuseInterval to 30 seconds. This lets a client retry a refresh with the old token and receive the same rotated token response when another request already consumed that refresh token. Set refreshTokenReuseInterval: 0 on mcp() to disable the overlap window.

MCP plugin OAuth provider options

Common OAuth provider options for mcp(): scopes (string[], default: ['openid', 'profile', 'email', 'offline_access']) - Scopes advertised by authorization server; resources (Array<string | OAuthResourceInput>) - Protected resources authorization server can issue access tokens for; accessTokenExpiresIn (number, default: 3600) - Lifetime of access tokens in seconds; idTokenExpiresIn (number, default: 36000) - Lifetime of ID tokens in seconds; refreshTokenExpiresIn (number, default: 2592000) - Lifetime of refresh tokens in seconds; refreshTokenReuseInterval (number, default: 30 for MCP) - Seconds a rotated refresh token can be reused to receive same token response; codeExpiresIn (number, default: 600) - Lifetime of authorization codes in seconds.

requireMcpAuth configuration options

requireMcpAuth accepts options: resource (protected resource identifier, must match mcp({ resource })), issuer (override when jwt.issuer is custom), jwksUrl (override JWKS URL when authorization server runs separately), challengeScopes (advertised in the 401 challenge), and requiredScopes (enforced against the token's scope claim).

requireMcpAuth scope enforcement

To require scopes in requireMcpAuth, pass requiredScopes array. A token missing any required scopes is rejected with a 403 and an insufficient_scope challenge naming every missing scope, so the client can re-authorize for all of them at once. When required scopes depend on the request, throw createInsufficientScopeError from the handler with the specific scopes needed.

requireMcpAuth Next.js route example

Example Next.js MCP route using requireMcpAuth: Import auth and requireMcpAuth, create mcpServerHandler with createMcpHandler setting legacy: 'reject', register tools with server.registerTool(), wrap handler with requireMcpAuth(auth, (request) => mcpServerHandler.fetch(request), { resource: 'https://api.example.com/mcp' }), and export POST.

requireMcpAuth wrapper usage

Wrap the MCP handler with requireMcpAuth to protect MCP routes. The wrapper reads the Authorization header, verifies access tokens against the authorization server's JWKS, and checks the signature, issuer, audience, and expiry. It enforces RFC 9449 DPoP when an access token is DPoP-bound. Unauthenticated requests receive a JSON-RPC 401 with the RFC 9728 WWW-Authenticate header.

MCP protocol version handling

MCP 2026-07-28 uses a stateless request and response model: every client JSON-RPC request or notification is an independent HTTP POST, and the server does not maintain a protocol-level session between requests. Use the official MCP TypeScript SDK v2 with legacy: 'reject' to accept only the MCP 2026-07-28 protocol.

MCP plugin DCR fallback configuration

To support older clients that require DCR, enable both provider controls: mcp({ loginPage: '/sign-in', consentPage: '/consent', resource: 'https://api.example.com/mcp', allowDynamicClientRegistration: true, allowUnauthenticatedClientRegistration: true }). The registration endpoint is absent from discovery unless DCR is enabled.

MCP plugin default client-registration resource

The mcp() plugin registers the resource identifier as a default client-registration resource. A dynamically registered client is linked to the MCP resource even when its registration request omits the non-standard resources field. Any clientRegistrationDefaultResources provided are preserved, and the MCP resource is appended once.

MCP resource parameter requirements

The resource parameter must be an HTTPS URL with no query, fragment, or credentials; HTTP is accepted only on loopback hosts for local development. A resource whose URL requires a query component cannot use mcp(), requireMcpAuth, or createMcpProtectedRequestHandler.

MCP protected resource metadata RFC 9728

The /.well-known/oauth-protected-resource document is served automatically at the well-known root and resource-path-inserted alias. It tells MCP clients which authorization server protects the resource, which scopes it supports, which resource identifier their access tokens must be bound to, and which DPoP proof algorithms are supported.

MCP plugin with device authorization

To add device authorization for a CLI application, compose mcp() with oauthDeviceAuthorization({ verificationUri: '/device' }). The mcp() plugin is already the OAuth provider, so no separate oauthProvider() is needed. MCP clients continue using discovery and authorization code with PKCE, while the registered public CLI can request tokens through /device/code and poll /oauth2/token.

MCP discovery endpoints

Discovery follows OAuth 2.0 Authorization Server Metadata (RFC 8414) and Protected Resource Metadata (RFC 9728). Well-known URLs are derived from the issuer. Discovery advertises client_id_metadata_document_supported only when cimd() is installed, and registration_endpoint only when DCR is enabled.

MCP plugin endpoints

The mcp() plugin serves standard OAuth 2.1 endpoints under /oauth2/*: Authorization at /oauth2/authorize, Token at /oauth2/token, Dynamic registration at /oauth2/register (when explicitly enabled), and UserInfo at /oauth2/userinfo.

MCP plugin schema tables

The MCP plugin uses the same schema as the OAuth Provider plugin: oauthClient, oauthAccessToken, oauthRefreshToken, oauthConsent, and oauthClientAssertion tables.

MCP plugin configuration options table

MCP-specific required configuration options: loginPage (string, required) - Path to login page where users are redirected for authentication; consentPage (string, required) - Path to consent page where users grant requested scopes; resource (string, required) - Protected resource identifier (RFC 8707/RFC 9728) that access tokens are bound to, advertised in protected resource metadata and used as expected token audience.

MCP plugin do not register oauthProvider separately

When using mcp(), do not also register a separate oauthProvider() plugin in the same app. The mcp() plugin is the OAuth provider.

MCP plugin configuration example

The mcp() configuration requires loginPage, consentPage, and resource parameters. Example: mcp({ loginPage: '/sign-in', consentPage: '/consent', resource: 'https://api.example.com/mcp' }). The CIMD plugin is configured separately with fetchClientMetadataResource and metadataProfile: 'mcp-2026-07-28'.

MCP plugin installation packages

Install @better-auth/mcp, @better-auth/cimd, @modelcontextprotocol/server, and zod.

createMcpProtectedRequestHandler example

Example using createMcpProtectedRequestHandler: import { createMcpProtectedRequestHandler } from '@better-auth/mcp'; const handler = createMcpProtectedRequestHandler({ issuer: 'https://auth.example.com', audience: 'https://api.example.com/mcp', jwksUrl: 'https://auth.example.com/api/auth/jwks' }, async (request, accessTokenClaims) => { return new Response(JSON.stringify({ jsonrpc: '2.0', result: { sub: accessTokenClaims.sub }, id: 1 })) });

MCP plugin requires JWT plugin

The JWT plugin is required when using the MCP plugin. The JWT plugin provides the stable signing key used for ID tokens and access tokens, and exposes the /jwks endpoint that resource servers use to verify tokens.

MCP plugin overview

The MCP plugin turns a Better Auth server into an OAuth 2.1 provider for Model Context Protocol clients. It is built on the OAuth 2.1 Provider plugin and enables MCP clients to discover endpoints and obtain resource-bound access tokens through standard OAuth flows. The mcp() function configures the OAuth provider with MCP resource binding and serves RFC 9728 protected resource metadata.

Give your agent this brain