whoami
whoami2y ago

Passing clerk generated token to python client?

I generated a token from clerk in react and passed it to a Python service
token = ... # passed from clerk in react
convex = ConvexClient(os.environ["CONVEX_URL"])
convex.set_auth(token)
chat = convex.query(
"chats:get", {'id': Id('chats', input.chat_id)}
) # this line failed
token = ... # passed from clerk in react
convex = ConvexClient(os.environ["CONVEX_URL"])
convex.set_auth(token)
chat = convex.query(
"chats:get", {'id': Id('chats', input.chat_id)}
) # this line failed
These are errors I got
Traceback (most recent call last):
File "/Users/miniforge3/envs/askstring/lib/python3.11/site-packages/convex/__init__.py", line 94, in _request
r.raise_for_status()
File "/Users/miniforge3/envs/askstring/lib/python3.11/site-packages/requests/models.py", line 1021, in raise_for_status
raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 401 Client Error: Unauthorized for url: https://clean-dinosaur-909.convex.cloud/api/query
Traceback (most recent call last):
File "/Users/miniforge3/envs/askstring/lib/python3.11/site-packages/convex/__init__.py", line 94, in _request
r.raise_for_status()
File "/Users/miniforge3/envs/askstring/lib/python3.11/site-packages/requests/models.py", line 1021, in raise_for_status
raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 401 Client Error: Unauthorized for url: https://clean-dinosaur-909.convex.cloud/api/query
I am using dev env and I am pretty sure the url I used in both frontend and backend is consistent. And what's more interesting is, when I removed convex.set_auth(token), the code ran through. I am really confused right now, what are we doing with the token if this is not required? Why providing a token will instead lead to the auth failure?
25 Replies
ballingt
ballingt2y ago
What does your "chats.get" query look like? Note that await auth.getUserIdentity(); evaluates to null when the user is not authenticated, at which point your function needs to throw an error an error if auth is bad. But if the auth token is expired (which happens every 60 seconds by default with Clerk!) auth.getUserIdentity() will throw. This could result in the behavior you see here; null is returned for "user is not authenticated," while an error is thrown for "user appears to have attempted to authenticate, but their token is expired." For Python a manually implemented API token is often a better fit, unless the Python code always runs within a few seconds of being handed an updated Clerk auth token.
whoami
whoamiOP2y ago
I generated a new clerk token and passed it to the python client so I don't think expiration is a problem? (I've also extended its time)
ballingt
ballingt2y ago
What does your chat.get function look like? When you say "generated a new clerk token" what kind of token is this, an OpenID token from a browser login flow?
whoami
whoamiOP2y ago
export const get = query({
args: {
id: v.id('chats'),
},

handler: async ({ db }, { id }) => {
// TODO: commented out for now, but we need to figure out how to do auth in Python properly
// const identity = await auth.getUserIdentity()
// if (!identity) {
// throw new Error('Unauthenticated call to query')
// }

const chat = await db.get(id)
if (!chat) {
throw new Error('Chat not found')
}

...
},
})
export const get = query({
args: {
id: v.id('chats'),
},

handler: async ({ db }, { id }) => {
// TODO: commented out for now, but we need to figure out how to do auth in Python properly
// const identity = await auth.getUserIdentity()
// if (!identity) {
// throw new Error('Unauthenticated call to query')
// }

const chat = await db.get(id)
if (!chat) {
throw new Error('Chat not found')
}

...
},
})
there you go 1. When I commented out the await auth.getUserIdentity() part, the function can run without auth, is that expected? 2. When await auth.getUserIdentity() is uncommented, with the token generated from clerk, I got the error 401 Client Error: Unauthorized for url: ... (I don't understand why, can I directly take the freshly generated token from clerk with (const { getToken } = useAuth()) and pass it to Python client?)
ballingt
ballingt2y ago
1. Yep, endpoints don't require auth so you can provide a signed-out experience. If you want to enforce auth, you need the four lines you have commented out. 2. Yeah you should be able to. I'll try to repro this.
whoami
whoamiOP2y ago
thanks Tom so technically, I can encode the token of my own in Python and pass it to convex? I thought convex would have a place for keep my secret for verifying the token I sent to it
ballingt
ballingt2y ago
No, the auth token needs to be an OpenID identity token, but you can add an extra argument that you could check, say an API token you managed yourself in another table @whoami I'm seeing what I think is the same thing, so it's not just you, I think this is some kind of issue on our end. The JavaScript HTTP client works for me, but Python isn't — looking into it
whoami
whoamiOP2y ago
thanks
ballingt
ballingt2y ago
@whoami I take it back, as long as I'm quick enough to paste in my Clerk token it's working for me.
No description
ballingt
ballingt2y ago
I don't get a 401 unless it takes longer than 60 seconds for me to refresh the page to get a new token, copy the token out of my browser and call client.set_auth() (Python) and client.setAuth() (JS)
ballingt
ballingt2y ago
If I do take too long, I get a "TokenExpired" 401 in Python and JavaScript
No description
ballingt
ballingt2y ago
Your "Unauthorized for URL" sounds different, maybe you're using a dev deployment for your browser and a production deployment for Python, and you haven't added this auth provider to both deployments?
whoami
whoamiOP2y ago
I think I am using dev url for both, what does Unauthorized for URL really mean? What was going on behind the scene?
ballingt
ballingt2y ago
ah I think these JWT tokens are specific to a deployment, so even if you'd added the same auth provider to both deployments, you can't use a token for one for the other
whoami
whoamiOP2y ago
so react / python are considered as different deployments?
ballingt
ballingt2y ago
no just dev vs prod
whoami
whoamiOP2y ago
you meant dev/pro ic
ballingt
ballingt2y ago
you can see double-check in https://jwt.io/ that you have the right "azp" field, but it sounds like you're not confusing these two
whoami
whoamiOP2y ago
I have "azp": "http://localhost:3000"
ballingt
ballingt2y ago
oh right, that's where you got it — I don't believe that's enforced I believe "Unauthorized for URL" is the default message that Requests (a dependency of the Convex Python client) prints when no message is specified for a 401
whoami
whoamiOP2y ago
Is that because convex checks for where the request is sent from based on the azp field? in my case my react is running on 3000 port while my python server is running on 8000, causing the latter being rejected because the azp doesn't match
ballingt
ballingt2y ago
I think that was a misdirect by me, I believe the azp field is not checked. It shouldn't matter from Python, when the request is made (even if you make it in a Flask handler or whatever) the port the python server is listening on isn't send to Convex in the convex.mutation("sendMessage", {}) HTTP request. Have you tried authenticated requests in the browser, do those work?
whoami
whoamiOP2y ago
const token = await getToken({ template: 'convex' }) ah I think the problem is I wasn't using the right template now it is working normally
ballingt
ballingt2y ago
good to hear, I'll think about better errors here; if it's a token from Clerk maybe we can guess that this is the problem and put that in an error message
whoami
whoamiOP2y ago
yea I think once convex has validated the token is from clerk, direct the user to check their token, especially if they have used the right template for generating it could be helpful thank you Tom for your time and patience

Did you find this page helpful?