Remix.run Clerk SDK server functions not returning permissions, roles, organization info, etc...
LumDermaku opened this issue · 8 comments
Preliminary Checks
- I have reviewed the documentation: https://clerk.com/docs
- I have searched for existing issues: https://github.com/clerk/javascript/issues
- I have not already reached out to Clerk support via email or Discord (if you have, no need to open an issue here)
- This issue is not a question, general help request, or anything other than a bug report directly related to Clerk. Please ask questions in our Discord community: https://clerk.com/discord.
Reproduction
https://github.com/LumDermaku/remix-clerk-bug
Publishable key
pk_test_bmF0aXZlLWNvdy01NS5jbGVyay5hY2NvdW50cy5kZXYk
Description
I cannot get any "custom" user info (org-related like permissions, roles, org itself) through Clerk SDK's server-related functions, such as getAuth
from @clerk/remix/ssr.server
as described by Clerk docs here.
Not even the `` component works when i pass my custom permission in the permission
prop. I made sure to copy-paste it from the Clerk dashboard. Same applies to roles.
The only way I can get all user info from Clerk is through client-components hooks, like useUser
's getOrganizationMemberships()
method, which returns all the correct info from Clerk, but for the moment I cannot use this when I can use `` for getting the user Info from the client-side, or getAuth
from server-side which is even better for me.
I have also customized the session token with the following:
"{permissions": "{{org_membership.permissions}}"}
The project is on Remix.run v2
with Vite v5.1.0
on Node.js v20.11.0
, with Clerk/Remix 4.0.0-beta.43
. I have to use the beta version otherwise I'll get that infamous Error 401 issue when running on v3.2.3
Steps to reproduce:
- Create a remix route but make sure you are signed in. For demo purposes, use
getAuth
in a loader, and also the `` component even though in this case if you use one you don't need the other:
export const loader = async (args: LoaderFunctionArgs) => {
const currentUser = await getAuth(args);
console.log(' * user info from server * : ', currentUser);
console.log(' * user permission from server * :', currentUser.has({ permission: 'my:custom:permission' }));
return json(currentUser)
}
export default function SomeComponent() {
const user = useUser()
... useEffect code to invoke getOrganizationMemberships() ...
console.log(' current user\'s permissions: ', currentUserPermissions)
return <Protect permission="my:custom:permission" fallback={<p>this renders</p>}>
This part will not be rendered
</Protect>
}
Expected behavior:
Based on the codebase (check the github repo)
On the server console.logs, i should get:
* user info from server* : {
actor: undefined,
sessionClaims: {
...default clerk properties...
permissions: ['my:custom:permission', 'what:ever'], <----------- i should get my permissions in here
...default clerk properties...
},
sessionId: 'sess_someSession',
userId: 'user_aUserId',
orgId: 'myOrgId', <----------- i should get orgId here
orgRole: 'myOrgRole', <----------- i should get an org role here
orgSlug: '', <----------- same story
orgPermissions: undefined, <----------- here, too
getToken: [AsyncFunction (anonymous)],
has: [Function (anonymous)],
debug: [Function (anonymous)],
user: undefined, <----------- should i get anything here?
organization: undefined <----------- should i get anything here?
}
* user has the permission i need from server * : true
Actual behavior:
* user info from server* : {
actor: undefined,
sessionClaims: {
...default clerk properties...
permissions: null,
...default clerk properties...
},
sessionId: 'sess_someSession',
userId: 'user_aUserId',
orgId: undefined,
orgRole: undefined,
orgSlug: undefined,
orgPermissions: undefined,
getToken: [AsyncFunction (anonymous)],
has: [Function (anonymous)],
debug: [Function (anonymous)],
user: undefined,
organization: undefined
}
* user has the permission i need from server * : false
On the client using the useUser()
hook, i am successfully getting all the signed-in user's info from Clerk.
current user's permissions: (7) ['org:sys_domains:manage', 'org:sys_domains:read', 'org:sys_memberships:manage', 'org:sys_memberships:read', 'org:sys_profile:delete', 'org:sys_profile:manage', 'my:custom:permission']
Environment
System:
OS: Linux 5.15 Ubuntu 22.04.4 LTS 22.04.4 LTS (Jammy Jellyfish)
CPU: (12) x64 Intel(R) Core(TM) i7-10710U CPU @ 1.10GHz
Memory: 10.95 GB / 14.64 GB
Container: Yes
Shell: 5.1.16 - /bin/bash
Binaries:
Node: 20.11.0 - ~/.nvm/versions/node/v20.11.0/bin/node
Yarn: 1.22.17 - /mnt/c/Users/Lum Dermaku/AppData/Roaming/npm/yarn
npm: 10.2.4 - ~/.nvm/versions/node/v20.11.0/bin/npm
pnpm: 8.15.4 - ~/.local/share/pnpm/pnpm
npmPackages:
@clerk/remix: 4.0.0-beta.43 => 4.0.0-beta.43
@remix-run/dev: ^2.8.1 => 2.8.1
@remix-run/node: ^2.8.1 => 2.8.1
@remix-run/react: ^2.8.1 => 2.8.1
@remix-run/serve: ^2.8.1 => 2.8.1
@types/react: ^18.2.20 => 18.2.74
@types/react-dom: ^18.2.7 => 18.2.23
@typescript-eslint/eslint-plugin: ^6.7.4 => 6.21.0
@typescript-eslint/parser: ^6.7.4 => 6.21.0
class-variance-authority: ^0.7.0 => 0.7.0
eslint: ^8.38.0 => 8.57.0
eslint-import-resolver-typescript: ^3.6.1 => 3.6.1
eslint-plugin-import: ^2.28.1 => 2.29.1
eslint-plugin-jsx-a11y: ^6.7.1 => 6.8.0
eslint-plugin-react: ^7.33.2 => 7.34.1
eslint-plugin-react-hooks: ^4.6.0 => 4.6.0
isbot: ^4.1.0 => 4.4.0
react: ^18.2.0 => 18.2.0
react-dom: ^18.2.0 => 18.2.0
typescript: ^5.1.6 => 5.4.3
vite: ^5.1.0 => 5.2.8
vite-tsconfig-paths: ^4.2.1 => 4.3.2
Hey @LumDermaku, I quickly wanna mention that not receiving any of org:sys_*
permissions from getAuth()
is expected. Only your custom permissions should be returned from there.
Thanks for providing us with the reproduction repo. I used both @clerk/remix@3
and the @clerk/remix@beta
I could not replicate, posting an image as example below.
The reason you were seeing a 401 error with v3 was probably dues not declaring and using the ClerkErrorBoundary
Thank you for your reply @panteliselef.
I actually need only custom permissions returned by getAuth()
, but still I am not getting the same response like yours, not even orgId
, or the default orgPermissions
. Maybe something is wrong with Clerk's dev environment? Not sure if my pub key can help.
I have added a custom prop in the session token "{permissions": "{{org_membership.permissions}}"}
but I am still getting null
for that.
Since getAuth
in loaders seem to work for you, is the <Protect>
component also working? I am sure my user has the needed permissions since with the client-side useUser()
hook I can see all permissions, including the custom ones.
For the Error Boundary, I've already tried implementing it but was still having the error. Someone from your team already recommended to upgrade to the latest beta which fixes that issues.
So with clerk, you need to specify an active organization. Easiest way to do this would be to mount the OrganizationSwitcher component and select or create an organization from the UI.
We also have this guide on how to "force" users into an organization, in case your app would only work with a populated orgId. Have a look here
@panteliselef the link you provided has a Next.js example, and for this section, there's a server example and client example. Both assume that you get an orgId
prop out of the returned object, either from the auth()
server function (that looks like the Next.js version of getAuth()
for Remix) or from the useAuth()
hook.
Either I am doing something wrong with Clerk dashboard setup or the Remix SDK is not working. I even created a custom role now, assigned my custom permission to it; Added a new user to my Org and assigned this custom role to it. Still not getting even an orgId
from the server function.
.
Thank you for your time.
@panteliselef I used the <OrganizationList />
and selected the Org i created instead of the personal one. After i clicked it, refreshed the page, now the response from Clerk is looking good in console, just like your response.
Before I begin writing the logic to automatically set one of the orgs as Active by default (this is our case), the docs say that the user will automatically have an org set as Active as soon as they accept the invitation. What type of invitation in this case? Email invites? I didn't accept any email invitations when I added an email of mine to my Org.
There will only be one Custom org that I need some specific users to be part of, and everyone added there should have it set as Active, without having to manually choose an org from our web app since this is kind of a deal breaker.
@panteliselef
I think we can somehow mark this as done or closed. For anyone interested in what i did:
I ended up writing that custom logic in a component that gets imported in root.tsx
. You can also do it in _index.tsx
component because it is rendered within the <Outlet />
of root.tsx
.
After the user signs-in, the homepage is loaded and that component does the following:
- invokes
useOrganizationList()
hook. - In a useEffect hook, i check if
userMemberships.data
array has an element who'sename
property is the same as the one i want. I invoke thesetActive()
function with the org id of the user's membership. Don't forget to add the objects, functions, variables in the dependency array of useEffect. - With a global state manager I set a boolean value which i use to render a button conditionally. In the useEffect hook i also have some custom logic to check whether the global state already has/doesn't have anything set.