Kinde and Supabase
Integrations
Integrating authentication into your backend doesn’t have to be complex. By combining Kinde for authentication and Hasura for real-time GraphQL APIs, you can build secure, scalable applications with minimal setup.
In this guide, you’ll learn how to connect Kinde with Hasura, allowing you to authenticate users, enforce row-level permissions, and safely expose data through Hasura’s GraphQL engine—all with clean and maintainable architecture.
Whether you’re building a SaaS app, a dashboard, or a data-driven product, this integration will help you focus on building features, not managing auth.
Sign in to your Hasura cloud account, and select New Project. A side panel opens.
Choose a region closest to your application, and select Create Free Project.
Rename your project by selecting the pencil icon next to the project name (optional).
Open the Env vars tab, and select New Env Var. A side panel opens.
Enter HASURA_GRAPHQL_JWT_SECRET in the key field, and enter the following JSON value in the text box.
{ "jwk_url":"https://<your_kinde_subdomain>.kinde.com/.well-known/jwks.json", "audience": "hasura"}Select Add.
Select Launch Console from the project dashboard. The Hasura console opens in a new tab.
Select the Data tab. You can connect your existing database with Hasura. In this example, we will create a free database from Neon.
Select Postgres from the list of database providers, and select Connect Neon Database.
A pop-up window will open, prompting you to sign up for a free account for Neon.
You will be taken to your default database.
Select Create Table to begin creating a new table.
Enter users for the Table Name. Then add the following columns:
id with the type of Text
name with the type of Text
last_seen with the type of Timestamp and a default value of now()
Scroll down and select id from the Primary Key dropdown, then select Add Table.
This will make the id column as the primary key of this table.
Select the Permissions tab, and enter user under the Role textbox. This creates a new role in your database.
Select the pencil icon in the select cell of user row. A new option panel opens up.
In the Row select permissions, select With custom check and enter the following code logic. This only allows the user to see their own data.
{"id":{"_eq":"X-Hasura-User-Id"}}On the same panel, under Column select permissions, select Toggle All to give the user access to all the columns.
Select Save Permissions.
Fork the workflow base template repo into your GitHub account by selecting Use this template > Create a new repository
Clone the repo into your computer with the following Git command:
git clone https://github.com/your_github_username/workflow-base-template.gitChange into the directory:
cd workflow-base-templateRemove the sample workflow with the command:
rm -rf kindeSrc/environment/workflows/postUserAuthenticationCreate a new workflow with the following command.
You can name it anything that resonates with your project structure. We will call it hasuraCustomClaim.
mkdir kindeSrc/environment/workflows/hasuraCustomClaimtouch kindeSrc/environment/workflows/hasuraCustomClaim/Workflow.tsOpen the Workflow.ts file with your favorite code editor (e.g., VS Code), add the following code into the file, and save changes:
import { onUserTokenGeneratedEvent, WorkflowSettings, WorkflowTrigger, accessTokenCustomClaims,} from "@kinde/infrastructure"
// The setting for this workflowexport const workflowSettings: WorkflowSettings = { id: "onUserTokenGeneration", name: "Hasura Custom Claim", trigger: WorkflowTrigger.UserTokenGeneration, failurePolicy: { action: "stop", }, bindings: { "kinde.accessToken": {}, // required to modify access token claims url: {}, // required for url params },}
// The workflow code to be executed when the event is triggeredexport default async function Workflow(event: onUserTokenGeneratedEvent) { const userId = event.context.user.id
// set the types for the custom claims const accessToken = accessTokenCustomClaims<{ "https://hasura.io/jwt/claims": { "x-hasura-user-id": string "x-hasura-default-role": string "x-hasura-allowed-roles": string[] } }>()
const customClaim = { "x-hasura-user-id": userId, "x-hasura-default-role": "user", "x-hasura-allowed-roles": ["user"], }
// Add the users company name to the access token accessToken["https://hasura.io/jwt/claims"] = customClaim}Optional: Install the Kinde infrastructure dependency with the following bash command for TypeScript intellisense.
npm installRun the following git commands in your terminal to push the changes to your GitHub repo:
git add .git commit -m "add Hasura custom claims workflow"git pushThis workflow will add the custom Hasura claims to your Kinde-issued JWT in the following format:
"https://hasura.io/jwt/claims": { "x-hasura-allowed-roles": [ "user" ], "x-hasura-default-role": "user", "x-hasura-user-id": "<kinde_user_id>" },Sign into your Kinde dashboard and select Workflows from the sidebar.
If you already have your workflow repo connected, go straight to step 4.
Select Connect repo > Connect GitHub and follow the onboarding flow to authorize Kinde to access your GitHub account.
From the dropdown, select your GitHub repository that contains the Kinde workflow code, choose the main branch, and click Next.
If you already have a repo connected and want to change it, go to Settings > Git repo > Change repo.
Select Sync code to pull your latest workflow code from GitHub into your Kinde project. You’ll now see your workflow listed inside the dashboard.
From the Kinde home page, select Settings > APIs > Add API.
Type a name for the API (e.g., Hasura).
Enter hasura in the Audience field and select Save. This enables your app to add a custom audience claim in your JWT for security.
From the Kinde home screen, select View details on your application.
Go to APIs, you will see the Hasura API you created earlier.
Select the three dots next to your Hasura API and select Authorize application.
This will add hasura into your Kinde JSON web token inside the aud field. It will allow Hasura to authorize a user using Kinde.
Open your Kinde React app with your favorite code editor (e.g., VS Code)
Open src/main.tsx file, replace the contents with the following code, and save changes.
We are adding the audience="hasura" to check for the custom claim when authenticating.
import { createRoot } from "react-dom/client"import { KindeProvider } from "@kinde-oss/kinde-auth-react"import "./index.css"import App from "./App.tsx"
createRoot(document.getElementById("root")!).render( <KindeProvider clientId={import.meta.env.VITE_KINDE_CLIENT_ID} domain={import.meta.env.VITE_KINDE_DOMAIN} logoutUri={import.meta.env.VITE_KINDE_LOGOUT_URL} redirectUri={import.meta.env.VITE_KINDE_REDIRECT_URL} audience="hasura" // When running local against a custom domain, include the line below // useInsecureForRefreshToken={true} > <App /> </KindeProvider>)Open src/components/LoggedIn.tsx file, replace its contents with the following code, and save changes. This:
token to store the JWT token value.import { useKindeAuth } from "@kinde-oss/kinde-auth-react"import { LogoutLink, PortalLink } from "@kinde-oss/kinde-auth-react/components"import { useState } from "react"
export default function LoggedIn() { const { user, getAccessToken } = useKindeAuth() const [token, setToken] = useState("token-not-loaded")
const fetchAccessToken = async () => { try { const accessToken = await getAccessToken() setToken(accessToken ? accessToken : "no-token-found") } catch (err) { console.log(err) } }
return ( <> <header> <nav className="nav container"> <h1 className="text-display-3">KindeAuth</h1> <div className="profile-blob"> {user?.picture !== "" ? ( <img className="avatar" src={user?.picture} alt="user profile avatar" /> ) : ( <div className="avatar"> {user?.givenName?.[0]} {user?.familyName?.[0]} </div> )} <div> <p className="text-heading-2"> {user?.givenName} {user?.familyName} </p> <ul className="c-user-menu"> <li> <PortalLink>Account</PortalLink> </li> <li> <LogoutLink className="text-subtle">Sign out</LogoutLink> </li> </ul> </div> </div> </nav> </header>
<main> <div className="container"> <div className="card start-hero"> <p className="text-body-2 start-hero-intro">Woohoo!</p> <p className="text-display-2"> Your authentication is all sorted. <br /> Build the important stuff. </p> </div> <button onClick={fetchAccessToken}> Get Access Token</button> <br /> <code>{token}</code>
<section className="next-steps-section"> <h2 className="text-heading-1">Next steps for you</h2> </section> </div> </main>
<footer className="footer"> <div className="container"> <strong className="text-heading-2">KindeAuth</strong> <p className="footer-tagline text-body-3"> Visit our{" "} <a className="link" href="https://kinde.com/docs"> help center </a> </p>
<small className="text-subtle"> © 2025 KindeAuth, Inc. All rights reserved </small> </div> </footer> </> )}Run the project on your local machine with the following command npm run dev .
Go to https://localhost:3000 and sign in or sign up for a new account.
Select Get Access Token, and copy the JWT for testing the Hasura implementation.
Sign in to your Hasura cloud account, and select Launch Console next to your project. Hasura Cloud Console will launch in a new tab.
Go to the API tab.
Test your GraphQL endpoint with the following steps:
x-hasura-admin-secret.KEY called Authorization.Bearer <your_jwt_token> in the VALUE field.Enter the following query in the text box:
query GetUsers { users { id name last_seen }}Select the play button.
You will see an empty users array. Hasura is successfully authorizing your request.
{ "data": { "users": [] }}To verify that Hasura is correctly enforcing user-level access, you can insert some test data tied to a Kinde user.
Find a User ID in Kinde. Go to Kinde > Users > View Profile and copy the user ID.
Add the Kinde User ID to your database. Add a record to your users table using the Kinde user ID you just copied.
Run a query with that user’s JWT. When you authenticate using that user’s JWT and run a query in Hasura, you should now see the correct user data returned:
{ "data": { "users": [ { "id": "kp_1234", "name": "Tamal Anwar Chowdhury", "last_seen": "timestamp" } ] }}Hasura extracts the user ID from the JWT and applies row-level permissions to return only the relevant data.
To try with a different user, log out and sign in (or sign up) with a different Kinde user. Use their JWT to query the same Hasura endpoint:
{ "data": { "users": [] }}You’ll notice that no data is returned—because the new user doesn’t have a corresponding record in the database.
This optional test confirms that Hasura is securely limiting access to each user’s own data, based on the JWT provided during authentication.
Now that authentication is working correctly, you can move on to connecting your frontend application:
If you’re using the Kinde React SDK, retrieve the JWT using the getAccessToken() method.
const token = await getAccessToken()Use this token to make authenticated requests to your Hasura backend.
This setup ensures secure and scoped access to your data, based on each authenticated user’s identity.
You’ve set up a secure integration between Kinde and Hasura, enabling powerful authentication and authorization for your application.
This approach ensures that each user only accesses their own data, helping you enforce strict access control while maintaining a smooth developer experience.
As always, make sure to:
If you need help, reach out to us at support@kinde.com. You can also connect with the team and other developers in the Kinde Slack community or Discord. We’re here to support you.