[GH-ISSUE #5772] Using custom IDP claims to assign roles #27669

Open
opened 2026-04-17 18:48:46 -05:00 by GiteaMirror · 6 comments
Owner

Originally created by @michidk on GitHub (Nov 5, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/5772

Our internal portal uses solely the Cognito social provider for login. This provider returns additional claims in the user info that will determine the role and org memberships of this user.

We are looking for a method of using that information (which is passed on every login) to assign the roles and org memberships in better-auth.

We currently have set overrideUserInfoOnSignIn: true and use mapProfileToUser to return that data and persist it in the user table.
Now what's left is to use that info and assign roles & org memberships.

This works fine in one of the hooks if session.cookieCache is disabled, but with it, it wouldn't work.

So far we tried the following:

  • session.create.after hook: runs after the session is created, user has to login twice so that the roles take effect
  • user.update.after hook: same problem
  • mapProfileToUser: same problem
  • session.create.before: same problem
  • user.update.before hook: cannot test because of https://github.com/better-auth/better-auth/issues/5771

Seems like the problem is that all the hooks/callbacks are executed when better-auth is in a certain state, where it would not fetch the database for roles anymore, because it already has that information. That means that this role data will only be pulled in the next login.

Our current workaround is that we tell users to log out and log in again twice when expecting role changes. Surely there has to be a better way.
SCIM support (https://github.com/better-auth/better-auth/issues/3276) goes into a similar direction and would probably solve this if Cognito supported SCIM - but this is not the case.

Originally created by @michidk on GitHub (Nov 5, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/5772 Our internal portal uses solely the Cognito social provider for login. This provider returns additional claims in the user info that will determine the role and org memberships of this user. We are looking for a method of using that information (which is passed on every login) to assign the roles and org memberships in better-auth. We currently have set `overrideUserInfoOnSignIn: true` and use `mapProfileToUser` to return that data and persist it in the user table. Now what's left is to use that info and assign roles & org memberships. This works fine in one of the hooks if `session.cookieCache` is disabled, but with it, it wouldn't work. So far we tried the following: - `session.create.after` hook: runs after the session is created, user has to login twice so that the roles take effect - `user.update.after` hook: same problem - `mapProfileToUser`: same problem - `session.create.before`: same problem - `user.update.before` hook: cannot test because of https://github.com/better-auth/better-auth/issues/5771 Seems like the problem is that all the hooks/callbacks are executed when better-auth is in a certain state, where it would not fetch the database for roles anymore, because it already has that information. That means that this role data will only be pulled in the next login. Our current workaround is that we tell users to log out and log in again twice when expecting role changes. Surely there has to be a better way. SCIM support (https://github.com/better-auth/better-auth/issues/3276) goes into a similar direction and would probably solve this if Cognito supported SCIM - but this is not the case.
GiteaMirror added the oauthorganization labels 2026-04-17 18:48:46 -05:00
Author
Owner

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

(No reply)

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3490298527 --> @better-auth-agent[bot] commented on GitHub (Nov 5, 2025): (No reply) <!-- 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)
Author
Owner

@dosubot[bot] commented on GitHub (Nov 5, 2025):

You're running into a known limitation: Better Auth caches roles and org memberships at session creation, so updates from Cognito claims only show up after a second login. This happens because hooks like session.create.after, user.update.after, and mapProfileToUser run after the session is already built, and Better Auth doesn't refetch roles for the current session until the next login. That's why your workaround (double login) is needed for changes to take effect [source].

There isn't a built-in way to force real-time role/org updates on the first login with Cognito, since SCIM isn't supported. The best workaround is to use a database hook (like account.update.after) to update the user record with fresh claims after each login, and a custom session plugin to inject those fields into the session response if you need them immediately [source]. For truly dynamic permissions, consider fetching roles/orgs per API request instead of relying on session state [source].

If you want to avoid double login for users, you can trigger a manual session refresh on the client (using the refetch method from useSession()) after updating roles/orgs, so users don't have to log out and back in [source].

Make sure your custom fields in mapProfileToUser match your database schema exactly (camelCase, not snake_case) to avoid mapping issues and TypeErrors in hooks [source].

SCIM support would solve this, but Cognito doesn't offer it yet [source]. For now, database hooks and session refreshes are your best options.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

<!-- gh-comment-id:3490326066 --> @dosubot[bot] commented on GitHub (Nov 5, 2025): <!-- Answer --> You're running into a known limitation: Better Auth caches roles and org memberships at session creation, so updates from Cognito claims only show up after a second login. This happens because hooks like `session.create.after`, `user.update.after`, and `mapProfileToUser` run after the session is already built, and Better Auth doesn't refetch roles for the current session until the next login. That's why your workaround (double login) is needed for changes to take effect [[source](https://github.com/better-auth/better-auth/issues/5662#issuecomment-3467572892)]. There isn't a built-in way to force real-time role/org updates on the first login with Cognito, since SCIM isn't supported. The best workaround is to use a database hook (like `account.update.after`) to update the user record with fresh claims after each login, and a custom session plugin to inject those fields into the session response if you need them immediately [[source](https://github.com/better-auth/better-auth/issues/5480)]. For truly dynamic permissions, consider fetching roles/orgs per API request instead of relying on session state [[source](https://github.com/better-auth/better-auth/issues/4668)]. If you want to avoid double login for users, you can trigger a manual session refresh on the client (using the `refetch` method from `useSession()`) after updating roles/orgs, so users don't have to log out and back in [[source](https://github.com/better-auth/better-auth/issues/4668)]. Make sure your custom fields in `mapProfileToUser` match your database schema exactly (camelCase, not snake_case) to avoid mapping issues and TypeErrors in hooks [[source](https://github.com/better-auth/better-auth/issues/5771)]. SCIM support would solve this, but Cognito doesn't offer it yet [[source](https://github.com/better-auth/better-auth/issues/3276)]. For now, database hooks and session refreshes are your best options. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/7a80a1f1-13af-4593-b14a-5c9b07ee1109?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/7a80a1f1-13af-4593-b14a-5c9b07ee1109?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/7a80a1f1-13af-4593-b14a-5c9b07ee1109?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/7a80a1f1-13af-4593-b14a-5c9b07ee1109?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/7a80a1f1-13af-4593-b14a-5c9b07ee1109?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/7a80a1f1-13af-4593-b14a-5c9b07ee1109?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/7a80a1f1-13af-4593-b14a-5c9b07ee1109?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/5772)
Author
Owner

@michidk commented on GitHub (Nov 5, 2025):

I found a workaround, by implementing a Better Auth plugin that syncs Cognito user roles and invalidates the session cache.

Better Auth creates the session before any hooks can modify user roles, so the first request after an OAuth login ends up with stale/default roles — even though the correct roles are already synced to the database. To make things worse, Better Auth’s cookie cache keeps serving the stale session data.

This plugin fixes that by:

  1. Running after the OAuth callback completes (when the session is already created)
  2. Syncing Cognito groups to the database (user.role and members tables)
  3. Invalidating the session cookie cache so the next request forces a fresh DB read
  4. Ensuring subsequent requests get the correct roles

How it works

  • Hooks into Better Auth’s after hook for OAuth callbacks
  • Matches all callbacks via /callback/:id route pattern
  • Calls syncCognitoUser() to extract cognito:groups from user.userInfo
  • Deletes the session cache cookie (handles __Secure- prefix for HTTPS)
<!-- gh-comment-id:3491771585 --> @michidk commented on GitHub (Nov 5, 2025): I found a workaround, by implementing a Better Auth plugin that syncs Cognito user roles and invalidates the session cache. Better Auth creates the session before any hooks can modify user roles, so the first request after an OAuth login ends up with stale/default roles — even though the correct roles are already synced to the database. To make things worse, Better Auth’s cookie cache keeps serving the stale session data. This plugin fixes that by: 1. Running **after the OAuth callback** completes (when the session is already created) 2. **Syncing Cognito groups** to the database (`user.role` and `members` tables) 3. **Invalidating the session cookie cache** so the next request forces a fresh DB read 4. Ensuring subsequent requests get the **correct roles** ### How it works - Hooks into Better Auth’s `after` hook for OAuth callbacks - Matches all callbacks via `/callback/:id` route pattern - Calls `syncCognitoUser()` to extract `cognito:groups` from `user.userInfo` - Deletes the session cache cookie (handles `__Secure-` prefix for HTTPS)
Author
Owner

@michidk commented on GitHub (Nov 7, 2025):

@Bekacru I think you comment here: https://github.com/better-auth/better-auth/issues/5771#issuecomment-3494056785 was ment to go onto this PR.

I implemented my workaround using createAuthMiddleware() now.

However, there needs to be a better way to work around the cookie cache than my custom invalidateSessionCache().

export const cognitoSync = (): BetterAuthPlugin => {
	return {
		id: "cognito-sync",
		hooks: {
			after: [
				{
					matcher: (context) => {
						// Only handle Cognito OAuth callbacks
						return (
							context.path === "/callback/:id" &&
							context.params?.id === "cognito"
						);
					},
					handler: createAuthMiddleware(async (ctx) => {
						try {
							// Only proceed if a new session was created
							const newSession = ctx.context.newSession;
							if (!newSession?.user?.id) {
								return;
							}

							const userId = newSession.user.id;

							// Sync Cognito roles and organization memberships to database
							const updatedRole = await syncCognitoUser(userId);

							// Update the session object with fresh user data
							// This ensures the session cookie has the correct roles from the start
							newSession.user = {
								...newSession.user,
								role: updatedRole,
							};

							// Invalidate the session cookie cache to ensure fresh data on next request
							invalidateSessionCache(ctx);

							console.log(
								`[Cognito Sync] Synced roles and updated session for user: ${userId}, role: ${updatedRole}`,
							);
						} catch (error) {
							// Log error but don't throw - allow OAuth flow to complete
							console.error(
								"[Cognito Sync] Failed to sync roles:",
								error instanceof Error ? error.message : String(error),
							);
						}
					}),
				},
			],
		},
	};
};

function invalidateSessionCache(
	ctx: Parameters<Parameters<typeof createAuthMiddleware>[0]>[0],
): void {
	const baseCookieName = `${cookiePrefix}.session_data`;
	const secureCookieName = `__Secure-${baseCookieName}`;

	const cookieOptions = {
		httpOnly: true,
		sameSite: "lax" as const,
		path: "/",
		maxAge: 0,
	};

	// Expire both possible cookie variants
	// One will be a no-op, but that's simpler than environment detection
	ctx.setCookie(baseCookieName, "", cookieOptions);
	ctx.setCookie(secureCookieName, "", { ...cookieOptions, secure: true });
}
<!-- gh-comment-id:3501408580 --> @michidk commented on GitHub (Nov 7, 2025): @Bekacru I think you comment here: https://github.com/better-auth/better-auth/issues/5771#issuecomment-3494056785 was ment to go onto this PR. I implemented my workaround using `createAuthMiddleware()` now. However, there needs to be a better way to work around the cookie cache than my custom `invalidateSessionCache()`. ```typescript export const cognitoSync = (): BetterAuthPlugin => { return { id: "cognito-sync", hooks: { after: [ { matcher: (context) => { // Only handle Cognito OAuth callbacks return ( context.path === "/callback/:id" && context.params?.id === "cognito" ); }, handler: createAuthMiddleware(async (ctx) => { try { // Only proceed if a new session was created const newSession = ctx.context.newSession; if (!newSession?.user?.id) { return; } const userId = newSession.user.id; // Sync Cognito roles and organization memberships to database const updatedRole = await syncCognitoUser(userId); // Update the session object with fresh user data // This ensures the session cookie has the correct roles from the start newSession.user = { ...newSession.user, role: updatedRole, }; // Invalidate the session cookie cache to ensure fresh data on next request invalidateSessionCache(ctx); console.log( `[Cognito Sync] Synced roles and updated session for user: ${userId}, role: ${updatedRole}`, ); } catch (error) { // Log error but don't throw - allow OAuth flow to complete console.error( "[Cognito Sync] Failed to sync roles:", error instanceof Error ? error.message : String(error), ); } }), }, ], }, }; }; function invalidateSessionCache( ctx: Parameters<Parameters<typeof createAuthMiddleware>[0]>[0], ): void { const baseCookieName = `${cookiePrefix}.session_data`; const secureCookieName = `__Secure-${baseCookieName}`; const cookieOptions = { httpOnly: true, sameSite: "lax" as const, path: "/", maxAge: 0, }; // Expire both possible cookie variants // One will be a no-op, but that's simpler than environment detection ctx.setCookie(baseCookieName, "", cookieOptions); ctx.setCookie(secureCookieName, "", { ...cookieOptions, secure: true }); } ```
Author
Owner

@dosubot[bot] commented on GitHub (Feb 6, 2026):

Hi, @michidk. I'm Dosu, and I'm helping the better-auth team manage their backlog and am marking this issue as stale.

Issue Summary

  • You reported that better-auth caches session roles from Cognito claims, causing role updates to only apply after multiple logins.
  • This caching behavior is a known limitation since roles are cached at session creation.
  • Workarounds include using database hooks and client-side session refreshes.
  • You developed a plugin that syncs Cognito roles after the OAuth callback and invalidates the session cookie cache to force fresh role data on the next request.
  • While this improves the user experience by avoiding double logins, you noted a desire for a cleaner solution to handle session cache invalidation.

Next Steps

  • Please let me know if this issue is still relevant with the latest version of better-auth by commenting here to keep the discussion open.
  • Otherwise, this issue will be automatically closed in 7 days.

Thank you for your understanding and contribution!

<!-- gh-comment-id:3861257020 --> @dosubot[bot] commented on GitHub (Feb 6, 2026): Hi, @michidk. I'm [Dosu](https://dosu.dev), and I'm helping the better-auth team manage their backlog and am marking this issue as stale. **Issue Summary** - You reported that better-auth caches session roles from Cognito claims, causing role updates to only apply after multiple logins. - This caching behavior is a known limitation since roles are cached at session creation. - Workarounds include using database hooks and client-side session refreshes. - You developed a plugin that syncs Cognito roles after the OAuth callback and invalidates the session cookie cache to force fresh role data on the next request. - While this improves the user experience by avoiding double logins, you noted a desire for a cleaner solution to handle session cache invalidation. **Next Steps** - Please let me know if this issue is still relevant with the latest version of better-auth by commenting here to keep the discussion open. - Otherwise, this issue will be automatically closed in 7 days. Thank you for your understanding and contribution!
Author
Owner

@michidk commented on GitHub (Feb 10, 2026):

@dosubot Issue persists

<!-- gh-comment-id:3879925164 --> @michidk commented on GitHub (Feb 10, 2026): @dosubot Issue persists
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#27669