#Custom Claims to Convex Auth Token
145 messages · Page 1 of 1 (latest)
It's supported by Convex, but not yet in Convex Auth. Relevant issue here: https://github.com/get-convex/convex-auth/issues/25
Is there any way to do what you're trying to do via the users table in your convex db?
Poking at the "having it as a token claim would make it much easier" part
I can achieve the same thing through the database, but this is just because of my requirments and how profiles work in my app (you can either be a provider or a client) but I imagine for other projects I would use some other tool just because this is missing (say a SASS where you can log under multiple organization and have different role under each one)
@hard depot Is there an API you're imagining for this?
import {signJwt} from "convex"
// mutation implementation
ctx.auth.token = signJwt({...ctx.auth.token.claims, ...otherClaims})
maybe?
or a readJwtClaims, since the token would be a string
here signJwt would take a Record<string|number, string|number>
which would be the same thing that readJwtClaims would return
you can even make them type safe
const readJwtClaims = <T extends EmptyObject>(token: string): T & {iat: number, exp: number} => {
///impl
}
const signJwt = <T extends EmptyObject>(): string & {__claims: T & {iat: number, exp: number}} => {//impl }
for even more typesafety we could have the __claims be a unique symbol as to not confuse people
// second signature
const readJwtClaims = <T extends string & {__claims: }>(token: T): T => {
///impl
}
I might be too in the details here, but assigning to ctx.auth.token doesn't make sense to me. Maybe backing up, what would it look like to use this? Would you specify a callback to auth that returns custom claims, and it's allowed to make arbitrary network calls? Or just a query, it can read from teh database? Where are you getting the extra information you want to stuff into the JWT?
I would like to know what profile the user is workign with and having it asa token claim would make it much easier
What would be easier about this, it'd be easier to access on the client side?
youre right in the ctx.auth.token assignment
it would be easier to assign what data the user has access to
if say a task is assigned to a user
and that user works under multiple organizations
it would be better to have the assigned to be a profile id instead of the user
id
thats what im going for here
as for the token assignment
maybe something like ctx.auth.addClaims
ctx.auth.removeClaims
and ctx.auth.setClaims
you already have a claim in the auth token which i think is the identifier field
ctx.auth.getUserIdentity
that is already a token claim
maybe we coulf have ctx.auth.getClaim(name: string)
so we keep that consistent api design
and since the identity claim is the most important + sure to be for each token it can have its own shorthand method
what do you think?
database
Are you putting this on ctx.auth because you're imagining that any function can add these? it's tough because most mutations/queries only have read access to these claims
It's only the convex auth library that actually mints the tokens
we shoudl start with a way to set claims when the token is created, not trying to dynamically add more claims later
yeah no need to change getUserIdentity(), that already works with custom claims
the new API would be for "how to we say what info should show up in a JWT," and I assume that should be a function you pass to auth()
well there needs to be a way to edit the claims in flight, even if creating a new token and replacing that token
again with the profiles example
after loggin in you would have to select an active profile
like slack workspaces
which workspace will you be working on today?
the custom claim here would be the profile: profile._id under which org you choose to work today
and i could read that when assigning tasks and so on
while you can use the current user id
the profile makes for clearer and actually separated workspaces
I don't follow this, replacing the token?
as you can transfer profiles to other users
yeah with a copy of all the claims in the previous plus a new one
it would function as refreshing it aswell
so we'd need a subscription on the client to something on the convex backend, so it knows when to request a new token because something has changed
what im trying to say here is it would be nice to either edit the claims of the token in function ( which ofc would come with replacing it )
they wouldn't really be claims anymore, claims means they've been signed by the provider
you have to mint a new token to add claims to it
depends on what you consider claims
im not to sure if that is totally what im trying to describe
basically adding fields to the decoded token
not sure if thats called a claim
ah and you don't care if they're on the client, you just want them on the convex deployment?
or is the point that they'd make it to the frontend
the client doesnt care nor know about them
the point is that they make it to the front end
so the front end can send them back to be read on other functions
basically same thing that getAuthUserId
but for other fields
getAuthClaim("profile")
in this case
or any other
and setAuthClaim
OK so the flow would need to be
- client knows that its profile or org or whatever has probably changed, so requests a new token from the convex backend auth functions
- the convex backend auth functions gather the new data, via custom query or action specified in auth, and mints a new jwt with these claims
- the new JWT is returned to the frontend
- now the frontend can decode the jwt and see this new info, and
- the new jwt is sent over the convex websocket to the connection, so queries can rerun with this new getUserIdentity() return value
not quite
let me explain the flow and maybe that helps you a bit
- user signs in (signIn)
- the user has been redirected to a select-profile screen where he views all active profiles
- the user selects a profile
- the selectProfile is called which takes in a profileId field
- a new token is issued which besided the standard claims, has a new claim, the profile field
6, user uses the dashboard and he does stuff in it, say creating an issue for the dev team - on the createIssue mutation, a function that can read the users claims exists (similiat to getAuthUserId) but that pulls any arbitrary field ( the porfile one in this case)
- the mutation is made and the issue is created in the db with the createdBy: profileId where profileId = getAuthClaim(ctx.auth, "profile") // or something instead of ctx.auth
the frontend doesnt interact with the token
the front end knows when the token changes ( it triggered that change )
when a claim is edited the storage mechanism knows to save this new token
maybe the function can bring some metadata back or as you said the front end can be notified
but front end cant decode it or read it ( doesnt need to + ight be a security risk)
what's selectProfile, a mutation?
ok so that sets some data inthe database, so between step 4) and 5) the frontend requests a new token from the convex backend
and that token reads the data set by that mutation and knows to set a custom claim about which profile is active
yeah selectProfile would read data in the db to make sure that profile exists + is under that user and would set the profile field on the token = to the value it came throughcas an arg
selectProfile can't issue a new token, only the frontend can trigger that
tokens are immutable things that the frontend needs to request from the backend
so the backend cant choose to send a token as a response.body
and have the front end set that token as a header in future requests
thats what im trying to achieve
but i do acknowledge that 1. might be hard with websockets, 2. might be hard with convexes impl
sure the frontend can call an http endpoint and get a token back, that's how getToken works today
i see
than an http action could be the select-profile
less ideal than it being function
but again how would i go about reading these custom values set by the http action
yeah this is possible, it just seems indirect if your goal is just to make this available in convex functions
This isn't a convex impl thing particularly, the same thing is hard in clerk
i see
if you want more claims, you have to wait for the frontend to request a new jwt
ive done these before with fastify for the exact same scenario
moving that beckend to convex
thats why im asking
but with fastify being http and not having to integrate all auth frameworks it was a bit easier
why do you want it on the jwt, because ctx.auth.getUserIdemtity() is more convenient than calling a helper to read that from the db?
because i dont have to send it as an argument in every request
I'm wondering if what you really want are sessions, or customizable auth that can do db lookups and return another record
following the sign in
oh so this is about not sending in the arg, cool yeah I'd call that sessions
yeah sessions can be used for the same thing
do you care if the user cheats and sends something else? that's what's special about a jwt, you can't fake it
but it can be achieved with stateless jwt as well
yeah thats why i wanted it in a signed jwt made in the backend, sent to the frontend and stored there for future requests
i think we want sessions, now trying to drill down on whether this is state set on the server or the client or both
till the token expires like any other\
This sounds like sessions then, either that or you'd like to be able add columns to the users table
but probably more like sessions, just for the currently logged in session
(so could be different in another browser)
yeah sessions, having columns that save the current active profile is less than ideal
and plus cant have to active profiles at the same time with that approach
because a user can login in on two devices and having the profile be part of the user record would break that
yeah it should be on the sessions table, not the users table
This stuff is too difficult currently, agree. You can customize the sessions table but you have to write your own apis for it today
but the docs said that not to edit taht, i guess i can have another table reference the session
ah yeah, ok that's the thing we need to fix
convex auth gives you sessions but they're pretty opaque
yeah shouldve though of the sessions table, totally exited my minf
i can have another profile_sessions table that refernces a session and has a profile field
it's common enough would be reasonable to let you customize, there's some customization work on users table we'd like to do too
you guys should add a function that gets the sessionId somehow than
yeah customizing the users table was a pain
especially the damned indexes which dont follow youre own naming conventions 😭
you should open an issue for that, it's related to some existing PRs to make users table not have these restrictions
email and phone
ok i will
ill probs also have a look at the prs and might try to put one in myself
thanks for talking it out, I get it
really enjoy this project