Skip to content

NextJS App Router SDK v1

This SDK is for developers already using the NextJS SDK v1.8.25 or earlier document. It is relevant for NextJS version 13+ and uses Server Side Components and App Router.

Other document versions

Link to this section

Register for a Kinde account

Link to this section

If you haven’t already got a Kinde account, register for free here (no credit card required). Registering gives you a Kinde domain, which you need to get started, e.g. yourapp.kinde.com.

You can also view the NextJS docs and NextJS starter kit in GitHub.

Terminal window
npm i @kinde-oss/kinde-auth-nextjs@1
yarn add @kinde-oss/kinde-auth-nextjs@1
pnpm i @kinde-oss/kinde-auth-nextjs@1

Set callback URLs

Link to this section
  1. In Kinde, go to Settings > Applications > [Your app] > View details.
  2. Add your callback URLs in the relevant fields. For example:
  3. Select Save.

If you would like to use our Environments feature as part of your development process. You will need to create them first within your Kinde account. In this case you would use the Environment subdomain in the code block above.

Configuring your app

Link to this section

Environment variables

Link to this section

Put these variables in your .env.local file. You can find these variables on your Kinde Settings > Applications > [Your app] > View details page.

  • KINDE_CLIENT_ID - Your business’s unique ID on Kinde
  • KINDE_CLIENT_SECRET - Your business’s secret key (do not share)
  • KINDE_ISSUER_URL - your kinde domain
  • KINDE_SITE_URL - where your app is running
  • KINDE_POST_LOGOUT_REDIRECT_URL - where you want users to be redirected to after logging out. Make sure this URL is under your allowed logout redirect URLs.
  • KINDE_POST_LOGIN_REDIRECT_URL - where you want users to be redirected to after authenticating.

Replace the information in the <angle brackets> with your own information. You might also set different URLs depending where your project is running. They need to be the same as the callback URLs you entered in Kinde, above.

Terminal window
KINDE_CLIENT_ID=<your_kinde_client_id>
KINDE_CLIENT_SECRET=<your_kinde_client_secret>
KINDE_ISSUER_URL=https://<your_kinde_subdomain>.kinde.com
KINDE_SITE_URL=http://localhost:3000
KINDE_POST_LOGOUT_REDIRECT_URL=http://localhost:3000
KINDE_POST_LOGIN_REDIRECT_URL=http://localhost:3000/dashboard

Create the following file src/app/api/auth/[kindeAuth]/route.js inside your NextJS project. Inside the file route.js put this code:

import {handleAuth} from "@kinde-oss/kinde-auth-nextjs/server";
export async function GET(request, {params}) {
const endpoint = params.kindeAuth;
return await handleAuth(request, endpoint);
}

This will handle Kinde Auth endpoints in your NextJS app.

Important! Our SDK relies on this file existing in this location specified above.

Integrate with your app

Link to this section

Sign up and sign in

Link to this section

The SDK ships with <LoginLink> and <RegisterLink> components which can be used to start the auth flow.

import {RegisterLink, LoginLink} from "@kinde-oss/kinde-auth-nextjs/server";
...
<LoginLink>Sign in</LoginLink>
<RegisterLink>Sign up</RegisterLink>

This is implemented in much the same way as signing up or signing in. A component is provided for you.

import {LogoutLink} from "@kinde-oss/kinde-auth-nextjs/server";
...
<LogoutLink>Log out</LogoutLink>

Register your first user by signing up yourself. You’ll see your newly registered user on the Users page in Kinde.

View user profile

Link to this section

You can get an authorized user’s profile from any component using the Kinde NextJS getKindeServerSession helper:

import {getKindeServerSession} from "@kinde-oss/kinde-auth-nextjs/server";
const SayHello = async () => {
const {getUser} = getKindeServerSession();
const user = await getUser();
return <p>Hi {user.given_name}!</p>;
};

To be on the safe side we also provide isAuthenticated flag in this helper:

import { getKindeServerSession } from "@kinde-oss/kinde-auth-nextjs/server";
const UserProfile = async () => {
const { getUser, isAuthenticated } = getKindeServerSession();
const user = await getUser();
return (
{
(await isAuthenticated()) ?
<div>
<h2>{user.given_name}</h2>
<p>{user.email}</p>
</div> :
<p>Please sign in or register!</p>
}
);
};

Client Components

Link to this section

In Next.js you can opt into using Client Components which give you interactivity benefits and access to the browser APIs. You can read more about them in the Next.js docs.

To get the Kinde session data in your Client Components follow these two steps:

  1. Create an API route in your Next.js project that returns the data from getKindeServerSession.

    app/api/kindeSession/route.ts
    import {getKindeServerSession} from "@kinde-oss/kinde-auth-nextjs/server";
    import {NextResponse} from "next/server";
    export async function GET() {
    const {getUser, isAuthenticated, getPermissions, getOrganization} = getKindeServerSession();
    const user = await getUser();
    const authenticated = await isAuthenticated();
    const permissions = await getPermissions();
    const organization = await getOrganization();
    return NextResponse.json({user, authenticated, permissions, organization});
    }
  2. Fetch the data from the API endpoint in your component inside a useEffect and then save the data to the component state.

    // some client component
    "use client";
    import { useEffect, useState } from "react";
    export default function UploadProfilePic() {
    const [user, setUser] = useState<any>();
    const [authStatus, setAuthStatus] = useState(null);
    console.log(user);
    useEffect(() => {
    const getKindeSession = async () => {
    const res = await fetch("/api/kindeSession");
    const data = await res.json();
    setUser(data.user);
    setAuthStatus(data.authenticated);
    };
    getKindeSession();
    }, []);

Protecting pages

Link to this section

It’s likely that your application will have both pages that are publicly available and private ones which should only be available to logged in users.

  1. Inside your src folder create a middleware.js file containing the following code:

    import {authMiddleware} from "@kinde-oss/kinde-auth-nextjs/server";
    export const config = {
    matcher: [
    /*
    * Match all request paths except for the ones starting with:
    * - api (API routes)
    * - _next/static (static files)
    * - _next/image (image optimization files)
    * - favicon.ico (favicon file)
    */
    "/((?!api|_next/static|_next/image|favicon.ico).*)"
    ]
    };
    export default authMiddleware;
  2. Create a page at src/app/dashboard/page.js

    export default function Dashboard() {
    return (
    <div>
    <p>Welcome to the dashboard!</p>
    </div>
    );
    }
  3. Try to access this page when signed in and when signed out. Notice how you’ll be redirected to the home page when not authenticated.

Protect your API

Link to this section

The getKindeServerSession helper is also available in your API. Create an endpoint in the new App Router pattern at app/api/protected/route.js and include the following code block:

import {NextResponse} from "next/server";
import {getKindeServerSession} from "@kinde-oss/kinde-auth-nextjs/server";
export async function GET() {
const {getUser, isAuthenticated} = getKindeServerSession();
if (await !isAuthenticated()) {
return new Response("Unauthorized", {status: 401});
}
const user = await getUser();
const data = {message: "Hello User", id: user.id};
return NextResponse.json({data});
}

This will check if the user is authenticated, and if not, will throw a 401 error.

User Permissions

Link to this section

Once a user has been verified as signed in, your product/application will receive the JWT token with an array of permissions for that user. You will need to configure your product/application to read permissions and unlock the respective functions.

You set permissions in your Kinde account (see help article), the below is an example set of permissions.

"permissions":[
"create:todos",
"update:todos",
"read:todos",
"delete:todos",
"create:tasks",
"update:tasks",
"read:tasks",
"delete:tasks"
]

We provide helper functions to more easily access permissions:

const {getPermission, getPermissions} = getKindeServerSession();
getPermission("create:todos");
// {orgCode: "org_1234", isGranted: true}
getPermissions();
// {orgCode: "org_1234", permissions: ["create:todos", "update:todos", "read:todos"]}

A practical example in code might look something like:

{
(await getPermission("create:todos").isGranted) ? <button>Create todo</button> : null;
}

An audience is the intended recipient of an access token - for example the API for your application. The audience argument can be set against KINDE_AUDIENCE in your environment variables.

The audience of a token is the intended recipient of the token.

// .env file
KINDE_AUDIENCE = your_audience;

For details on how to connect, see Register an API.

When a user signs in the Access token your product/application receives contains a custom claim called feature_flags which is an object detailing the feature flags for that user.

You can set feature flags in your Kinde account. Here’s an example.

feature_flags: {
theme: {
"t": "s",
"v": "pink"
},
is_dark_mode: {
"t": "b",
"v": true
},
competitions_limit: {
"t": "i",
"v": 5
}
}

In order to minimize the payload in the token we have used single letter keys / values where possible. The single letters represent the following:

t = type

v = value

s = string

b = boolean

i = integer

We provide helper functions to more easily access feature flags:

/**
* Get a flag from the feature_flags claim of the access_token.
* @param {string} code - The name of the flag.
* @param {obj} [defaultValue] - A fallback value if the flag isn't found.
* @param {'s'|'b'|'i'|undefined} [flagType] - The data type of the flag (integer / boolean / string).
* @return {object} Flag details.
*/
const { getFlag } = getKindeServerSession();
/* Example usage */
getFlag('theme');
/*{
// "code": "theme",
// "type": "string",
// "value": "pink",
// "is_default": false // whether the fallback value had to be used
*/}
getFlag('create_competition', {defaultValue: false});
/*{
"code": "create_competition",
"value": false,
"is_default": true // because fallback value had to be used
}*/

A practical example in code might look something like:

const {getFlag} = getKindeServerSession();
{
(await getFlag("create_competition").value) ? <button>Create competition</button> : null;
}

We also require wrapper functions by type which should leverage getFlag above.

Booleans:

/**
* Get a boolean flag from the feature_flags claim of the access_token.
* @param {string} code - The name of the flag.
* @param {bool} [defaultValue] - A fallback value if the flag isn't found.
* @return {bool}
*/
const {getBooleanFlag} = getKindeServerSession();
/* Example usage */
getBooleanFlag("is_dark_mode");
// true
getBooleanFlag("is_dark_mode", false);
// true
getBooleanFlag("new_feature", false);
// false (flag does not exist so falls back to default)

Strings and integers work in the same way as booleans above:

/**
* Get a string flag from the feature_flags claim of the access_token.
* @param {string} code - The name of the flag.
* @param {string} [defaultValue] - A fallback value if the flag isn't found.
* @return {string}
*/
const {getStringFlag} = getKindeServerSession();
/**
* Get an integer flag from the feature_flags claim of the access_token.
* @param {string} code - The name of the flag.
* @param {int} [defaultValue] - A fallback value if the flag isn't found.
* @return {int}
*/
const {getIntegerFlag} = getKindeServerSession();

A practical example in code might look something like:

const {getBooleanFlag, getStringFlag} = getKindeServerSession();
{
(await getBooleanFlag("create_competition")) ? (
<button className={`theme-${getStringFlag("theme")}`}>Create competition</button>
) : null;
}

Creating an organization

Link to this section

To have a new organization created within your application, you can use the <CreateOrgLink> component that ships with the SDK. This will redirect the user to Kinde and create an organization with them as a member.

import {CreateOrgLink} from "@kinde-oss/kinde-auth-nextjs/server";
...
<CreateOrgLink orgName="My org">Create Org</CreateOrgLink>

Signing up/login users to organizations

Link to this section

Every organization in Kinde has a unique code. To sign up a new user into a particular organization you will need to pass through this code in the <RegisterLink /> component.

import {RegisterLink} from "@kinde-oss/kinde-auth-nextjs/server";
...
<RegisterLink orgCode="org_123456">Sign up to org</RegisterLink>

This code should also be passed along with the <LoginLink> component if you wish for a user to be logged into a specific organization.

<LoginLink orgCode="org_123456">Sign into org</LoginLink>

For more information about using organizations, see Kinde organizations for developers.

Kinde Management API

Link to this section

You need to enable the application’s access to the Kinde Management API. You can do this in Kinde by going to Settings > APIs > Kinde Management API and then toggling on your Next.js application under the Applications tab.

To use our management API please see @kinde/management-api-js

Persisting app state

Link to this section

If you want your project to remember which url your user was intending to visit before they were asked to authenticate, you can pass an additional parameter in the /login and /register links.

After the user has completed authentication at your defined callback url they will be redirected to the path you define here. This value does not need to be added to your allowed callback urls in Kinde.

NextJS 13

// NextJS 13
{/* eslint-disable-next-line @next/next/no-html-link-for-pages */}
<a href="api/auth/login?post_login_redirect_url=/dashboard">
Sign in
</a>
<LoginLink postLoginRedirectURL={'/dashboard'}>Login</LoginLink>
<RegisterLink postLoginRedirectURL={'/dashboard'}>Register</RegisterLink>

Note: the value of post_login_redirect_url should either be a url on the same origin or a relative path.

Working with preview URLs

Link to this section

Our Kinde NextJS SDK currently requires that KINDE_SITE_URL, KINDE_POST_LOGOUT_REDIRECT_URL, and KINDE_POST_LOGIN_REDIRECT_URL are defined and also that the respective callback URLs and logout redirect URLs are added to your app in Kinde.

Since Kinde doesn’t allow wildcard URLs as a security measure, to be able to take advantage of Vercel’s dynamically generated preview URLs you need to follow the steps below:

1. Add the following to your next.config.js :

Link to this section
/** @type {import('next').NextConfig} */
const nextConfig = {
env: {
KINDE_SITE_URL: process.env.KINDE_SITE_URL ?? `https://${process.env.VERCEL_URL}`,
KINDE_POST_LOGOUT_REDIRECT_URL:
process.env.KINDE_POST_LOGOUT_REDIRECT_URL ?? `https://${process.env.VERCEL_URL}`,
KINDE_POST_LOGIN_REDIRECT_URL:
process.env.KINDE_POST_LOGIN_REDIRECT_URL ?? `https://${process.env.VERCEL_URL}/dashboard`
}
};
module.exports = nextConfig;

This will make sure Vercel uses its generated preview URLs to populate those 3 Kinde variables. Just make sure to update the above to suit your application (e.g. “/dashboard” for KINDE_POST_LOGIN_REDIRECT_URL) and also that those variables are not set for the preview environment in your Vercel project, otherwise, they will get overridden.

2. Add callback URLs and logout redirect URLs to Kinde dynamically:

Link to this section

We will create a script that will run every time a new preview is deployed by Vercel and will add the newly generated URL to Kinde.

  1. First, make sure the Kinde Management API is enabled for your NextJS app. In the Kinde dashboard, go to your application and click APIs on the left-side menu.

  2. Then, make the Kinde Management API active by toggling the switch on.

  3. Click Save.

  4. Now, in your application source code, create a folder at the top level called scripts.

  5. Within that folder, create a file called add-urls-to-kinde.js and add the following code:

    async function getAuthToken() {
    try {
    const response = await fetch(`${process.env.KINDE_ISSUER_URL}/oauth2/token`, {
    method: "POST",
    headers: {
    "Content-Type": "application/x-www-form-urlencoded",
    Accept: "application/json"
    },
    body: new URLSearchParams({
    client_id: process.env.KINDE_CLIENT_ID,
    client_secret: process.env.KINDE_CLIENT_SECRET,
    grant_type: "client_credentials",
    audience: `${process.env.KINDE_ISSUER_URL}/api`
    })
    });
    if (!response.ok) {
    throw new Error(`Failed to get auth token: ${response.statusText}`);
    }
    const data = await response.json();
    return data.access_token;
    } catch (error) {
    console.error("Error getting auth token:", error);
    throw error;
    }
    }
    async function addLogoutUrlToKinde(token) {
    try {
    const response = await fetch(
    `${process.env.KINDE_ISSUER_URL}/api/v1/applications/${process.env.KINDE_CLIENT_ID}/auth_logout_urls`,
    {
    method: "POST",
    headers: {
    Authorization: `Bearer ${token}`,
    Accept: "application/json",
    "Content-Type": "application/json"
    },
    body: JSON.stringify({
    urls: [`https://${process.env.VERCEL_URL}`]
    })
    }
    );
    if (!response.ok) {
    throw new Error(`Failed to add logout URL to Kinde: ${response.statusText}`);
    }
    const responseData = await response.json();
    console.log(`SUCCESS: Logout URL added to Kinde: ${process.env.VERCEL_URL}`, responseData);
    } catch (error) {
    console.error("Failed to add logout URL to Kinde", error);
    throw error;
    }
    }
    async function addCallbackUrlToKinde(token) {
    try {
    const response = await fetch(
    `${process.env.KINDE_ISSUER_URL}/api/v1/applications/${process.env.KINDE_CLIENT_ID}/auth_redirect_urls`,
    {
    method: "POST",
    headers: {
    Authorization: `Bearer ${token}`,
    Accept: "application/json",
    "Content-Type": "application/json"
    },
    body: JSON.stringify({
    urls: [`https://${process.env.VERCEL_URL}/api/auth/kinde_callback`]
    })
    }
    );
    if (!response.ok) {
    throw new Error(`Failed to add callback URL to Kinde: ${response.statusText}`);
    }
    const responseData = await response.json();
    console.log(
    `SUCCESS: Callback URL added to Kinde: ${process.env.VERCEL_URL}/api/auth/kinde_callback`,
    responseData
    );
    } catch (error) {
    console.error("Failed to add callback URL to Kinde", error);
    throw error;
    }
    }
    (async () => {
    if (process.env.VERCEL == 1) {
    try {
    const authToken = await getAuthToken();
    await addCallbackUrlToKinde(authToken);
    await addLogoutUrlToKinde(authToken);
    } catch (error) {
    console.error("Script failed:", error);
    }
    }
    })();
  6. Now, in your package.json, add a postbuild script that will run the /scripts/add-urls-to-kinde.js file after Vercel builds your app.

    "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "postbuild": "node ./scripts/add-urls-to-kinde.js"
    }
  7. After you commit these changes, the next deploy will add the newly created preview URLs to your Kinde application.

If you need any assistance with getting Kinde connected reach out to us at support@kinde.com.