api.auth.getSession returns null

Better auth team. Can you fix this issue? This is really bugging my application. https://github.com/better-auth/better-auth/issues/2055
GitHub
auth.api.getSession returns null in RSC Layout.tsx with valid sessi...
Is this suited for github? Yes, this is suited for github To Reproduce I'm trying to debug why auth.api.getSession will return null, when the session is valid after a query to /api/auth/get-ses...
4 Replies
Ping
Ping2mo ago
Hey there is too many variables in that issue to confirm that you're facing the exact same thing they are - it's too broad. Mind providing details of your own? Where are you calling the getSession, what framework, what version, etc.
$golddy
$golddyOP2mo ago
I am using next.js app router v15.4.3 and better-auth v1.3.2
export const auth = betterAuth({
session: {
expiresIn: getEnvSecrets().BETTER_AUTH_SESSION_EXPIRES_IN,
updateAge: getEnvSecrets().BETTER_AUTH_SESSION_UPDATE_AGE,
freshAge: getEnvSecrets().BETTER_AUTH_SESSION_FRESH_AGE,
cookieCache: {
enabled: true,
maxAge: getEnvSecrets().BETTER_AUTH_SESSION_COOKIE_CACHE_MAX_AGE,
},
},
trustedOrigins: (_) => {
const origins = [...getEnvSecrets().BETTER_AUTH_TRUSTED_ORIGINS];
const vercelBranchUrl = getEnvSecrets().VERCEL_BRANCH_URL;
if (vercelBranchUrl) {
origins.push(vercelBranchUrl);
}
const vercelUrl = getEnvSecrets().VERCEL_URL;
if (vercelUrl) {
origins.push(vercelUrl);
}
return origins;
},
hooks: {
before: createAuthMiddleware(async (ctx) => {
switch (ctx.path) {
case "/sign-up/email": {
const allowedEmailDomains = getEnvSecrets().ALLOWED_EMAIL_DOMAINS;
if (
allowedEmailDomains.length !== 0 &&
!allowedEmailDomains.includes(ctx.body?.email.split("@")[1])
) {
throw new APIError("BAD_REQUEST", {
code: "EMAIL_DOMAIN_NOT_ALLOWED",
message: allowedEmailDomains.join(", "),
});
}

if (!ctx.body?.termsAccepted) {
throw new APIError("BAD_REQUEST", {
code: "TERMS_NOT_ACCEPTED",
});
}

break;
}
}
}),
after: createAuthMiddleware(async (ctx) => {
if (ctx.path.startsWith("/sign-in")) {
const user = ctx.context.newSession?.user;
if (user && !user.termsAccepted) {
throw new APIError("BAD_REQUEST", {
code: "TERMS_NOT_ACCEPTED",
});
}
}
}),
},
disabledPaths: ["/sign-up/email", "/sign-in"],
plugins: [
organization({
async sendInvitationEmail(data) {
const inviteLink = `${getEnvSecrets().BETTER_AUTH_URL}/accept-invitation/${data.id}`;
const t = await getTranslations("Library.Auth.Email.InviteUserEmail");

await resend.emails.send({
from: fromEmail,
to: data.email,
subject: t("subject"),
react: reactInviteUserEmail({
organizationName: data.organization.name,
invitorUsername: data.inviter.user.name,
inviteLink,
}),
});
},
async invitationLimit({ member }) {
return member.role === MemberRole.ADMIN ? 100 : 0;
},
cancelPendingInvitationsOnReInvite: true,
}),
nextCookies(),
],
});
export const auth = betterAuth({
session: {
expiresIn: getEnvSecrets().BETTER_AUTH_SESSION_EXPIRES_IN,
updateAge: getEnvSecrets().BETTER_AUTH_SESSION_UPDATE_AGE,
freshAge: getEnvSecrets().BETTER_AUTH_SESSION_FRESH_AGE,
cookieCache: {
enabled: true,
maxAge: getEnvSecrets().BETTER_AUTH_SESSION_COOKIE_CACHE_MAX_AGE,
},
},
trustedOrigins: (_) => {
const origins = [...getEnvSecrets().BETTER_AUTH_TRUSTED_ORIGINS];
const vercelBranchUrl = getEnvSecrets().VERCEL_BRANCH_URL;
if (vercelBranchUrl) {
origins.push(vercelBranchUrl);
}
const vercelUrl = getEnvSecrets().VERCEL_URL;
if (vercelUrl) {
origins.push(vercelUrl);
}
return origins;
},
hooks: {
before: createAuthMiddleware(async (ctx) => {
switch (ctx.path) {
case "/sign-up/email": {
const allowedEmailDomains = getEnvSecrets().ALLOWED_EMAIL_DOMAINS;
if (
allowedEmailDomains.length !== 0 &&
!allowedEmailDomains.includes(ctx.body?.email.split("@")[1])
) {
throw new APIError("BAD_REQUEST", {
code: "EMAIL_DOMAIN_NOT_ALLOWED",
message: allowedEmailDomains.join(", "),
});
}

if (!ctx.body?.termsAccepted) {
throw new APIError("BAD_REQUEST", {
code: "TERMS_NOT_ACCEPTED",
});
}

break;
}
}
}),
after: createAuthMiddleware(async (ctx) => {
if (ctx.path.startsWith("/sign-in")) {
const user = ctx.context.newSession?.user;
if (user && !user.termsAccepted) {
throw new APIError("BAD_REQUEST", {
code: "TERMS_NOT_ACCEPTED",
});
}
}
}),
},
disabledPaths: ["/sign-up/email", "/sign-in"],
plugins: [
organization({
async sendInvitationEmail(data) {
const inviteLink = `${getEnvSecrets().BETTER_AUTH_URL}/accept-invitation/${data.id}`;
const t = await getTranslations("Library.Auth.Email.InviteUserEmail");

await resend.emails.send({
from: fromEmail,
to: data.email,
subject: t("subject"),
react: reactInviteUserEmail({
organizationName: data.organization.name,
invitorUsername: data.inviter.user.name,
inviteLink,
}),
});
},
async invitationLimit({ member }) {
return member.role === MemberRole.ADMIN ? 100 : 0;
},
cancelPendingInvitationsOnReInvite: true,
}),
nextCookies(),
],
});
Here is my auth.ts
import { getSessionCookie } from "better-auth/cookies";
import { NextRequest, NextResponse } from "next/server";

export async function middleware(request: NextRequest) {
const response = NextResponse.next();

// Add current URL to headers for server components
const pathname = request.nextUrl.pathname;
const searchParams = request.nextUrl.search;

response.headers.set("x-pathname", pathname);
response.headers.set("x-search-params", searchParams);

const sessionCookie = getSessionCookie(request);
if (!sessionCookie) {
const currentUrl = pathname + searchParams;
const returnUrl = encodeURIComponent(currentUrl);
return NextResponse.redirect(
new URL(`/login?returnUrl=${returnUrl}`, request.url),
);
}

return response;
}

export const config = {
matcher: ["/app/:path*"], // Apply middleware to specific routes
};
import { getSessionCookie } from "better-auth/cookies";
import { NextRequest, NextResponse } from "next/server";

export async function middleware(request: NextRequest) {
const response = NextResponse.next();

// Add current URL to headers for server components
const pathname = request.nextUrl.pathname;
const searchParams = request.nextUrl.search;

response.headers.set("x-pathname", pathname);
response.headers.set("x-search-params", searchParams);

const sessionCookie = getSessionCookie(request);
if (!sessionCookie) {
const currentUrl = pathname + searchParams;
const returnUrl = encodeURIComponent(currentUrl);
return NextResponse.redirect(
new URL(`/login?returnUrl=${returnUrl}`, request.url),
);
}

return response;
}

export const config = {
matcher: ["/app/:path*"], // Apply middleware to specific routes
};
This is my middleware Middleware check is passed, but when I call this function to get session
export async function getSession(): Promise<Session | null> {
const session = await auth.api.getSession({
headers: await headers(),
});

return session;
}
export async function getSession(): Promise<Session | null> {
const session = await auth.api.getSession({
headers: await headers(),
});

return session;
}
This returns null. cc: @Ping
Ping
Ping2mo ago
It's possible that the session has expired, so you're holding on to an out-dated cookie.
$golddy
$golddyOP2mo ago
@Ping Should I check session is expired on middleware also then? We set the session's expiresIn to 7 days. But we see this error too often. https://github.com/masumi-network/sokosumi/blob/main/web-app/src/lib/auth/auth.ts I normally get this error, when I log in.

Did you find this page helpful?