Is there slightly incorrect wording on webhook update docs
On Sync users with webhooks
https://docs.kinde.com/authenticate/enterprise-connections/mapping-users-enterprise/
In the section Sync users with webhooks, it says:
"user.updated - when a user is added to an organization, their roles or permissions change, or when their assigned properties change"
I wanted to have critical entry info (like if they have onboarded) to come with the Kinde User data. This section lead KindeAI to inform me that properties are the only thing that send a webhook event for an update profile, and so I have to run a kinde-driven update through it.
After testing it seems that it does not create a user.updated event. I think its because their assigned properties change, is not their user but their organization or role. Consider in the docs clarifying their to be organization/role/permission so that I and the AI are not confused on what update
My question is: do properties set on the user get sent down with getUser? What do you recommend setting data that needs to be set programmatically and needs to be accessed immediately on first call? Properties? Permissions? Default+Non-Default Role/Org Properties?
Kinde docs
Mapping and syncing users for enterprise auth
Our developer tools provide everything you need to get started with Kinde.
9 Replies
Hi Oren,
Thanks for the detailed feedback and questions. We’re currently reviewing the behaviour around user.updated webhook events and how user properties trigger them. We'll also take your suggestion on clarifying the docs into consideration to reduce confusion. Regarding your question about immediate access to critical data via getUser(), we're investigating the best approach and will get back to you with a recommendation. Appreciate your patience in the meantime.
Thanks for the detailed feedback and questions. We’re currently reviewing the behaviour around user.updated webhook events and how user properties trigger them. We'll also take your suggestion on clarifying the docs into consideration to reduce confusion. Regarding your question about immediate access to critical data via getUser(), we're investigating the best approach and will get back to you with a recommendation. Appreciate your patience in the meantime.
@Krish - Kinde thanks! My intuition is that a toggle called Critical Property could be a fast way to add on that the data is sent with user authentication without breaking anything else
Thanks for pointing out the wording in this section of the docs:
Mapping users in enterprise connections "user.updated – when a user is added to an organization, their roles or permissions change, or when their assigned properties change." Confirmed Behaviour (based on internal testing): The user.updated webhook does fire when: A user is added to an organization Their roles or permissions change
But it does not fire when: User properties (even assigned ones) are updated This confirms your findings .Thank you again for testing and providing a reproducible scenario. Docs Update: We’ve updated the description for clarity: “user.updated – when a user is added to an organization, or their roles or permissions change.” This better reflects the actual behaviour and avoids confusion around “assigned properties.” If you need onboarding or similar flags : Here’s what we recommend for now: - Set the value using User Properties (custom or built-in) - In your Kinde Admin UI → Token customization settings, add those property keys - When the user logs in again, the new ID token will include the updated properties(if the value is not empty) - Calling getUser() will return those values immediately (since they come from the token) What if you want this without a full login? Right now, refreshing the ID token (via silent login or Management API) is the only option. However: Properties do not yet refresh via silent auth So, the only reliable way is to force the user to log in again. But this is not ideal in many use cases.
We’ll share this feedback with the product team, and I’ll tag this internally as a feature request for token refresh to include updated properties. Thanks again for your detailed feedback. It really helps us improve both the docs and product.
Mapping users in enterprise connections "user.updated – when a user is added to an organization, their roles or permissions change, or when their assigned properties change." Confirmed Behaviour (based on internal testing): The user.updated webhook does fire when: A user is added to an organization Their roles or permissions change
But it does not fire when: User properties (even assigned ones) are updated This confirms your findings .Thank you again for testing and providing a reproducible scenario. Docs Update: We’ve updated the description for clarity: “user.updated – when a user is added to an organization, or their roles or permissions change.” This better reflects the actual behaviour and avoids confusion around “assigned properties.” If you need onboarding or similar flags : Here’s what we recommend for now: - Set the value using User Properties (custom or built-in) - In your Kinde Admin UI → Token customization settings, add those property keys - When the user logs in again, the new ID token will include the updated properties(if the value is not empty) - Calling getUser() will return those values immediately (since they come from the token) What if you want this without a full login? Right now, refreshing the ID token (via silent login or Management API) is the only option. However: Properties do not yet refresh via silent auth So, the only reliable way is to force the user to log in again. But this is not ideal in many use cases.
We’ll share this feedback with the product team, and I’ll tag this internally as a feature request for token refresh to include updated properties. Thanks again for your detailed feedback. It really helps us improve both the docs and product.
Kinde docs
Mapping and syncing users for enterprise auth
Our developer tools provide everything you need to get started with Kinde.
@Krish - Kinde
To extract the property from the original token efficiently within the hook I have to do this, which feels yuck:
const routeGuard: Handler = async ({ event, resolve }) => {
if (event?.url.pathname.includes("auth")) {
return await resolve(event);
}
const ok = await kindeAuthClient.isAuthenticated(event.request);
if (!ok) throw redirect(303, "/auth");
const rawCookie = event.request.headers.get("cookie") ?? "";
const cookies = Object.fromEntries(rawCookie.split(";").map((c) => c.trim().split("=")));
const token = cookies["kinde_id_token"];
if (token && event?.route?.id?.includes("admin")) {
const claims = decodeJwt(token) as { user_properties: { "super-user": { v: string } } };
if (claims && claims.user_properties["super-user"].v === "true") {
return await resolve(event);
} else {
throw redirect(303, "/");
}
}
return await resolve(event);
};
Perhaps you could fetch properties through claims/permissions or something else?
Hi Oren,
Thanks for sharing your route guard code snippet. To better assist you and provide the most relevant guidance or examples, could you please confirm which framework or runtime environment you are using? For example, is this SvelteKit, Next.js, React, or another setup? Knowing this will help us tailor the solution specifically to your environment. Looking forward to your reply!
Thanks for sharing your route guard code snippet. To better assist you and provide the most relevant guidance or examples, could you please confirm which framework or runtime environment you are using? For example, is this SvelteKit, Next.js, React, or another setup? Knowing this will help us tailor the solution specifically to your environment. Looking forward to your reply!
Sveltekit 🙂
Hi Oren,
Thanks for waiting, and you're right to aim for a cleaner and more idiomatic approach than manually decoding token properties.
To streamline your admin route protection, we recommend leveraging Kinde Permissions, which are built for exactly this use case and avoid the need to manually parse nested properties from tokens.
Below is a recommended hooks.server.ts example using permission checks via SvelteKit SDK:
hooks.server.ts Example (Permission-Based Access)
import {
kindeAuthClient,
sessionHooks,
type Handler
} from '@kinde-oss/kinde-auth-sveltekit';
import { redirect } from '@sveltejs/kit';
export const handle: Handler = async ({ event, resolve }) => {
// Ensure session and cookies are handled
sessionHooks({ event });
// Guard /admin routes only
if (event.route?.id?.startsWith('/admin')) {
const isAuth = await kindeAuthClient.isAuthenticated(event.request);
if (!isAuth) {
throw redirect(303, '/auth'); // Redirect to login if not authenticated
}
const { permissions } = await kindeAuthClient.getPermissions(event.request);
console.log('Permissions:', permissions);
if (!permissions.includes('admin:access')) {
throw redirect(303, '/'); // Redirect away if user lacks permission
}
}
return resolve(event);
};
How to Use This
Step 1: Create a Permission in Kinde
1. Go to your Kinde account → your app
2. Navigate to User management > Roles & Permissions
3. Under Permissions, click “Create permission”
4. Name it something like:
* admin:access
* or super-user
5. Save
Step 2: Assign Permission to a Role
1. Under Roles, create (or use an existing one), e.g., Admin
2. Click into the Role → Assign Permissions
3. Assign the new permission (e.g., admin:access) to that role
Step 3: Assign the Role to a User
1. Go to Users in the Kinde dashboard
2. Click the user you’re testing with
3. Assign them to the Admin role you just updated
Let us know whether this helps.Happy to assist further.I guess perhaps admin is a bad example and your answer is a shortcut out. What I have is page loads and server stuff, and hooks.server force runs on init. In my mind, its important to send some tokens immediately for both speed and security. For instance there may request of:
1. is the user a paying customer and what tier? This may send them to different routes or change guarding of stuff
2. is the user onboarded and to what degree? This may mean that we do not reveal any of the app if the user is not registered and send them elsewhere
3. is the user part of a specific local/compliance region, where we cannot show something
4. other properties that impact above the fold UI/UX stuff
When exploring Kinde I notice that Permissions are tied to organizations and roles right. What about if the user does not have a organization in a fremium style model? Perhaps default works but I am not sure. Also, do permissions update reactively on change or do I need to log the user out?
Now perhaps this is not what your auth provider is supposed to be used for and I should just wait for the second trip to the DB after I have the KindeId, but I guess my comparison is to Supabase which had the postgress user table and easy-ish to use metakeys that immediately arrive with the token. Maybe I am wrong, but a UI flash and double trip feel like bad design. Further, I guess that I hope for more than "Get started in ten seconds", but rather this is the best way to do stuff for your app type and why, which I felt Supabase had. I am concerned as to why you even have the ability to send properties with the token if you cannot access them in an accessible way. I am not that technical and also thus not sure if my cookie code is robust. I also know you have feature flags and you want me to put stuff into the system, but this does not give me confidence. If all those things are just Key:Value, why not make it robust, generalizable and easily-in-sync.
While I have you @Krish - Kinde, I have been struggling with get a fresh ID token.
The hook requires cookie to get id (not getToken), for the properties and to let convex get authenticate with OIDC. As the user expires their token (perhaps not accessing the sessionHook for a hour), the getToken refreshes access but ID becomes stale. I am trying to get a fresh ID but nothing seems to work
---hooks.server.ts
const routeGuard: Handler = async ({ event, resolve }) => {
if (event?.url.pathname.includes("auth")) {
return await resolve(event);
}
const ok = await kindeAuthClient.isAuthenticated(event.request);
if (!ok) throw redirect(303, "/auth");
const kinde_id_token = event.cookies.get("kinde_id_token");
if (!kinde_id_token) throw redirect(303, "/auth");
event.locals.token = kinde_id_token;
const claims = decodeJwt(kinde_id_token) as { user_properties: { "super-user": { v: string } } };
if (event.route.id?.includes("admin")) {
const isSuper = claims.user_properties?.["super-user"]?.v === "true";
if (!isSuper) throw redirect(303, "/");
}
return await resolve(event);
};
---
after a thin passthrough of layout.server.ts
---
$effect.pre(() => {
const client = useConvexClient();
client.setAuth(async ({ forceRefreshToken }) => {
if (!forceRefreshToken) return data.token;
const refreshedToken = await fetch("/api/auth/refreshToken", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then((res) => res.json());
const { token } = refreshedToken;
// const refreshedToken = await fetchRefreshedToken(fetch);
return token;
});
console.log(client.query(api.tested.testedTwo, {}));
});
---
// src/routes/api/token/+server.ts
import { kindeAuthClient, type SessionManager } from "@kinde-oss/kinde-auth-sveltekit";
import { json } from "@sveltejs/kit";
import type { RequestHandler } from "./$types";
export const GET: RequestHandler = async ({ request, cookies }) => {
try {
const accessToken = await kindeAuthClient.getToken(request as unknown as SessionManager);
const kinde_id_token = cookies.get("kinde_id_token");
if (!accessToken) {
return json({ error: "No token available" }, { status: 401 });
}
return json({ token: kinde_id_token });
} catch (error) {
return json({ error: "Failed to get token" }, { status: 500 });
}
};
Hi Oren,
We're still actively looking into this and will provide you with an update as soon as possible. Thanks for your patience. Hi Oren,
We've explored several approaches to refresh and retrieve a new ID token on demand (e.g. immediately after changes to user properties, scopes, or permissions) within the current SvelteKit SDK. Unfortunately, due to how Kinde manages ID tokens, issuing them via secure HTTP-only cookies and refreshing them only through a full silent authentication redirect (
Thank you for understanding.
We're still actively looking into this and will provide you with an update as soon as possible. Thanks for your patience. Hi Oren,
We've explored several approaches to refresh and retrieve a new ID token on demand (e.g. immediately after changes to user properties, scopes, or permissions) within the current SvelteKit SDK. Unfortunately, due to how Kinde manages ID tokens, issuing them via secure HTTP-only cookies and refreshing them only through a full silent authentication redirect (
/auth/login?refresh=true
),there's no supported way to fetch a fresh ID token without triggering a full page redirect.
This means:
- The ID token remains unchanged until the session naturally expires or the user is reauthenticated via redirect.
- There's no getFreshIdToken()
equivalent in the current SDK.
- getToken()
only refreshes the access token, not the ID token.
Also tried workarounds (like fetching after silent redirect or using fetch
+ cookie inspection), but none result in a reliably updated ID token within a single-page flow in SvelteKit.
That said, we've raised this SvelteKit SDK limitation with the Kinde team and will monitor any updates or roadmap considerations for a future enhancement.Thank you for understanding.