listUsers returns status 401 UNAUTHORIZED even though the current user is an admin.

I am using the admin() and adminClient() plugins while testing out better-auth. I am trying to list out the users using the auth client, but I am getting a 401 unauthorized. I've registered a user and manually changed the role to admin in the database. I am using nextjs15 Here's the code:
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { authClient } from "@/lib/auth-client";
import { verifySession } from "@/lib/verify-session";

export default async function UsersPage() {
const { user } = await verifySession();

console.log("CURRENT USER: ", user);

const { data } = await authClient.admin.listUsers({
query: {
limit: 10,
offset: 0,
},
});

const users = data?.users;

return (
<div className="space-y-4">
<h1 className="text-3xl font-bold">Users</h1>
<Card>
<CardHeader>
<CardTitle>Users</CardTitle>
</CardHeader>
<CardContent>
<ul>
{users && users.map((user) => <li key={user.id}>{user.email}</li>)}
</ul>
</CardContent>
</Card>
</div>
);
}
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { authClient } from "@/lib/auth-client";
import { verifySession } from "@/lib/verify-session";

export default async function UsersPage() {
const { user } = await verifySession();

console.log("CURRENT USER: ", user);

const { data } = await authClient.admin.listUsers({
query: {
limit: 10,
offset: 0,
},
});

const users = data?.users;

return (
<div className="space-y-4">
<h1 className="text-3xl font-bold">Users</h1>
<Card>
<CardHeader>
<CardTitle>Users</CardTitle>
</CardHeader>
<CardContent>
<ul>
{users && users.map((user) => <li key={user.id}>{user.email}</li>)}
</ul>
</CardContent>
</Card>
</div>
);
}
Here are the console logs:
CURRENT USER: {
id: 'ZjLohn28IT3OHklqxwAdspRV1Om2X0Ss',
name: 'xxxx',
emailVerified: true,
image: null,
createdAt: 2025-02-07T10:24:09.645Z,
updatedAt: 2025-02-07T10:24:09.645Z,
role: 'admin',
banned: null,
banReason: null,
banExpires: null
}
✓ Compiled /api/auth/[...all] in 180ms (1197 modules)
GET /api/auth/admin/list-users?limit=10&offset=0 401 in 345ms
CURRENT USER: {
id: 'ZjLohn28IT3OHklqxwAdspRV1Om2X0Ss',
name: 'xxxx',
emailVerified: true,
image: null,
createdAt: 2025-02-07T10:24:09.645Z,
updatedAt: 2025-02-07T10:24:09.645Z,
role: 'admin',
banned: null,
banReason: null,
banExpires: null
}
✓ Compiled /api/auth/[...all] in 180ms (1197 modules)
GET /api/auth/admin/list-users?limit=10&offset=0 401 in 345ms
What am I missing? 😅
5 Replies
invocation97
invocation97OP4w ago
Okay, so I realized I was using the authClient on the server side. I've changed it since to use the server side auth. However, the issue persists. This is what I changed it to:
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { redirect } from "next/navigation";

export default async function UsersPage() {
const session = await auth.api.getSession({
headers: await headers(),
});

if (!session) {
return redirect("/auth/sign-in");
} else {
console.log("SESSION: ", session);
}
const response = await auth.api.listUsers({
query: {
limit: 10,
offset: 0,
},
});
console.log("RESPONSE: ", response);

const users = response.users;

if (!users || users.length === 0) {
return (
<div className="space-y-4">
<h1 className="text-3xl font-bold">Users</h1>
<p>No users found.</p>
</div>
);
}

return (
<div className="space-y-4">
<h1 className="text-3xl font-bold">Users</h1>
<Card>
<CardHeader>
<CardTitle>Users</CardTitle>
</CardHeader>
<CardContent>
<ul>
{users && users.map((user) => <li key={user.id}>{user.email}</li>)}
</ul>
</CardContent>
</Card>
</div>
);
}
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { redirect } from "next/navigation";

export default async function UsersPage() {
const session = await auth.api.getSession({
headers: await headers(),
});

if (!session) {
return redirect("/auth/sign-in");
} else {
console.log("SESSION: ", session);
}
const response = await auth.api.listUsers({
query: {
limit: 10,
offset: 0,
},
});
console.log("RESPONSE: ", response);

const users = response.users;

if (!users || users.length === 0) {
return (
<div className="space-y-4">
<h1 className="text-3xl font-bold">Users</h1>
<p>No users found.</p>
</div>
);
}

return (
<div className="space-y-4">
<h1 className="text-3xl font-bold">Users</h1>
<Card>
<CardHeader>
<CardTitle>Users</CardTitle>
</CardHeader>
<CardContent>
<ul>
{users && users.map((user) => <li key={user.id}>{user.email}</li>)}
</ul>
</CardContent>
</Card>
</div>
);
}
I added the manual auth check, thinking that it might be the culprit. The session logs correctly and the role is still set to admin, however I get the same error as before
⨯ [Error [BetterCallAPIError]: API Error: UNAUTHORIZED ] {
status: 'UNAUTHORIZED',
headers: Headers {},
body: [Object],
digest: '3161027971'
}
⨯ [Error [BetterCallAPIError]: API Error: UNAUTHORIZED ] {
status: 'UNAUTHORIZED',
headers: Headers {},
body: [Object],
digest: '3161027971'
}
It turns out you need to pass in the headers the same way you would with any other auth form, so this is the final solution to the conundrum
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { redirect } from "next/navigation";

export default async function UsersPage() {
const session = await auth.api.getSession({
headers: await headers(),
});

if (!session) {
return redirect("/auth/sign-in");
} else {
console.log("SESSION: ", session);
}
const response = await auth.api.listUsers({
headers: await headers(),
query: {
limit: 10,
offset: 0,
},
next: {
tags: ["users"],
},
});
console.log("RESPONSE: ", response);

const users = response.users;

if (!users || users.length === 0) {
return (
<div className="space-y-4">
<h1 className="text-3xl font-bold">Users</h1>
<p>No users found.</p>
</div>
);
}

return (
<div className="space-y-4">
<h1 className="text-3xl font-bold">Users</h1>
<Card>
<CardHeader>
<CardTitle>Users</CardTitle>
</CardHeader>
<CardContent>
<ul>
{users && users.map((user) => <li key={user.id}>{user.email}</li>)}
</ul>
</CardContent>
</Card>
</div>
);
}
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { redirect } from "next/navigation";

export default async function UsersPage() {
const session = await auth.api.getSession({
headers: await headers(),
});

if (!session) {
return redirect("/auth/sign-in");
} else {
console.log("SESSION: ", session);
}
const response = await auth.api.listUsers({
headers: await headers(),
query: {
limit: 10,
offset: 0,
},
next: {
tags: ["users"],
},
});
console.log("RESPONSE: ", response);

const users = response.users;

if (!users || users.length === 0) {
return (
<div className="space-y-4">
<h1 className="text-3xl font-bold">Users</h1>
<p>No users found.</p>
</div>
);
}

return (
<div className="space-y-4">
<h1 className="text-3xl font-bold">Users</h1>
<Card>
<CardHeader>
<CardTitle>Users</CardTitle>
</CardHeader>
<CardContent>
<ul>
{users && users.map((user) => <li key={user.id}>{user.email}</li>)}
</ul>
</CardContent>
</Card>
</div>
);
}
Unknown User
Unknown User4w ago
Message Not Public
Sign In & Join Server To View
invocation97
invocation97OP4w ago
@belikebee Have you seen my latest message? It turns out you just need to pass in the headers: await headers() to the object because it is a server function and can't know if there's a session on the request time without including the headers.
Unknown User
Unknown User4w ago
Message Not Public
Sign In & Join Server To View
joseph013318
joseph01331812h ago
@invocation97 Thankyou soo much 🙏 Was facing the same issue. They should include this in the docs

Did you find this page helpful?