#Custom Claims to Convex Auth Token

145 messages · Page 1 of 1 (latest)

hard depot
#

Pretty much tht etitle, can I add any extra claims to a cnvex auth issued token? I'm using one user multiple profiles so I would like to know what profile the user is workign with and having it asa token claim would make it much easier

sick plume
#

Poking at the "having it as a token claim would make it much easier" part

hard depot
#

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)

unborn bear
#

@hard depot Is there an API you're imagining for this?

hard depot
#

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
}

unborn bear
# hard depot import {signJwt} from "convex" // mutation implementation ctx.auth.token = sig...

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?

hard depot
#

youre right in the ctx.auth.token assignment

hard depot
#

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?

unborn bear
#

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()

hard depot
#

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

unborn bear
#

I don't follow this, replacing the token?

hard depot
#

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

unborn bear
#

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

hard depot
#

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 )

unborn bear
#

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

hard depot
#

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

unborn bear
#

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

hard depot
#

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

unborn bear
#

OK so the flow would need to be

  1. client knows that its profile or org or whatever has probably changed, so requests a new token from the convex backend auth functions
  2. 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
  3. the new JWT is returned to the frontend
  4. now the frontend can decode the jwt and see this new info, and
  5. the new jwt is sent over the convex websocket to the connection, so queries can rerun with this new getUserIdentity() return value
hard depot
#

not quite

#

let me explain the flow and maybe that helps you a bit

#
  1. user signs in (signIn)
  2. the user has been redirected to a select-profile screen where he views all active profiles
  3. the user selects a profile
  4. the selectProfile is called which takes in a profileId field
  5. 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
  6. 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)
  7. 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)

unborn bear
#

what's selectProfile, a mutation?

hard depot
#

yeah

#

it would make the most sense for it to e be a mutation

unborn bear
#

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

hard depot
#

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

unborn bear
#

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

hard depot
#

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

unborn bear
#

sure the frontend can call an http endpoint and get a token back, that's how getToken works today

hard depot
#

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

unborn bear
#

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

hard depot
#

i see

unborn bear
#

if you want more claims, you have to wait for the frontend to request a new jwt

hard depot
#

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

unborn bear
#

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?

hard depot
#

because i dont have to send it as an argument in every request

unborn bear
#

I'm wondering if what you really want are sessions, or customizable auth that can do db lookups and return another record

hard depot
#

following the sign in

unborn bear
#

oh so this is about not sending in the arg, cool yeah I'd call that sessions

hard depot
#

yeah sessions can be used for the same thing

unborn bear
#

do you care if the user cheats and sends something else? that's what's special about a jwt, you can't fake it

hard depot
#

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

unborn bear
#

i think we want sessions, now trying to drill down on whether this is state set on the server or the client or both

hard depot
#

till the token expires like any other\

unborn bear
#

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)

hard depot
#

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

unborn bear
#

oh ok say more

#

why not columns?

#

oh not on the years table, yeah agree

hard depot
#

because a user can login in on two devices and having the profile be part of the user record would break that

unborn bear
#

yeah it should be on the sessions table, not the users table

hard depot
#

yeah the sessions table

#

can work

#

it can work great actually

unborn bear
#

This stuff is too difficult currently, agree. You can customize the sessions table but you have to write your own apis for it today

hard depot
#

but the docs said that not to edit taht, i guess i can have another table reference the session

unborn bear
#

ah yeah, ok that's the thing we need to fix

#

convex auth gives you sessions but they're pretty opaque

hard depot
#

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

unborn bear
#

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

hard depot
#

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 😭

unborn bear
#

you should open an issue for that, it's related to some existing PRs to make users table not have these restrictions

hard depot
#

email and phone

#

ok i will

#

ill probs also have a look at the prs and might try to put one in myself

unborn bear
#

thanks for talking it out, I get it

hard depot
#

really enjoy this project