better-auth-ui
componentactive-sessionssource: ok · hit
Active Sessions
Display and manage all active sessions for the current user with revoke controls. Shows each session's browser, OS, and creation time.
npx shadcn add "https://better-auth-ui.com/r/{style}/active-sessions.json"free
No selection tags yet — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lighticonslucide-react
Registry dependencies
alertalert-dialogbadgebuttoncarditemskeletonsonnerspinner
Files4 · source cached 10/9/2026, 11:54:50 PM
examples/start-shadcn-example/src/components/auth/reauthentication.tsx
"use client"
import {
getReauthenticationSignInURL,
isReauthenticationSignInURL
} from "@better-auth-ui/core"
import { useAuth, useSignOut } from "@better-auth-ui/react"
import { useSyncExternalStore } from "react"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { Button } from "@/components/ui/button"
import { Spinner } from "@/components/ui/spinner"
import { cn } from "cn"
const subscribeToLocation = () => () => undefined
function useIsReauthenticationSignIn() {
return useSyncExternalStore(
subscribeToLocation,
() => isReauthenticationSignInURL(new URL(window.location.href)),
() => false
)
}
export type ReauthenticationActionProps = {
className?: string
showTitle?: boolean
}
export function ReauthenticationAction({
className,
showTitle = true
}: ReauthenticationActionProps) {
const auth = useAuth()
const signOut = useSignOut(auth.authClient)
const handleReauthentication = () => {
const signInURL = getReauthenticationSignInURL(
new URL(window.location.href),
`${auth.basePaths.auth}/${auth.viewPaths.auth.signIn}`
)
signOut.mutate(undefined, {
onSuccess: () => auth.navigate({ to: signInURL })
})
}
return (
<div className={cn("flex flex-col items-start gap-3 p-4", className)}>
<div className="flex flex-col gap-1">
{showTitle ? (
<h3 className="text-sm font-medium">
{auth.localization.settings.reauthenticationTitle}
</h3>
) : null}
<p className="text-sm text-muted-foreground">
{auth.localization.settings.reauthenticationDescription}
</p>
</div>
<Button
disabled={signOut.isPending}
onClick={handleReauthentication}
size="sm"
>
{signOut.isPending ? <Spinner data-icon="inline-start" /> : null}
{auth.localization.settings.reauthenticationAction}
</Button>
</div>
)
}
export function ReauthenticationNotice() {
const auth = useAuth()
const isReauthenticationSignIn = useIsReauthenticationSignIn()
if (!isReauthenticationSignIn) return null
return (
<Alert className="mx-4 w-auto group-data-[size=sm]/card:mx-3">
<AlertTitle>
{auth.localization.settings.reauthenticationTitle}
</AlertTitle>
<AlertDescription>
{auth.localization.settings.reauthenticationDescription}
</AlertDescription>
</Alert>
)
}
examples/start-shadcn-example/src/components/auth/settings/security/active-session.tsx
"use client"
import { useAuth, useRevokeSession, useSession } from "@better-auth-ui/react"
import type { Session } from "better-auth"
import Bowser from "bowser"
import { LogOut, Monitor, Smartphone, X } from "lucide-react"
import { toast } from "sonner"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle
} from "@/components/ui/item"
import { Spinner } from "@/components/ui/spinner"
function timeAgo(date: Date) {
const seconds = Math.floor((Date.now() - date.getTime()) / 1000)
const rtf = new Intl.RelativeTimeFormat(undefined, { numeric: "auto" })
const UNITS: [Intl.RelativeTimeFormatUnit, number][] = [
["year", 31536000],
["month", 2592000],
["week", 604800],
["day", 86400],
["hour", 3600],
["minute", 60],
["second", 1]
]
for (const [unit, threshold] of UNITS) {
if (seconds >= threshold) {
return rtf.format(-Math.floor(seconds / threshold), unit)
}
}
return rtf.format(0, "second")
}
export type ActiveSessionProps = {
activeSession: Session
}
/**
* Render a single active session row with device info and revoke control.
*
* Shows the session's browser, OS, and creation time. The current session is marked
* and navigates to sign-out on click, while other sessions can be revoked individually.
*
* @param session - The session object containing id, token, userAgent, ipAddress, and createdAt
* @returns A JSX element containing the active session row
*/
export function ActiveSession({ activeSession }: ActiveSessionProps) {
const { authClient, basePaths, localization, viewPaths, navigate } = useAuth()
const { data: session } = useSession(authClient, { refetchOnMount: false })
const { mutate: revokeSession, isPending: isRevoking } = useRevokeSession(
authClient,
{
onSuccess: () => toast.success(localization.settings.revokeSessionSuccess)
}
)
const isCurrentSession = activeSession.token === session?.session.token
const ua = activeSession.userAgent
? Bowser.parse(activeSession.userAgent)
: undefined
const isMobile =
ua?.platform.type === "mobile" || ua?.platform.type === "tablet"
return (
<Item>
<ItemMedia variant="icon">
{isMobile ? <Smartphone /> : <Monitor />}
</ItemMedia>
<ItemContent>
<ItemTitle>
{ua?.browser.name || "Unknown Browser"}
{ua?.os.name ? `, ${ua.os.name}` : ""}
</ItemTitle>
{isCurrentSession ? (
<Badge variant="secondary">
{localization.settings.currentSession}
</Badge>
) : (
activeSession.createdAt && (
<ItemDescription className="capitalize">
{timeAgo(activeSession.createdAt)}
</ItemDescription>
)
)}
</ItemContent>
<ItemActions>
<Button
variant="outline"
size="sm"
onClick={() =>
isCurrentSession
? navigate({
to: `${basePaths.auth}/${viewPaths.auth.signOut}`
})
: revokeSession(activeSession)
}
disabled={isRevoking}
aria-label={
isCurrentSession
? localization.auth.signOut
: localization.settings.revokeSession
}
>
{isRevoking ? <Spinner /> : isCurrentSession ? <LogOut /> : <X />}
{isCurrentSession
? localization.auth.signOut
: localization.settings.revoke}
</Button>
</ItemActions>
</Item>
)
}
examples/start-shadcn-example/src/components/auth/settings/security/active-sessions.tsx
"use client"
import { isReauthenticationRequiredError } from "@better-auth-ui/core"
import { useAuth, useListSessions, useSession } from "@better-auth-ui/react"
import { Fragment } from "react"
import { Card, CardContent } from "@/components/ui/card"
import {
Item,
ItemContent,
ItemGroup,
ItemMedia,
ItemSeparator
} from "@/components/ui/item"
import { Skeleton } from "@/components/ui/skeleton"
import { cn } from "cn"
import { ReauthenticationAction } from "../../reauthentication"
import { ActiveSession } from "./active-session"
import { SessionActions } from "./session-actions"
export type ActiveSessionsProps = {
className?: string
}
/**
* Render a card listing all active sessions for the current user with revoke controls.
*
* Shows each session's browser, OS, IP address, and creation time. The current session is marked
* and navigates to sign-out on click, while other sessions can be revoked individually.
*
* @returns A JSX element containing the sessions card
*/
export function ActiveSessions({ className }: ActiveSessionsProps) {
const { authClient, localization } = useAuth()
const { data: session } = useSession(authClient)
const sessionsQuery = useListSessions(authClient, {
meta: { errorPresentation: "inline" }
})
const { data: sessions, error, isPending } = sessionsQuery
const activeSessions = [...(sessions ?? [])].sort((activeSession) =>
activeSession.id === session?.session.id ? -1 : 1
)
return (
<div>
<h2 className="text-sm font-semibold mb-3">
{localization.settings.activeSessions}
</h2>
<Card className={cn("gap-0 p-0", className)}>
<CardContent className="p-0">
{isReauthenticationRequiredError(error) ? (
<ReauthenticationAction />
) : error ? (
<div className="p-4 text-destructive text-sm">{error.message}</div>
) : isPending ? (
<SessionRowSkeleton />
) : (
<ItemGroup className="gap-0!">
{activeSessions?.map((activeSession, index) => (
<Fragment key={activeSession.id}>
{index > 0 && <ItemSeparator className="my-0!" />}
<ActiveSession activeSession={activeSession} />
</Fragment>
))}
</ItemGroup>
)}
</CardContent>
{!isPending && !error && (
<SessionActions
hasOtherSessions={activeSessions.some(
(activeSession) => activeSession.id !== session?.session.id
)}
/>
)}
</Card>
</div>
)
}
function SessionRowSkeleton() {
return (
<Item>
<ItemMedia>
<Skeleton className="size-10 rounded-md" />
</ItemMedia>
<ItemContent>
<Skeleton className="h-4 w-20" />
<Skeleton className="h-3 w-32" />
</ItemContent>
</Item>
)
}
examples/start-shadcn-example/src/components/auth/settings/security/session-actions.tsx
"use client"
import {
useAuth,
useRevokeOtherSessions,
useRevokeSessions
} from "@better-auth-ui/react"
import { LogOut } from "lucide-react"
import { useState } from "react"
import { toast } from "sonner"
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogTitle
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
import { Spinner } from "@/components/ui/spinner"
type PendingAction = "other" | "all"
export function SessionActions(props: { hasOtherSessions: boolean }) {
const { authClient, basePaths, localization, navigate, viewPaths } = useAuth()
const [action, setAction] = useState<PendingAction | null>(null)
const revokeOtherSessions = useRevokeOtherSessions(authClient, {
onSuccess: () => {
toast.success(localization.settings.signOutOtherDevicesSuccess)
setAction(null)
}
})
const revokeSessions = useRevokeSessions(authClient, {
onSuccess: () =>
navigate({
to: `${basePaths.auth}/${viewPaths.auth.signIn}`,
replace: true
})
})
const isPending = revokeOtherSessions.isPending || revokeSessions.isPending
const isEverywhere = action === "all"
return (
<>
<div className="flex flex-wrap justify-end gap-2 border-t p-4">
<Button
disabled={!props.hasOtherSessions || isPending}
onClick={() => setAction("other")}
size="sm"
type="button"
variant="outline"
>
{localization.settings.signOutOtherDevices}
</Button>
<Button
disabled={isPending}
onClick={() => setAction("all")}
size="sm"
type="button"
variant="destructive"
>
{localization.settings.signOutEverywhere}
</Button>
</div>
<AlertDialog
open={action !== null}
onOpenChange={(open) => !open && !isPending && setAction(null)}
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogMedia className="bg-destructive/10 text-destructive dark:bg-destructive/20 dark:text-destructive">
<LogOut />
</AlertDialogMedia>
<AlertDialogTitle>
{isEverywhere
? localization.settings.signOutEverywhere
: localization.settings.signOutOtherDevices}
</AlertDialogTitle>
<AlertDialogDescription>
{isEverywhere
? localization.settings.signOutEverywhereDescription
: localization.settings.signOutOtherDevicesDescription}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={isPending}>
{localization.settings.cancel}
</AlertDialogCancel>
<Button
disabled={isPending}
onClick={() =>
isEverywhere
? revokeSessions.mutate()
: revokeOtherSessions.mutate()
}
type="button"
variant={isEverywhere ? "destructive" : "default"}
>
{isPending && <Spinner />}
{isEverywhere
? localization.settings.signOutEverywhere
: localization.settings.signOutOtherDevices}
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
)
}