go-auth
GuidesClient

Sessions

sessionsApi — a named-method wrapper around the list/revoke session endpoints, built on the apiRequest helper.

Sessions client

sessionsApi wraps the list/revoke/refresh endpoints from the Sessions guide, built on the apiRequest helper.

UI components

All UI examples on this page use shadcn/ui with the default base variant. Only layout and styling change between projects — the component API stays the same. Install with npx shadcn@latest add button card badge alert checkbox.

Looking for me()?

GET /auth/me lives on authApi.me() on the Authentication client.

The wrapper

// lib/api/sessions.ts
import { apiRequest } from "./client";

const API_BASE = "/api";

export const sessionsApi = {
  list: (offset = 0, limit = 20) =>
    apiRequest(API_BASE, "GET", `/auth/sessions?offset=${offset}&limit=${limit}`),

  listAll: () => apiRequest(API_BASE, "GET", "/auth/sessions/all"),

  refresh: () => apiRequest(API_BASE, "POST", "/auth/refresh"),

  revoke: (sessionId: string) =>
    apiRequest(API_BASE, "DELETE", `/auth/sessions/${sessionId}`),

  revokeMany: (sessionIds: string[]) =>
    apiRequest(API_BASE, "POST", "/auth/sessions/revoke", { sessionIds }),

  revokeAllExceptCurrent: () => apiRequest(API_BASE, "DELETE", "/auth/sessions"),
};

Each method returns the matching endpoint response without reshaping errors. For full request/response shapes and error codes, see the Sessions guide.

Error handling

// lib/session-errors.ts
const errorMessages: Record<string, string> = {
  session_expired: "Your session has expired, please log in again",
  unauthorized: "Not authenticated",
  session_not_found: "Session not found",
  invalid_input: "Invalid session IDs",
  invalid_refresh: "Refresh token is invalid or expired",
};

export function getSessionErrorMessage(err: { error?: string; message?: string }): string {
  if (err.error && err.error in errorMessages) {
    return errorMessages[err.error];
  }
  return err.message ?? "Something went wrong";
}

List sessions (paginated)

GET /auth/sessions

Session list

// components/session-list.tsx
"use client";

import { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { sessionsApi } from "../lib/api/sessions";
import { getSessionErrorMessage } from "../lib/session-errors";

interface Session {
  id: string;
  ipAddress?: string;
  userAgent?: string;
  parsedUA?: { browser: string; os: string; deviceType: string };
  isRevoked: boolean;
  expiresAt: string;
  createdAt: string;
  lastActiveAt?: string;
}

export function SessionList() {
  const [sessions, setSessions] = useState<Session[]>([]);
  const [currentSessionId, setCurrentSessionId] = useState("");
  const [total, setTotal] = useState(0);
  const [offset, setOffset] = useState(0);
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(true);
  const limit = 10;

  useEffect(() => { loadSessions(); }, [offset]);

  async function loadSessions() {
    setLoading(true);
    setError(null);
    try {
      const result = await sessionsApi.list(offset, limit);
      setSessions(result.sessions);
      setCurrentSessionId(result.currentSessionId);
      setTotal(result.total);
    } catch (err: any) {
      setError(getSessionErrorMessage(err));
    } finally {
      setLoading(false);
    }
  }

  function formatDevice(session: Session): string {
    if (session.parsedUA) {
      return `${session.parsedUA.browser} on ${session.parsedUA.os} (${session.parsedUA.deviceType})`;
    }
    return session.userAgent ?? "Unknown device";
  }

  return (
    <Card className="w-full max-w-2xl">
      <CardHeader>
        <CardTitle>Active sessions</CardTitle>
      </CardHeader>
      <CardContent className="space-y-4">
        {error && (
          <Alert variant="destructive">
            <AlertDescription>{error}</AlertDescription>
          </Alert>
        )}

        {loading ? (
          <p className="text-sm text-muted-foreground">Loading sessions...</p>
        ) : sessions.length === 0 ? (
          <p className="text-sm text-muted-foreground">No active sessions</p>
        ) : (
          <>
            <div className="space-y-2">
              {sessions.map((session) => {
                const isCurrent = session.id === currentSessionId;
                return (
                  <div key={session.id} className="flex items-center justify-between rounded-lg border p-3">
                    <div className="space-y-1">
                      <div className="flex items-center gap-2">
                        <span className="text-sm font-medium">{formatDevice(session)}</span>
                        {isCurrent && <Badge variant="secondary">Current</Badge>}
                      </div>
                      <p className="text-xs text-muted-foreground">
                        {session.ipAddress && `${session.ipAddress} · `}
                        Last active {new Date(session.lastActiveAt ?? session.createdAt).toLocaleString()}
                      </p>
                    </div>
                  </div>
                );
              })}
            </div>

            <div className="flex items-center justify-between">
              <p className="text-sm text-muted-foreground">
                {total} session{total !== 1 ? "s" : ""} total
              </p>
              <div className="flex gap-2">
                <Button variant="outline" size="sm" onClick={() => setOffset(Math.max(0, offset - limit))} disabled={offset === 0}>
                  Previous
                </Button>
                <Button variant="outline" size="sm" onClick={() => setOffset(offset + limit)} disabled={offset + limit >= total}>
                  Next
                </Button>
              </div>
            </div>
          </>
        )}
      </CardContent>
    </Card>
  );
}

List all sessions

GET /auth/sessions/all

No pagination — every session in one response.

// components/session-list-all.tsx
"use client";

import { useState, useEffect } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { sessionsApi } from "../lib/api/sessions";
import { getSessionErrorMessage } from "../lib/session-errors";

interface Session {
  id: string;
  ipAddress?: string;
  userAgent?: string;
  parsedUA?: { browser: string; os: string; deviceType: string };
  isRevoked: boolean;
  expiresAt: string;
  createdAt: string;
  lastActiveAt?: string;
}

export function SessionListAll() {
  const [sessions, setSessions] = useState<Session[]>([]);
  const [currentSessionId, setCurrentSessionId] = useState("");
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => { loadSessions(); }, []);

  async function loadSessions() {
    setLoading(true);
    setError(null);
    try {
      const result = await sessionsApi.listAll();
      setSessions(result.sessions);
      setCurrentSessionId(result.currentSessionId);
    } catch (err: any) {
      setError(getSessionErrorMessage(err));
    } finally {
      setLoading(false);
    }
  }

  function formatDevice(session: Session): string {
    if (session.parsedUA) {
      return `${session.parsedUA.browser} on ${session.parsedUA.os} (${session.parsedUA.deviceType})`;
    }
    return session.userAgent ?? "Unknown device";
  }

  return (
    <Card className="w-full max-w-2xl">
      <CardHeader>
        <CardTitle>All sessions</CardTitle>
      </CardHeader>
      <CardContent className="space-y-4">
        {error && (
          <Alert variant="destructive">
            <AlertDescription>{error}</AlertDescription>
          </Alert>
        )}

        {loading ? (
          <p className="text-sm text-muted-foreground">Loading sessions...</p>
        ) : sessions.length === 0 ? (
          <p className="text-sm text-muted-foreground">No active sessions</p>
        ) : (
          <div className="space-y-2">
            {sessions.map((session) => {
              const isCurrent = session.id === currentSessionId;
              return (
                <div key={session.id} className="flex items-center justify-between rounded-lg border p-3">
                  <div className="space-y-1">
                    <div className="flex items-center gap-2">
                      <span className="text-sm font-medium">{formatDevice(session)}</span>
                      {isCurrent && <Badge variant="secondary">Current</Badge>}
                    </div>
                    <p className="text-xs text-muted-foreground">
                      {session.ipAddress && `${session.ipAddress} · `}
                      Last active {new Date(session.lastActiveAt ?? session.createdAt).toLocaleString()}
                    </p>
                  </div>
                </div>
              );
            })}
          </div>
        )}
      </CardContent>
    </Card>
  );
}

Revoke one session

DELETE /auth/sessions/{id}

// components/revoke-session-button.tsx
"use client";

import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { sessionsApi } from "../lib/api/sessions";
import { getSessionErrorMessage } from "../lib/session-errors";

export function RevokeSessionButton({
  sessionId,
  onRevoked,
}: {
  sessionId: string;
  onRevoked?: () => void;
}) {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  async function handleRevoke() {
    setError(null);
    setLoading(true);
    try {
      await sessionsApi.revoke(sessionId);
      onRevoked?.();
    } catch (err: any) {
      setError(getSessionErrorMessage(err));
    } finally {
      setLoading(false);
    }
  }

  return (
    <div className="space-y-2">
      {error && (
        <Alert variant="destructive">
          <AlertDescription>{error}</AlertDescription>
        </Alert>
      )}
      <Button variant="ghost" size="sm" onClick={handleRevoke} disabled={loading}>
        {loading ? "Revoking..." : "Revoke"}
      </Button>
    </div>
  );
}

Revoke multiple sessions

POST /auth/sessions/revoke

Select and revoke up to 100 sessions at once.

// components/revoke-sessions-bulk.tsx
"use client";

import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { sessionsApi } from "../lib/api/sessions";
import { getSessionErrorMessage } from "../lib/session-errors";

interface Session {
  id: string;
  parsedUA?: { browser: string; os: string; deviceType: string };
  ipAddress?: string;
}

export function RevokeSessionsBulk({
  sessions,
  currentSessionId,
  onRevoked,
}: {
  sessions: Session[];
  currentSessionId: string;
  onRevoked?: () => void;
}) {
  const [selected, setSelected] = useState<Set<string>>(new Set());
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [success, setSuccess] = useState<number | null>(null);

  const selectable = sessions.filter((s) => s.id !== currentSessionId);

  function toggleSelect(id: string) {
    setSelected((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  }

  function toggleSelectAll() {
    if (selected.size === selectable.length) {
      setSelected(new Set());
    } else {
      setSelected(new Set(selectable.map((s) => s.id)));
    }
  }

  async function handleRevokeSelected() {
    if (selected.size === 0) return;
    setError(null);
    setSuccess(null);
    setLoading(true);
    try {
      const { revoked } = await sessionsApi.revokeMany(Array.from(selected));
      setSuccess(revoked);
      setSelected(new Set());
      onRevoked?.();
    } catch (err: any) {
      setError(getSessionErrorMessage(err));
    } finally {
      setLoading(false);
    }
  }

  function formatDevice(session: Session): string {
    if (session.parsedUA) {
      return `${session.parsedUA.browser} on ${session.parsedUA.os}`;
    }
    return session.userAgent ?? "Unknown device";
  }

  return (
    <div className="space-y-4">
      {error && (
        <Alert variant="destructive">
          <AlertDescription>{error}</AlertDescription>
        </Alert>
      )}

      {success !== null && (
        <Alert>
          <AlertDescription>{success} session{success !== 1 ? "s" : ""} revoked</AlertDescription>
        </Alert>
      )}

      <div className="flex items-center justify-between">
        <div className="flex items-center gap-2">
          <Checkbox checked={selected.size === selectable.length && selectable.length > 0} onCheckedChange={toggleSelectAll} />
          <span className="text-sm text-muted-foreground">Select all</span>
        </div>
        <Button variant="destructive" size="sm" onClick={handleRevokeSelected} disabled={loading || selected.size === 0}>
          {loading ? "Revoking..." : `Revoke ${selected.size} selected`}
        </Button>
      </div>

      <div className="space-y-2">
        {selectable.map((session) => (
          <div key={session.id} className="flex items-center gap-3 rounded-lg border p-3">
            <Checkbox checked={selected.has(session.id)} onCheckedChange={() => toggleSelect(session.id)} />
            <div className="space-y-1">
              <span className="text-sm font-medium">{formatDevice(session)}</span>
              <p className="text-xs text-muted-foreground">
                {session.ipAddress && `${session.ipAddress} · `}
                {session.id.slice(0, 8)}...
              </p>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

Revoke all except current

DELETE /auth/sessions

"Log out everywhere else."

// components/revoke-all-sessions.tsx
"use client";

import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { sessionsApi } from "../lib/api/sessions";
import { getSessionErrorMessage } from "../lib/session-errors";

export function RevokeAllSessions() {
  const [loading, setLoading] = useState(false);
  const [success, setSuccess] = useState(false);
  const [error, setError] = useState<string | null>(null);

  async function handleRevokeAll() {
    setError(null);
    setSuccess(false);
    setLoading(true);
    try {
      await sessionsApi.revokeAllExceptCurrent();
      setSuccess(true);
    } catch (err: any) {
      setError(getSessionErrorMessage(err));
    } finally {
      setLoading(false);
    }
  }

  return (
    <div className="space-y-4">
      {error && (
        <Alert variant="destructive">
          <AlertDescription>{error}</AlertDescription>
        </Alert>
      )}

      {success && (
        <Alert>
          <AlertDescription>All other sessions have been revoked</AlertDescription>
        </Alert>
      )}

      <Button variant="destructive" onClick={handleRevokeAll} disabled={loading}>
        {loading ? "Revoking..." : "Log out everywhere else"}
      </Button>
    </div>
  );
}

Refresh tokens

POST /auth/refresh

Rotates both tokens. Usually handled transparently by the browser.

await sessionsApi.refresh();
// new cookies are set automatically by the browser

Using it

// Paginated list
const { sessions, currentSessionId, total } = await sessionsApi.list(0, 20);

// All sessions
const { sessions, currentSessionId } = await sessionsApi.listAll();

// Revoke one
await sessionsApi.revoke(sessionId);

// Revoke many
const { revoked } = await sessionsApi.revokeMany([id1, id2]);

// Revoke all except current
await sessionsApi.revokeAllExceptCurrent();

// Refresh tokens
await sessionsApi.refresh();

Next

  • Setup — the apiRequest helper this is built on
  • Sessions — full request/response/error reference, and the curl cookie-jar gotcha worth reading if something isn't working

On this page