Getting started

1. Create a new remix project

npx create-remix@latest

2. Install the Kinde Remix SDK into your project

npm i @kinde-oss/kinde-auth-remix-sdk

3. Set up environment variables

Values for environment variables can be found in your Kinde app under “Application Details”.

// .env
KINDE_CLIENT_ID=<your-client-id> (found in Kinde app)
KINDE_CLIENT_SECRET=<your-client-secret> (found in Kinde app)
KINDE_ISSUER_URL=https://<your-kinde-subdomain>.kinde.com  (found in Kinde app)
KINDE_SITE_URL=http://localhost:3000
KINDE_POST_LOGOUT_REDIRECT_URL=http://localhost:3000
KINDE_POST_LOGIN_REDIRECT_URL=http://localhost:3000

SESSION_SECRET=<your-secret>

4. Set up authentication routes

Create file app/routes/kinde-auth.$index.tsx

import { handleAuth } from "@kinde-oss/kinde-auth-remix-sdk";
import { LoaderFunctionArgs } from "@remix-run/node";

export async function loader({ params, request }: LoaderFunctionArgs) {
  return await handleAuth(request, params.index);
}

5. Start authenticating!

Authenticate users by redirecting them to /kinde-auth/login, /kinde-auth/register and /kinde-auth/logout with RemixLinks.

Use getKindeSession inside the Remix loader to get the user data from Kinde.

import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import { getKindeSession } from "@kinde-oss/kinde-auth-remix-sdk";
import { Link, useLoaderData } from "@remix-run/react";
import { json } from "@remix-run/node";

export const meta: MetaFunction = () => {
  return [
    { title: "New Remix App" },
    { name: "description", content: "Welcome to Remix!" },
  ];
};

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const { getUser } = await getKindeSession(request);
  const user = await getUser();
  return json({ user });
};

export default function Index() {
  const data = useLoaderData<{ user: KindeUser }>();

  return (
    <div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
      <h1>Welcome to Remix (with Kinde)</h1>

      {data.user ? (
        <>
          <Link to={"/kinde-auth/logout"}>
            <button>Logout</button>
          </Link>
          <code>
            <pre>{JSON.stringify(data.user, null, 2)}</pre>
          </code>
        </>
      ) : (
        <>
          <Link to={"/kinde-auth/login"}>
            <button>Login</button>
          </Link>

          <Link to={"/kinde-auth/register"}>
            <button>Register</button>
          </Link>
        </>
      )}
    </div>
  );
}

Protecting routes

In the loader, you can check if the user exists and then handle route protection there.

In this example I redirect the user to login if there is no logged in data.

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const { user } = await getKindeSession(request);

  if (user === null) {
    throw redirect("/kinde-auth/login");
  }

  return json({ user });
};