Additional fields defaulting to null when you use /api/auth/get-session #2243

Closed
opened 2026-03-13 09:37:54 -05:00 by GiteaMirror · 1 comment
Owner

Originally created by @Bruh-Codes on GitHub (Oct 31, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

set up a default better-auth on nextjs

Current vs. Expected behavior

so i decided to check for an additional field role on the session in nextjs middleware and it was defualting to null when using /api/auth/get-session. On the client side i get it from the user object

import { NextRequest, NextResponse } from "next/server";
import { getSessionCookie } from "better-auth/cookies";
import axios from "axios";
import { allowedUsers } from "./lib/utils";

export async function middleware(request: NextRequest) {
	const sessionCookie = getSessionCookie(request);

	const { data: session } = await axios.get("/api/auth/get-session", {
		baseURL: request.nextUrl.origin,
		headers: {
			cookie: request.headers.get("cookie") || "", // Forward the cookies from the request
		},
	});

	if (
		!sessionCookie ||
		!session?.role ||
		!allowedUsers.includes(session.role)
	) {
		return NextResponse.redirect(new URL("/", request.url));
	}

	return NextResponse.next();
}

export const config = {
	matcher: ["/admin/:path*"],
};


to make it work i had to use customSession on the plugins. Doing this works but makes the additional fields declared on the session config in auth.ts useless


plugins: [
		admin({
			defaultRole: UserRoles.user,
			adminRoles: [UserRoles.admin],
			ac,
			roles,
		}),
		customSession(async ({ user, session }) => {
			return {
				...session,
				...user,
				role: user.role,
			};
		}),
	],

What version of Better Auth are you using?

^1.3.8

System info

{
  "system": {
    "platform": "win32",
    "arch": "x64",
    "version": "Windows 11 Pro",
    "release": "10.0.26200",
    "cpuCount": 8,
    "cpuModel": "Intel(R) Core(TM) i7-10510U CPU @ 1.80GHz",
    "totalMemory": "15.78 GB",
    "freeMemory": "2.38 GB"
  },
  "node": {
    "version": "v24.1.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.3.0"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "15.3.3"
    },
    {
      "name": "react",
      "version": "^19.0.0"
    }
  ],
  "databases": [
    {
      "name": "pg",
      "version": "^8.16.3"
    },
    {
      "name": "drizzle",
      "version": "^0.44.7"
    }
  ],
  "betterAuth": {
    "version": "^1.3.8",
    "config": {
      "user": {
        "additionalFields": {
          "role": {
            "type": [
              "admin",
              "user"
            ],
            "input": false
          }
        }
      },
      "session": {
        "additionalFields": {
          "role": {
            "type": [
              "admin",
              "user"
            ],
            "required": true
          }
        }
      },
      "account": {
        "accountLinking": {
          "enabled": true,
          "trustedProviders": [
            "google"
          ]
        }
      },
      "trustedOrigins": [
        "https://yieldedu.xyz",
        "http:localhost:3000"
      ],
      "socialProviders": {
        "google": {
          "clientId": "[REDACTED]",
          "clientSecret": "[REDACTED]"
        }
      },
      "plugins": [
        {
          "name": "admin",
          "config": {
            "id": "admin",
            "hooks": {
              "after": [
                {}
              ]
            },
            "endpoints": {},
            "$ERROR_CODES": {
              "FAILED_TO_CREATE_USER": "Failed to create user",
              "USER_ALREADY_EXISTS": "User already exists. Use another email.",
              "YOU_CANNOT_BAN_YOURSELF": "You cannot ban yourself",
              "YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE": "You are not allowed to change users role",
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS": "You are not allowed to create users",       
              "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS": "You are not allowed to list users",
              "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS": "You are not allowed to list users sessions",
              "YOU_ARE_NOT_ALLOWED_TO_BAN_USERS": "You are not allowed to ban users",
              "YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS": "You are not allowed to impersonate users",
              "YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS": "You are not allowed to revoke users sessions",
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS": "You are not allowed to delete users",       
              "YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD": "[REDACTED]",
              "BANNED_USER": "You have been banned from this application",
              "YOU_ARE_NOT_ALLOWED_TO_GET_USER": "You are not allowed to get user",
              "NO_DATA_TO_UPDATE": "No data to update",
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS": "You are not allowed to update users",       
              "YOU_CANNOT_REMOVE_YOURSELF": "You cannot remove yourself"
            },
            "schema": {
              "user": {
                "fields": {
                  "role": {
                    "type": "string",
                    "required": false,
                    "input": false
                  },
                  "banned": {
                    "type": "boolean",
                    "defaultValue": false,
                    "required": false,
                    "input": false
                  },
                  "banReason": {
                    "type": "string",
                    "required": false,
                    "input": false
                  },
                  "banExpires": {
                    "type": "date",
                    "required": false,
                    "input": false
                  }
                }
              },
              "session": {
                "fields": {
                  "impersonatedBy": {
                    "type": "string",
                    "required": false
                  }
                }
              }
            },
            "options": {
              "defaultRole": "user",
              "adminRoles": [
                "admin"
              ],
              "ac": {
                "statements": {
                  "user": [
                    "create",
                    "list",
                    "set-role",
                    "ban",
                    "impersonate",
                    "delete",
                    "set-password",
                    "get",
                    "update"
                  ],
                  "session": [
                    "list",
                    "revoke",
                    "delete"
                  ]
                }
              },
              "roles": {
                "user": {
                  "statements": {
                    "user": [
                      "create",
                      "list",
                      "set-role",
                      "ban",
                      "impersonate",
                      "delete",
                      "set-password",
                      "get",
                      "update"
                    ]
                  }
                },
                "admin": {
                  "statements": {
                    "user": [
                      "create",
                      "list",
                      "set-role",
                      "ban",
                      "impersonate",
                      "delete",
                      "set-password",
                      "get",
                      "update"
                    ],
                    "session": [
                      "list",
                      "revoke",
                      "delete"
                    ]
                  }
                }
              }
            }
          }
        },
        {
          "name": "custom-session",
          "config": {
            "id": "custom-session",
            "hooks": {
              "after": [
                {}
              ]
            },
            "endpoints": {}
          }
        }
      ]
    }
  }
}

Which area(s) are affected? (Select all that apply)

Backend, Client

Auth config (if applicable)

import { betterAuth } from "better-auth";
import { admin, customSession } from "better-auth/plugins";
import { ac, roles } from "@/lib/permissions";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "./drizzle";
import * as schema from "@/drizzle/schema";

export enum UserRoles {
	admin = "admin",
	user = "user",
}

export const auth = betterAuth({
	user: {
		additionalFields: {
			role: {
				type: Object.keys(UserRoles),
				input: false,
			},
		},
	},

	session: {
		additionalFields: {
			role: {
				type: Object.keys(UserRoles),
				required: true,
			},
		},
	},
	account: {
		accountLinking: {
			enabled: true,
			trustedProviders: ["google"],
		},
	},

	trustedOrigins: ["https://yieldedu.xyz", "http:localhost:3000"],
	socialProviders: {
		google: {
			clientId: process.env.GOOGLE_CLIENT_ID!,
			clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
		},
	},

	plugins: [
		admin({
			defaultRole: UserRoles.user,
			adminRoles: [UserRoles.admin],
			ac,
			roles,
		}),
		customSession(async ({ user, session }) => {
			return {
				...session,
				...user,
				role: user.role,
			};
		}),
	],

	database: drizzleAdapter(db, {
		provider: "pg",
		schema,
	}),
});

Additional context

to make it work i had to use customSession on the plugins. Doing this works but makes the additional fields declared on the session config in auth.ts useless


plugins: [
		admin({
			defaultRole: UserRoles.user,
			adminRoles: [UserRoles.admin],
			ac,
			roles,
		}),
		customSession(async ({ user, session }) => {
			return {
				...session,
				...user,
				role: user.role,
			};
		}),
	],

Originally created by @Bruh-Codes on GitHub (Oct 31, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce set up a default better-auth on nextjs ### Current vs. Expected behavior so i decided to check for an additional field role on the session in nextjs middleware and it was defualting to null when using /api/auth/get-session. On the client side i get it from the user object ```ts import { NextRequest, NextResponse } from "next/server"; import { getSessionCookie } from "better-auth/cookies"; import axios from "axios"; import { allowedUsers } from "./lib/utils"; export async function middleware(request: NextRequest) { const sessionCookie = getSessionCookie(request); const { data: session } = await axios.get("/api/auth/get-session", { baseURL: request.nextUrl.origin, headers: { cookie: request.headers.get("cookie") || "", // Forward the cookies from the request }, }); if ( !sessionCookie || !session?.role || !allowedUsers.includes(session.role) ) { return NextResponse.redirect(new URL("/", request.url)); } return NextResponse.next(); } export const config = { matcher: ["/admin/:path*"], }; ``` to make it work i had to use customSession on the plugins. Doing this works but makes the additional fields declared on the session config in auth.ts useless ```ts plugins: [ admin({ defaultRole: UserRoles.user, adminRoles: [UserRoles.admin], ac, roles, }), customSession(async ({ user, session }) => { return { ...session, ...user, role: user.role, }; }), ], ``` ### What version of Better Auth are you using? ^1.3.8 ### System info ```bash { "system": { "platform": "win32", "arch": "x64", "version": "Windows 11 Pro", "release": "10.0.26200", "cpuCount": 8, "cpuModel": "Intel(R) Core(TM) i7-10510U CPU @ 1.80GHz", "totalMemory": "15.78 GB", "freeMemory": "2.38 GB" }, "node": { "version": "v24.1.0", "env": "development" }, "packageManager": { "name": "npm", "version": "11.3.0" }, "frameworks": [ { "name": "next", "version": "15.3.3" }, { "name": "react", "version": "^19.0.0" } ], "databases": [ { "name": "pg", "version": "^8.16.3" }, { "name": "drizzle", "version": "^0.44.7" } ], "betterAuth": { "version": "^1.3.8", "config": { "user": { "additionalFields": { "role": { "type": [ "admin", "user" ], "input": false } } }, "session": { "additionalFields": { "role": { "type": [ "admin", "user" ], "required": true } } }, "account": { "accountLinking": { "enabled": true, "trustedProviders": [ "google" ] } }, "trustedOrigins": [ "https://yieldedu.xyz", "http:localhost:3000" ], "socialProviders": { "google": { "clientId": "[REDACTED]", "clientSecret": "[REDACTED]" } }, "plugins": [ { "name": "admin", "config": { "id": "admin", "hooks": { "after": [ {} ] }, "endpoints": {}, "$ERROR_CODES": { "FAILED_TO_CREATE_USER": "Failed to create user", "USER_ALREADY_EXISTS": "User already exists. Use another email.", "YOU_CANNOT_BAN_YOURSELF": "You cannot ban yourself", "YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE": "You are not allowed to change users role", "YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS": "You are not allowed to create users", "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS": "You are not allowed to list users", "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS": "You are not allowed to list users sessions", "YOU_ARE_NOT_ALLOWED_TO_BAN_USERS": "You are not allowed to ban users", "YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS": "You are not allowed to impersonate users", "YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS": "You are not allowed to revoke users sessions", "YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS": "You are not allowed to delete users", "YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD": "[REDACTED]", "BANNED_USER": "You have been banned from this application", "YOU_ARE_NOT_ALLOWED_TO_GET_USER": "You are not allowed to get user", "NO_DATA_TO_UPDATE": "No data to update", "YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS": "You are not allowed to update users", "YOU_CANNOT_REMOVE_YOURSELF": "You cannot remove yourself" }, "schema": { "user": { "fields": { "role": { "type": "string", "required": false, "input": false }, "banned": { "type": "boolean", "defaultValue": false, "required": false, "input": false }, "banReason": { "type": "string", "required": false, "input": false }, "banExpires": { "type": "date", "required": false, "input": false } } }, "session": { "fields": { "impersonatedBy": { "type": "string", "required": false } } } }, "options": { "defaultRole": "user", "adminRoles": [ "admin" ], "ac": { "statements": { "user": [ "create", "list", "set-role", "ban", "impersonate", "delete", "set-password", "get", "update" ], "session": [ "list", "revoke", "delete" ] } }, "roles": { "user": { "statements": { "user": [ "create", "list", "set-role", "ban", "impersonate", "delete", "set-password", "get", "update" ] } }, "admin": { "statements": { "user": [ "create", "list", "set-role", "ban", "impersonate", "delete", "set-password", "get", "update" ], "session": [ "list", "revoke", "delete" ] } } } } } }, { "name": "custom-session", "config": { "id": "custom-session", "hooks": { "after": [ {} ] }, "endpoints": {} } } ] } } } ``` ### Which area(s) are affected? (Select all that apply) Backend, Client ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth"; import { admin, customSession } from "better-auth/plugins"; import { ac, roles } from "@/lib/permissions"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { db } from "./drizzle"; import * as schema from "@/drizzle/schema"; export enum UserRoles { admin = "admin", user = "user", } export const auth = betterAuth({ user: { additionalFields: { role: { type: Object.keys(UserRoles), input: false, }, }, }, session: { additionalFields: { role: { type: Object.keys(UserRoles), required: true, }, }, }, account: { accountLinking: { enabled: true, trustedProviders: ["google"], }, }, trustedOrigins: ["https://yieldedu.xyz", "http:localhost:3000"], socialProviders: { google: { clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }, }, plugins: [ admin({ defaultRole: UserRoles.user, adminRoles: [UserRoles.admin], ac, roles, }), customSession(async ({ user, session }) => { return { ...session, ...user, role: user.role, }; }), ], database: drizzleAdapter(db, { provider: "pg", schema, }), }); ``` ### Additional context to make it work i had to use customSession on the plugins. Doing this works but makes the additional fields declared on the session config in auth.ts useless ```ts plugins: [ admin({ defaultRole: UserRoles.user, adminRoles: [UserRoles.admin], ac, roles, }), customSession(async ({ user, session }) => { return { ...session, ...user, role: user.role, }; }), ], ```
GiteaMirror added the enhancementnextjs labels 2026-03-13 09:37:54 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Oct 31, 2025):

It turns out that the missing role isn’t a mistake in your user schema—it’s how the built‐in session caching works. By default, the session endpoint doesn’t merge your user’s additional fields into the session data when fetching via /api/auth/get-session. Instead, fields declared in the session configuration aren’t persisted into the cache and so they come through as null unless you manually merge them (for example, via the customSession plugin) [1].

In other words, using customSession forces the merging of the user object (which carries the correct role) into the session so that you see it on both client and API side. Without that, the additional session fields defined in your auth config aren’t automatically included at runtime because they’re not stored in secondary storage or the cookie cache [2].

Next steps for you would be:

• Verify that at sign in the user object properly has a valid role.
• Decide whether you want to enrich your session response using the customSession plugin (which you’re already doing) or if you believe the session configuration should automatically include those fields.
• If you’d prefer the latter behavior (without using customSession), then consider raising this as a bug or feature request with the maintainers, since the documentation notes that session caching does not include custom fields by default [1].

These steps should allow you to either work with the current design (using customSession) or push for a change if you want the declared session additionalFields to be applied automatically.

[1] Documentation on Customizing Session Response
[2] Notes on session caching and additional fields behavior

DiagramDiscordGitHub

Diagram Join Star

@better-auth-agent[bot] commented on GitHub (Oct 31, 2025): It turns out that the missing role isn’t a mistake in your user schema—it’s how the built‐in session caching works. By default, the session endpoint doesn’t merge your user’s additional fields into the session data when fetching via /api/auth/get-session. Instead, fields declared in the session configuration aren’t persisted into the cache and so they come through as null unless you manually merge them (for example, via the customSession plugin) [[1]](https://github.com/better-auth/better-auth/issues/5687). In other words, using customSession forces the merging of the user object (which carries the correct role) into the session so that you see it on both client and API side. Without that, the additional session fields defined in your auth config aren’t automatically included at runtime because they’re not stored in secondary storage or the cookie cache [[2]](https://github.com/better-auth/better-auth/issues/2170). Next steps for you would be: • Verify that at sign in the user object properly has a valid role. • Decide whether you want to enrich your session response using the customSession plugin (which you’re already doing) or if you believe the session configuration should automatically include those fields. • If you’d prefer the latter behavior (without using customSession), then consider raising this as a bug or feature request with the maintainers, since the documentation notes that session caching does not include custom fields by default [[1]](https://github.com/better-auth/better-auth/issues/5687). These steps should allow you to either work with the current design (using customSession) or push for a change if you want the declared session additionalFields to be applied automatically. [[1]](https://github.com/better-auth/better-auth/issues/5687) Documentation on Customizing Session Response [[2]](https://github.com/better-auth/better-auth/issues/2170) Notes on session caching and additional fields behavior <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#2243