Skip to content

Next.js App Router SDK v1

This SDK is for developers already using the Next.js SDK v1.8.25 or earlier document. It is relevant for Next.js 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 Next.js docs and Next.js starter kit in GitHub.

Terminal window
npm 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 Next.js 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 Next.js 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 Next.js 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.

Next.js 13

// Next.js 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 Next.js SDK currently requires that these environment variables KINDE_SITE_URL, KINDE_POST_LOGOUT_REDIRECT_URL, and KINDE_POST_LOGIN_REDIRECT_URL are defined, and that the callback URLs and logout redirect URLs are added to your app in Kinde.

To add Vercel’s dynamically generated URLs you can either securely use our API to add them on the fly or you can use wildcard URLs. It should be noted that whilst wildcards are more convenient it is not as secure as explicitly adding the url to the allowlist via API as we outline below.

Add the following to your next.config.js.

/** @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 ensures Vercel uses its generated preview URLs to populate the three Kinde variables.

  • Make sure the above values match your application (e.g. “/dashboard” for KINDE_POST_LOGIN_REDIRECT_URL)
  • Also make sure variables are not set for the preview environment in your Vercel project. If they are, they will be overridden by the new variables in the next.config.js file.

Add callback URLs and logout redirect URLs to Kinde dynamically

Link to this section

Create a script that will run each time a new preview is deployed by Vercel, which will add the newly generated URL to Kinde.

It is important to note that for this part you will need to create a machine to machine (M2M) application in Kinde to interface with the Kinde Management API.

  1. Create a Machine to machine (M2M) app.

    1. In Kinde, go to Settings > Applications and click on Add application.
    2. Give your application a name and select Machine to machine (M2M).
    3. Select Save.
    4. On the next screen, take note of the Client ID and Client secret values and add them to your .env.local file as KINDE_M2M_CLIENT_ID and KINDE_M2M_CLIENT_SECRET.
    5. On the same screen, click on APIs on the left menu.
    6. Authorize your M2M application to access the Kinde Management API by selecting the three dots (...) and clicking Authorize application.
    7. Once the application is authorized, select the three dots (...) again and this time select Manage scopes.
    8. Since we will be adding callback and redirect URLs dynamically via the Kinde Management API, you will need to toggle the switch for create:application_redirect_uris and create:application_logout_uris.
    9. Select Save.
  2. In your application source code, create a folder at the top level called scripts.

  3. 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_M2M_CLIENT_ID,
    client_secret: process.env.KINDE_M2M_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);
    }
    }
    })();
  4. 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"
    }
  5. Commit these changes. The next deploy will add the newly created preview URLs to your Kinde application.