Learn how to set up Supabase auth for both the frontend and backend of your application using a JWT - JSON web token.
Supabase is, as they describe it, an open-source alternative to Firebase.
It is essentially a managed Postgres environment with additional functionalities such as auth, storage, and real-time capabilities.
In this README, I will show you how to set up Supabase auth on both the front end and back end. We're going to use Next.js as an example here. We're going to use this example repo for demonstration purposes.
I'm going to assume that you have at least some familiarity with Supabase. Prior knowledge of Next.js will be helpful, but it won't be necessary to understand this readme.
We're going to use npm, so make sure it's installed in your system. Make sure to sign up for a Supabase account, as well.
Once you have the prerequisites, clone this repo to your local environment.
Make a new organization and project on Supabase's website.
Then, go into the directory of your clone, and copy .env.template to .env.
Go to Supabase, go into your project -> settings -> API. Copy and paste your keys into your newly created .env file.
Then, run:
npm install
npm run dev
Then, you should be able to see something like this:
The following paths are available in this app:
/ (index)
: it has the main form for sending data to the database. Redirects to /signup if the user is not signed in.
/signup, /signin
: the signup and sign in forms. Redirects to index if the user is signed in.
/logout
: it logs out the user.
You should now be able to sign up, sign in, and sign out using these paths.
In this example, we're going to create a simple job posting web app.
So, we're going to create a table called jobs.
Go to your project and click create a new table.
Call it jobs, enable row level security, and add the following fields:
created_by should be a foreign key to the auth.users table.
If this was a real app, it would probably need more columns, but let's keep it simple here.
Notice that we have the is_public column, and it's set to false by default.
The assumption here is that when a user posts a new job posting on the site, we don't want to make it show up automatically on the site. We want to have some kind of approval process so that website admins can decide which job postings are legit enough to show on the site.
Go to the root path, and try submitting the form!
It should have worked! You can verify it on Supabase's dashboard:
How did that happen?
Go to src/pages/index.tsx, and you'll see this function to handle form submission:
const onSubmit = async (data: any) => {
console.log(data);
let result;
try {
result = await fetch('/api/submit_job_posting', {
headers: {
Authentication: session.access_token,
},
method: 'POST',
body: JSON.stringify(data),
});
} catch (err) {
console.log(err);
}
console.log(result);
reset();
};
The important part is, we're sending Supabase's access token / JWT in the headers of the request.
That's this part:
headers: {
Authentication: session.access_token,
},
A JWT is, in short, a key to verify that the user is signed in.
By sending it to our backend code on our API path, we're able to send the backend proof that the user is signed in.
Go to src/pages/api/submit_job_posting.js. Let's try and understand this file, section by section.
const supabase = createClient(supabaseUrl, supabaseAnonKey);
const supabaseSecret = createClient(supabaseUrl, supabaseServiceKey);
^These two lines at the top create clients in two different ways.
The first one is the same type of client as the front end - it's safe to use it publicly:
const supabase = createClient(supabaseUrl, supabaseAnonKey);
The second one uses Supabase's secret service key. It has admin-level access to your data:
const supabaseSecret = createClient(supabaseUrl, supabaseServiceKey);
Then:
const input_data = JSON.parse(req.body);
const jwt = req.headers.authentication;
^these two lines parse the data that was sent from the front end and then retrieve the JWT from the headers.
const { data: user, userError } = await supabase.auth.api.getUser(jwt);
const id = user.identities[0]['id'];
^Then, these two lines retrieve the logged-in user, and we get their ID.
const { data, error } = await supabaseSecret.from('jobs').insert([input_data]);
^Finally, this line inserts the data in our table using admin-level access.
Another potential way to approach this is - we could insert data directly from the front-end, using row-level security. However, with that, each user will be able to manually set "is_public" to true, which is not what we want.
In order to avoid that, we need to use admin-level access so that we can set it to false to begin with, using our backend code.
Thank you for reading this README!
If you'd like to learn more about how this particular pattern could be used in a real-world application, feel free to check out this open-source project of mine that uses it.