BETTER-AUTH. UI
Plugins

Device Authorization

Approve CLI, TV, and limited-input device sign-ins from an authenticated browser.

The device-authorization plugin adds the browser half of Better Auth's device authorization flow. A user enters the short code shown by a CLI, TV, or another limited-input device, signs in if needed, then approves or denies access.

It contributes:

  • A <DeviceAuthorization /> view at /auth/device
  • Code verification with the user_code query parameter prefilled when present
  • Sign-in redirection that preserves the pending code
  • Approve and deny confirmation states
  • useVerifyDeviceCode, useApproveDevice, and useDenyDevice mutation hooks

The requesting device remains responsible for calling Better Auth's /device/code endpoint and polling /device/token.

Setup

Install the Better Auth plugin

Add the Device Authorization plugin to your Better Auth server. Set verificationUri to the public route that renders the UI view:

lib/auth.ts
import { betterAuth } from "better-auth"
import { deviceAuthorization } from "better-auth/plugins"

export const auth = betterAuth({
  // ...
  plugins: [
    deviceAuthorization({ 
      verificationUri: "/auth/device"
    }) 
  ]
})

Generate or migrate the Better Auth schema after enabling the server plugin. It adds the deviceCode model used to track pending requests.

Install the matching client plugin

Add deviceAuthorizationClient() to your browser auth client:

lib/auth-client.ts
import { createAuthClient } from "better-auth/react"
import { deviceAuthorizationClient } from "better-auth/client/plugins"

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

Register the UI plugin

Pass deviceAuthorizationPlugin() to <AuthProvider>:

components/providers.tsx
import { AuthProvider } from "@better-auth-ui/heroui"
import { deviceAuthorizationPlugin } from "@better-auth-ui/heroui/plugins"

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

Allow the new auth path

Include the plugin's path in the route that renders <Auth />. Keep this path aligned with the server's verificationUri:

routes/auth/$path.tsx
import { viewPaths } from "@better-auth-ui/core"
import { Auth } from "@better-auth-ui/heroui"
import { deviceAuthorizationPlugin } from "@better-auth-ui/heroui/plugins"
import { createFileRoute, notFound } from "@tanstack/react-router"

const validAuthPathSegments = new Set([
  ...Object.values(viewPaths.auth),
  deviceAuthorizationPlugin().viewPaths.auth.deviceAuthorization
])

export const Route = createFileRoute("/auth/$path")({
  beforeLoad({ params: { path } }) {
    if (!validAuthPathSegments.has(path)) {
      throw notFound()
    }
  },
  component: AuthPage
})

function AuthPage() {
  const { path } = Route.useParams()
  return <Auth path={path} />
}

Components

<DeviceAuthorization />

Device Authorization

Enter the code displayed on your device.

Enter any eight-character code in the preview to exercise the approval flow.

The component is rendered automatically at /auth/device when the plugin is registered. You can also use it directly:

import { DeviceAuthorization } from "@better-auth-ui/heroui/plugins"

<DeviceAuthorization />

Prop

Type

Options

deviceAuthorizationPlugin({
  // Override the URL segment. Default: "device"
  path: "activate",
  // Match Better Auth's server-side userCodeLength. Default: 8
  userCodeLength: 8,
  localization: {
    approveDevice: "Allow this device?"
  }
})

Prop

Type

Keep userCodeLength equal to the value passed to Better Auth's server plugin. A mismatched length prevents valid codes from being submitted.

Localization

Prop

Type

Read these values from useAuthPlugin(deviceAuthorizationPlugin).localization inside custom plugin views.

Sessions and revocation

The device token creates an ordinary Better Auth session after approval. There is no separate device registry in the device-authorization plugin. Use <ActiveSessions /> to list sessions and revoke access for an approved device.

Last updated on

On this page