Create Unique API Key For Users
Hi guys - quick question... I have have a next.js app deployed to vercel, all using SSR and auth with supabase. I am now adding in an API to that app. Thus, in doing this, I'd like to have users have their own individual API (like any other api youve used likely 🙂 and give the user the ability from my front end, to see their api and roll the key when needed. When I receive an API request for the serverless function, in that header i'll have to take this API key they have inputted and somehow use this to find the user i nthe users table with that API key... SO my question is this > I could just do this by adding an "api key" column (or secret and private key) type scenario to my users table right lol? I am just wondering if there is another best practice, or a common way to do this with supabase in terms of authenticating an "actual" key as opposed to just "matching on on file" with the user as a form of "auth".... thanks!
31 Replies
I wrote up a thing on this.
https://gist.github.com/j4w8n/25d233194877f69c1cbf211de729afb2
Gist
Implement user API keys with Supabase
Implement user API keys with Supabase. GitHub Gist: instantly share code, notes, and snippets.
@j4 Thanks man! Will check it out!
@j4 @j4 This was an awesome guide! Thanks! -- Quesiton -- Did any of you guys run into any issue when running the "create JWT" part of this? Being that the JWT is in the auth schema as mentioned above as well, am I not supposed to see the JWT's created by the users? Ive added a "select"policy as described... But any which way I go to run this >>
let { data, error } = await supabase
.rpc('create_api_key', {
id_of_user,
key_description
})
if (error) console.error(error)
else console.log(data)
When I try and run that request through API or within my JS (ive tried both) i check the logs and get a 204, but NOTHING gets created in the JWT it seems..... anyone else have this issue??
I'll have to find my project for that and test again.
@Jon3 actually, after thinking about this for a minute, you won't see the JWTs in
auth.jwts
because they're stored in Vault. The only thing in auth.jwts
are references to them. This is by design, for security. However, you can craft a request to Vault and have them returned decrypted, if you're just wanting to verify they're there.@j4 This is the part that slightly loses me... So, ive confirme when i create a new user, it will create secrets in the vault for that new user. I have added the create_api_key SQL to supabase but for some reason, I cannot seem to invoke this....

I am supposed to use this JS to invoke that "create_or_replace api_key" function in supabase right?

AS a test, Ive made an API request to supabase to tst that function being called correctly... im getting a 204 BUT, seems nothing is happenng within supabase when i make that request... nothing is added to JWT, nor is anything added to secrets... could their be some sort of policy or whatnot dis-allowing this?

Here is the code in my demo app. Works for me. This is a SvelteKit page action.
Good question. I thought I had added all of the need steps on that Gist, but.
There are no RLS policies for this in my project, because I don't use regular tables in the process.
oh ok so you create the api key when they sign in it looks like?
I was more/less jsut trying to test with an external api request to see how th ewhole JWT situation worked, but that jsut seesm to be the issue that the create_api_key doesnt seem to run when invoked
So just to confirm @j4 , when you run that create_api_key, you DO see something that pops up in the JWT table right?

I do see the request is making it to supabase, which is the strangest thing - and its getting a 204 (excuse the other 404's those were jsut testing)

yes, I see entries in the table. I assume you've double-checked the function code? Just in case you missed a character or two at either end?
Well, I built a page where if you're signed in, you can create as many keys as you'd like, delete them, etc. The function should work from wherever you call it.
Yeah i jus tcut and pasted your code
It must be something to do with the origin of the request coming from localhost or something.. maybe CORS issue...hmmm i mgonna have to troubleshoot this one
My demo is on localhost too.
hmmm

So jsut to cofnirm, it SHOULD show up in that JWT table right if the request has been performed successfully?
yep, I have three
hmmm weird
Well ill let you know if I figure it out... Also, let me know if you can think of any reason why the create_api_key wont run
Ive done a "hello world" test to some basic SQL with the same methods (Localhost, API inputs, supabse url etc.) - jsut to see if I was making the request incorrectly. Seems that though that request went through - jsu tto rule out possibl e server errors etc. But it is correct etc which means, likely that the error is something is wrong within the SQL itself (for some reason) in my supabase account

Im goign to try playing around with that and add some debug points and whatnot
Ok, I'm curious to see if you find anything.
OK so FINALLY got it to work haha
Nothing wrong with the SQL
I have a supabase "types" file in my app that had to be updated due to the addition of the new functions
So i updated that and it seems to work in my front end environemnt now
i didnt even reaslize that when I generated that types file that it pulls i nteh funtions - Ill have to liekly regenerate the fiel (whiech is no biggie) to ake sure I add all the functions correctly
For whateber reason, the request from a curl werent workign still but I think that had to do with the SQL for finding the auth.uid in the SQL - wasnt finding the user because i think that part of teh SQL is for authentication from the browser/ssr im guessing lol? no idea tho
@j4 Quick follow up - when you run a "handle" request to use the code here >>

What does your response typically look like for example
As in, when you perform this in your code - is the response supposed to be formatted a specific way where it shows the user_id??

Perhaps im testing with the wrong variable as the API key? Which column is the api_key that the user will use lol? There is ID, secret, and key_id - so its one of those jsut dont know exactly which one to use
I'm not sure I'm understanding your question. Let me explain my perspective and we can go from there.
Context: The user knows their API key and is sending it in a request header to your API server. The expectation is for you to grab the API key from the header and use it to make whatever DB request the user is asking for.
The code screenshot is just showing an example for a GET request, where you'd theoretically take the API key, authenticate a Supabase client and request the user's data from some table. The structure of the data or error response you send back to them is up to you.
The
auth.key_uid()
is a check you'd put in your RLS policy, so that it can handle authentication to the DB, based on the key, and return only that user's data.
If you go to the vault schema in your table editor, the decrypted_secret
is the JWT. It gets hashed in realtime, to form the API key.
I'm sorry about all the other verbiage.. I finally re-read your last question and saw the bit about the column name.
If you want to get an API key, then you need to call this function from a button on your frontend, say the user's dashboard or settings page: get_api_key(id_of_user text, secret_id text)
e.g. from my SvelteKit demo app
Ahhh... I had thought that code was fully pre-confiugred to look for sonething called 'table' in the api key, but i should have figured it was a placeholder for my OWN table - which is what made me confused.
So to simplify, for the inbound API request its sounds like you (and the best practice in using the supabase secret) is really to just use the users API key to create the client and then the rest of the function is as it is? So, mainly to initialize the client is what that JWT is for...
exactly
They'll technically be sending an API key, but you could always use the
exchange_api_key_for_jwt
rpc call to grab the JWT, but that's an extra network request. I just figured adding a tad bit more to an RLS policy is better than making the extra network request.Oh yeah no you definitely made it the best way possible as the added JWT auth could be needed down the line
Did you find a solution in the end? It's a nightmare currently to just let users access the API with a custom api key