Next.js Pages Router SDK
SDKs and APIs
This SDK is for Next.js version 13+ and uses Server Side Components and App Router.
New to Kinde? Get started here
If you’re using version 1 see Next.js App Router V1
If you’re using the pages router see Next.js Pages Router
The easiest way to get started is to use the Next.js starter kit, and watch a demo video.
npm i @kinde-oss/kinde-auth-nextjs
yarn add @kinde-oss/kinde-auth-nextjs
pnpm add @kinde-oss/kinde-auth-nextjs
http://localhost:3000/api/auth/kinde_callback
http://localhost:3000
Put these variables in a .env.local
file in the root of your Next.js app. You can find these variables on your Kinde Settings > Applications > [Your app] > View details page.
KINDE_CLIENT_ID
- Your business’s unique ID on KindeKINDE_CLIENT_SECRET
- Your business’s secret key (do not share)KINDE_ISSUER_URL
- your kinde domainKINDE_SITE_URL
- where your app is runningKINDE_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.KINDE_AUDIENCE
- optional - a whitespace separated list of audiences to populate the aud
claim in the token.Replace the information in the example with your own information. You might also set different URLs depending where your project is running. They need to match the callback URLs you entered in Kinde, above.
KINDE_CLIENT_ID=<your_kinde_client_id>KINDE_CLIENT_SECRET=<your_kinde_client_secret>KINDE_ISSUER_URL=https://<your_kinde_subdomain>.kinde.comKINDE_SITE_URL=http://localhost:3000KINDE_POST_LOGOUT_REDIRECT_URL=http://localhost:3000KINDE_POST_LOGIN_REDIRECT_URL=http://localhost:3000/dashboard
Create the following file 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 const GET = handleAuth();
This will handle Kinde Auth endpoints in your Next.js app.
Important! Our SDK relies on this file existing in this location specified above.
The default path for the Kinde Auth API is /api/auth
. If your Next.js application uses a custom base path for your API, you can override this setting by setting the following variable in your .env
file:
KINDE_AUTH_API_PATH="/my/custom/path"
You can also customise the Kinde Auth API sub-paths by setting the following variables in your .env
file:
KINDE_AUTH_LOGIN_ROUTE
- defaults to login
KINDE_AUTH_LOGOUT_ROUTE
- defaults to logout
KINDE_AUTH_REGISTER_ROUTE
- defaults to register
KINDE_AUTH_CREATEORG_ROUTE
- defaults to create_org
KINDE_AUTH_HEALTH_ROUTE
- defaults to health
KINDE_AUTH_SETUP_ROUTE
- defaults to setup
Given the following .env
file:
KINDE_AUTH_API_PATH="/my/custom/path"KINDE_AUTH_LOGIN_ROUTE="app_login"
The Kinde login route for your application will be /my/custom/path/app_login
.
Middleware is used to protect routes in your Next.js app, and is a requirement for a seamless authentication experience.
We provide a withAuth
helper that will protect routes covered by the matcher. If the user is not authenticated then they are redirected to login and once they have logged in they will be redirected back to the protected page which they should now have access to.
We require this middleware to run on all routes beside Next.js internals and static files. The provided matcher will do this for you.
This means that by default, all routes will be protected. You must opt-out public routes - see opting routes out of middleware protection for more information.
Want to learn more about middleware? Check out the Next.js middleware docs.
Create a middleware.ts
file in your project’s root directory and add the following code:
import { withAuth } from "@kinde-oss/kinde-auth-nextjs/middleware";
export default function middleware(req) { return withAuth(req);}
export const config = { matcher: [ // Run on everything but Next internals and static files '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)', ]};
You can use the withAuth
helper as shown below with a middleware
callback function which has access to the req.kindeAuth
object that exposes the token and user data.
import {withAuth} from "@kinde-oss/kinde-auth-nextjs/middleware";
export default withAuth(async function middleware(req) { console.log("look at me", req.kindeAuth);});
export const config = { matcher: [ '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)', ]};
As the middleware matcher is set to protect all routes, you can opt routes out of middleware protection by adding them to the publicPaths
array.
import { withAuth } from "@kinde-oss/kinde-auth-nextjs/middleware";
export default withAuth( async function middleware(req) { }, { // Middleware still runs on all routes, but doesn't protect the blog route publicPaths: ["/blog"], });
export const config = { matcher: [ '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)', ],}
There are options that can be passed into the middleware function to configure its functionality.
isReturnToCurrentPage
- redirect the user back to the page they were trying to accessloginPage
- define the path of the login page (where the users are redirected to when not authenticated)publicPaths
- define the public pathsisAuthorized
- define the criteria for authorizationimport { withAuth } from "@kinde-oss/kinde-auth-nextjs/middleware";
export default withAuth( async function middleware(req) { console.log("look at me", req.kindeAuth); }, { isReturnToCurrentPage: true, loginPage: "/login", publicPaths: ["/public", '/more'], isAuthorized: ({token}) => { // The user will be considered authorized if they have the permission 'eat:chips' return token.permissions.includes("eat:chips"); } });
export const config = { matcher: [ '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)', ],}
Wrap your app in the Kinde Auth Provider. This will give you access to the Kinde Auth data in your app and will ensure that the tokens are refreshed when needed.
Create a file AuthProvider.tsx
in your app directory.
"use client";import {KindeProvider} from "@kinde-oss/kinde-auth-nextjs";
export const AuthProvider = ({children}) => { return <KindeProvider>{children}</KindeProvider>;};
Then wrap your app in the AuthProvider
component.
...import {AuthProvider} from './AuthProvider';
export const metadata = { title: 'Kinde Auth', description: 'Kinde with Next.js App Router'};
export default async function RootLayout({ children}: { children: React.ReactNode;}) { return ( <AuthProvider> <html lang="en"> // Your app code here </html> </AuthProvider> );}
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/components";
...
<LoginLink>Sign in</LoginLink><RegisterLink>Sign up</RegisterLink>
Static redirect
If you want to redirect users to a certain page after logging in, you can set the KINDE_POST_LOGIN_REDIRECT_URL
environment variable in your .env.local
file.
Dynamic redirect
You can also set a postLoginRedirectURL
parameter to tell us where to redirect after authenticating.
import {RegisterLink, LoginLink} from "@kinde-oss/kinde-auth-nextjs/components";
...
<LoginLink postLoginRedirectURL="/dashboard">Sign in</LoginLink><RegisterLink postLoginRedirectURL="/welcome">Sign up</RegisterLink>
This appends post_login_redirect_url
to the search params when redirecting to Kinde Auth. You can achieve the same result as above, like this:
import { redirect } from "next/navigation";...
redirect('/api/auth/login?post_login_redirect_url=/dashboard')
...
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/components";
...
<LogoutLink>Log out</LogoutLink>
You can get an authorized user’s Kinde Auth data from any server component using the getKindeServerSession
helper.
Method | Description |
---|---|
isAuthenticated | Check if the user is authenticated |
getUser | Get the current user’s details |
getOrganization | Get the current user’s organization |
getUserOrganizations | Get all the organizations the current user belongs to |
getPermission | Check if the current user has a permission |
getPermissions | Get the current user’s permissions |
getFlag | Get a feature flag |
getBooleanFlag | Get a boolean feature flag |
getIntegerFlag | Get an integer feature flag |
getStringFlag | Get a string feature flag |
refreshTokens | Refresh tokens to get up-to-date Kinde data |
getAccessToken | Get the decoded access token |
getAccessTokenRaw | Get the access token |
getIdToken | Get the decoded ID token |
getIdTokenRaw | Get the ID token |
getClaim | Get a claim from either token |
isAuthenticated
Check if the user is authenticated.
import {getKindeServerSession} from "@kinde-oss/kinde-auth-nextjs/server";
const {isAuthenticated} = getKindeServerSession();const isUserAuthenticated = await isAuthenticated();
true;
getUser
Get the logged in user’s details.
import {getKindeServerSession} from "@kinde-oss/kinde-auth-nextjs/server";
const {getUser} = getKindeServerSession();const user = await getUser();
console.log(user);
{ "id": "kp_123", "email": "example@email.com", "family_name": "Example", "given_name": "User", "picture": null, "username": "ExampleUsername", "phone_number": "1234567890", "properties": { "usr_city": "", "usr_industry": "", "usr_job_title": "", "usr_middle_name": "", "usr_postcode": "", "usr_salutation": "", "usr_state_region": "", "usr_street_address": "", "usr_street_address_2": "" }}
getOrganization
Get the current user’s organization
import {getKindeServerSession} from "@kinde-oss/kinde-auth-nextjs/server";
const {getOrganization} = getKindeServerSession();const org = await getOrganization();
console.log(org);
{ "orgCode": "org_123", "orgName": "Deafault Org", "properties": { "org_city": "", "org_country": "", "org_industry": "", "org_postcode": "", "org_state_region": "", "org_street_address": "", "org_street_address_2": "" }}
getUserOrganizations
Get all the organizations the current user belongs to
import {getKindeServerSession} from "@kinde-oss/kinde-auth-nextjs/server";
const {getUserOrganizations} = getKindeServerSession();const userOrgs = await getUserOrganizations();
console.log(userOrgs);
{ "orgCodes": ["org_123", "org_456"], "orgs": [ { "code": "org_123", "name": "Deafault Org" }, { "code": "org_456", "name": "Another Org" } ]}
getPermission
Check if the current user has a permission.
Parameter | Type | Description |
---|---|---|
code | string | The permission code to check |
import {getKindeServerSession} from "@kinde-oss/kinde-auth-nextjs/server";
const {getPermission} = getKindeServerSession();const canEatTacos = await getPermission("eat:tacos");
console.log(canEatTacos);
{ "isGranted": true, "orgCode": "org_123"}
getPermissions
Get the current user’s permissions.
import {getKindeServerSession} from "@kinde-oss/kinde-auth-nextjs/server";
const {getPermissions} = getKindeServerSession();const permissions = await getPermissions();
console.log(permissions);
{ "permissions": ["eat:tacos", "read:books"], "orgCode": "org_123"}
getFlag
Get a feature flag
Parameter | Type | Description |
---|---|---|
code | string | The flag code to check |
defaultValue | boolean | string | number | The default value to return if the flag is not set |
type | enum (b | s | i ) | The type of the flag |
import {getKindeServerSession} from "@kinde-oss/kinde-auth-nextjs/server";
const {getFlag} = getKindeServerSession();const billingFlag = await getFlag("billing", false, "b");
console.log(billingFlag);
{ "code": "billing", "type": "boolean", "value": true, "defaultValue": false, "is_default": false}
getBooleanFlag
Get a boolean feature flag
Parameter | Type | Description |
---|---|---|
code | string | The flag code to check |
defaultValue | boolean | The default value to return if the flag is not set |
import {getKindeServerSession} from "@kinde-oss/kinde-auth-nextjs/server";
const {getBooleanFlag} = getKindeServerSession();const billingFlag = await getBooleanFlag("billing", false);
console.log(billingFlag);
true;
getIntegerFlag
Get a boolean feature flag
Parameter | Type | Description |
---|---|---|
code | string | The flag code to check |
defaultValue | boolean | The default value to return if the flag is not set |
import {getKindeServerSession} from "@kinde-oss/kinde-auth-nextjs/server";
const {getIntegerFlag} = getKindeServerSession();const billingVersion = await getIntegerFlag("billingVersion", 0);
console.log(billingVersion);
2
getStringFlag
Get a string feature flag
Parameter | Type | Description |
---|---|---|
code | string | The flag code to check |
defaultValue | string | The default value to return if the flag is not set |
import {getKindeServerSession} from "@kinde-oss/kinde-auth-nextjs/server";
const {getStringFlag} = getKindeServerSession();const theme = await getStringFlag("theme", "system");
console.log(theme);
"light"
refreshTokens
Refresh tokens to get up-to-date Kinde data
import {getKindeServerSession} from "@kinde-oss/kinde-auth-nextjs/server";import {someUpdateFunction} from "@/app/actions";
const {refreshTokens} = getKindeServerSession();await someUpdateFunction({ param_1: "value_1", param_2: "value_2"});await refreshTokens();
getAccessToken
Get the decoded access token
import {getKindeServerSession} from "@kinde-oss/kinde-auth-nextjs/server";
const {getAccessToken} = getKindeServerSession();const accessToken = await getAccessToken();
console.log(accessToken);
{ "aud": ["your-api"], "azp": 1234567890, "email": "example@email.com", "exp": 1234567890, "feature_flags": { "isonboardingcomplete": { "t": "b", "v": false } }, "iat": 1234567890, "iss": "https://your-kinde-subdomain.kinde.com", "jti": "7802e2d2-asdf-431e-bc72-5ed95asdf475d", "org_code": "org_123", "org_name": "Default Org", "organization_properties": { "kp_org_city": {} }, "permissions": ["create:template"], "roles": [ { "id": "018ee9aa-f92b-83fc-1d40-1234567890", "key": "admin", "name": "Admin" } ], "scp": ["openid", "profile", "email", "offline"], "sub": "kp_6123456789009876", "user_properties": { "kp_usr_city": {} }}
getAccessTokenRaw
Get the access token
import {getKindeServerSession} from "@kinde-oss/kinde-auth-nextjs/server";
const {getAccessTokenRaw} = getKindeServerSession();const accessToken = await getAccessTokenRaw();
console.log(accessToken);
eyJhxxx.eyJhdxxx.A4djjxxx
getIdToken
Get the decoded ID token
import {getKindeServerSession} from "@kinde-oss/kinde-auth-nextjs/server";
const {getIdToken} = getKindeServerSession();const idToken = await getIdToken();
console.log(idToken);
{ "at_hash": "ZY6jx1SGjzgkHGJ_2Jabcd", "aud": ["feb8e697b967466eacb96d26c5ca0e12"], "auth_time": 1234567890, "azp": "feb8e099xxx", "email": "example@email.com", "email_verified": true, "exp": 123456789, "ext_provider": { "claims": { "connection_id": "bcc486xxx", "email": "example@email.com", "family_name": "User", "given_name": "Example", "is_confirmed": true, "picture": "https://lh3.googleusercontent.com/a/ACgxxx", "profile": { "email": "example@email.com", "family_name": "User", "given_name": "Example", "id": "1234567890", "name": "Example user", "picture": "https://lh3.googleusercontent.com/a/ACgxxx", "verified_email": true } }, "connection_id": "bccxxx", "name": "Google" }, "family_name": "User", "given_name": "Example", "iat": 1234567890, "iss": "https://your-kinde-subdomain.kinde.com", "jti": "e7e18303-0ea5-402d-932c-xxx", "name": "Example user", "org_codes": ["org_123"], "organizations": [ { "id": "org_123", "name": "Default Organization" } ], "picture": "https://lh3.googleusercontent.com/a/ACgxxx", "rat": 1234567890, "sub": "kp_1234567890", "updated_at": 1234567890, "user_properties": { "kp_usr_city": {} }}
getIdTokenRaw
Get the ID token
import {getKindeServerSession} from "@kinde-oss/kinde-auth-nextjs/server";
const {getIdTokenRaw} = getKindeServerSession();const idToken = await getIdTokenRaw();
console.log(idToken);
eyJhxxx.eyJhdxxx.A4djjxxx
getClaim
Get a claim from either token
Parameter | Type | Description |
---|---|---|
claim | string | The claim key |
type | enum (access_token | id_token ) | The token to get the claim from |
import {getKindeServerSession} from "@kinde-oss/kinde-auth-nextjs/server";
const {getClaim} = getKindeServerSession();const username = await getClaim("preferred_username", "id_token");
console.log(idToken);
"exampleUsername"
You can get an authorized user’s Kinde Auth data from any client component using the useKindeBrowser
helper.
Variable / Method | Description |
---|---|
isAuthenticated | Get a feature flag |
user / getUser | The current in user’s details |
organization / getOrganization | The current user’s organization |
userOrganizations / getUserOrganizations | All the organizations the current user belongs to |
getPermission | Check if the current user has a permission |
permissions / getPermissions | The current user’s permissions |
getFlag | Get the access token |
getBooleanFlag | Get the access token |
getIntegerFlag | Get the access token |
getStringFlag | Get the access token |
refreshData | Refresh tokens to get up-to-date Kinde data |
accessToken / getAccessToken | Check if the user is authenticated |
accessTokenRaw / getAccessTokenRaw | Get the current user’s details |
idToken / getIdToken | Get all the organizations the current user belongs to |
idTokenRaw / getIdTokenRaw | Check if the current user has a permission |
isLoading | Is Kinde data loading |
error | Error message if there is an error |
Tip: Use isLoading
to ensure the data is up to date. You can return a loading spinner or something similar if you want.
isAuthenticated
Check if the user is authenticated.
"use client";import {useKindeBrowserClient} from "@kinde-oss/kinde-auth-nextjs";
const {isAuthenticated} = useKindeBrowserClient();console.log(isAuthenticated);
true;
user
/ getUser
Get the logged in user’s details.
"use client";
import {useKindeBrowserClient} from "@kinde-oss/kinde-auth-nextjs";
const {user, getUser} = useKindeBrowserClient();const alsoUser = getUser();
console.log(user);
{ "id": "kp_123", "email": "example@email.com", "family_name": "Example", "given_name": "User", "picture": null, "username": "ExampleUsername", "phone_number": "1234567890", "properties": { "usr_city": "", "usr_industry": "", "usr_job_title": "", "usr_middle_name": "", "usr_postcode": "", "usr_salutation": "", "usr_state_region": "", "usr_street_address": "", "usr_street_address_2": "" }}
organization
/ getOrganization
Get the current user’s organization
"use client";import {useKindeBrowserClient} from "@kinde-oss/kinde-auth-nextjs";
const {organization, getOrganization} = useKindeBrowserClient();const org = getOrganization();
console.log(organization, org);
{ "orgCode": "org_123", "orgName": "Deafault Org", "properties": { "org_city": "", "org_country": "", "org_industry": "", "org_postcode": "", "org_state_region": "", "org_street_address": "", "org_street_address_2": "" }}
userOrganizations
/ getUserOrganizations
Get all the organizations the current user belongs to
"use client";import {useKindeBrowserClient} from "@kinde-oss/kinde-auth-nextjs";
const {userOrganizations, getUserOrganizations} = useKindeBrowserClient();const userOrgs = getUserOrganizations();
console.log(userOrganizations, userOrgs);
{ "orgCodes": ["org_123", "org_456"], "orgs": [ { "code": "org_123", "name": "Deafault Org" }, { "code": "org_456", "name": "Another Org" } ]}
getPermission
Check if the current user has a permission.
Parameter | Type | Description |
---|---|---|
code | string | The permission code to check |
"use client";import {useKindeBrowserClient} from "@kinde-oss/kinde-auth-nextjs";
const {getPermission} = useKindeBrowserClient();const canEatTacos = getPermission("eat:tacos");
console.log(canEatTacos);
{ "isGranted": true, "orgCode": "org_123"}
permissions
/ getPermissions
Get the current user’s permissions.
"use client";import {useKindeBrowserClient} from "@kinde-oss/kinde-auth-nextjs";
const {permissions, getPermissions} = useKindeBrowserClient();const perms = getPermissions();
console.log(permissions, permis);
{ "permissions": ["eat:tacos", "read:books"], "orgCode": "org_123"}
getFlag
Get a feature flag
Parameter | Type | Description |
---|---|---|
code | string | The flag code to check |
defaultValue | boolean | string | number | The default value to return if the flag is not set |
type | enum (b | s | i ) | The type of the flag |
"use client";import {useKindeBrowserClient} from "@kinde-oss/kinde-auth-nextjs";
const {getFlag} = useKindeBrowserClient();const billingFlag = getFlag("billing", false, "b");
console.log(billingFlag);
{ "code": "billing", "type": "boolean", "value": true, "defaultValue": false, "is_default": false}
getBooleanFlag
Get a boolean feature flag
Parameter | Type | Description |
---|---|---|
code | string | The flag code to check |
defaultValue | boolean | The default value to return if the flag is not set |
"use client";import {useKindeBrowserClient} from "@kinde-oss/kinde-auth-nextjs";
const {getBooleanFlag} = useKindeBrowserClient();const billingFlag = getBooleanFlag("billing", false);
console.log(billingFlag);
true;
getIntegerFlag
Get a boolean feature flag
Parameter | Type | Description |
---|---|---|
code | string | The flag code to check |
defaultValue | boolean | The default value to return if the flag is not set |
"use client";import {useKindeBrowserClient} from "@kinde-oss/kinde-auth-nextjs";
const {getIntegerFlag} = useKindeBrowserClient();const billingVersion = getIntegerFlag("billingVersion", 0);
console.log(billingVersion);
2
getStringFlag
Get a string feature flag
Parameter | Type | Description |
---|---|---|
code | string | The flag code to check |
defaultValue | string | The default value to return if the flag is not set |
"use client";import {useKindeBrowserClient} from "@kinde-oss/kinde-auth-nextjs";
const {getStringFlag} = useKindeBrowserClient();const theme = getStringFlag("theme", "system");
console.log(theme);
"light"
refreshData
Refresh tokens to get up-to-date Kinde data
"use client";import {useKindeBrowserClient} from "@kinde-oss/kinde-auth-nextjs";import {someUpdateFunction} from "@/app/actions";
const {refreshData} = useKindeBrowserClient();
await someUpdateFunction({ param_1: "value_1", param_2: "value_2"});await refreshData();
accessToken
/ getAccessToken
Get the decoded access token
"use client";import {useKindeBrowserClient} from "@kinde-oss/kinde-auth-nextjs";
const {accessToken, getAccessToken} = useKindeBrowserClient();const aTok = getAccessToken();
console.log(accessToken, aTok);
{ "aud": ["your-api"], "azp": 1234567890, "email": "example@email.com", "exp": 1234567890, "feature_flags": { "isonboardingcomplete": { "t": "b", "v": false } }, "iat": 1234567890, "iss": "https://your-kinde-subdomain.kinde.com", "jti": "7802e2d2-asdf-431e-bc72-5ed95asdf475d", "org_code": "org_123", "org_name": "Default Org", "organization_properties": { "kp_org_city": {} }, "permissions": ["create:template"], "roles": [ { "id": "018ee9aa-f92b-83fc-1d40-1234567890", "key": "admin", "name": "Admin" } ], "scp": ["openid", "profile", "email", "offline"], "sub": "kp_6123456789009876", "user_properties": { "kp_usr_city": {} }}
accessTokenRaw
/ getAccessTokenRaw
Get the access token
import {useKindeBrowserClient} from "@kinde-oss/kinde-auth-nextjs";
const {accessTokenRaw, getAccessTokenRaw} = useKindeBrowserClient();const aTokRaw = getAccessTokenRaw();
console.log(accessTokenRaw, aTokRaw);
eyJhxxx.eyJhdxxx.A4djjxxx
idToken
/ getIdToken
Get the decoded ID token
import {useKindeBrowserClient} from "@kinde-oss/kinde-auth-nextjs";
const {idToken, getIdToken} = useKindeBrowserClient();const idTok = getIdToken();
console.log(idToken, idTok);
{ "at_hash": "ZY6jx1SGjzgkHGJ_2Jabcd", "aud": ["feb8e697b967466eacb96d26c5ca0e12"], "auth_time": 1234567890, "azp": "feb8e099xxx", "email": "example@email.com", "email_verified": true, "exp": 123456789, "ext_provider": { "claims": { "connection_id": "bcc486xxx", "email": "example@email.com", "family_name": "User", "given_name": "Example", "is_confirmed": true, "picture": "https://lh3.googleusercontent.com/a/ACgxxx", "profile": { "email": "example@email.com", "family_name": "User", "given_name": "Example", "id": "1234567890", "name": "Example user", "picture": "https://lh3.googleusercontent.com/a/ACgxxx", "verified_email": true } }, "connection_id": "bccxxx", "name": "Google" }, "family_name": "User", "given_name": "Example", "iat": 1234567890, "iss": "https://your-kinde-subdomain.kinde.com", "jti": "e7e18303-0ea5-402d-932c-xxx", "name": "Example user", "org_codes": ["org_123"], "organizations": [ { "id": "org_123", "name": "Default Organization" } ], "picture": "https://lh3.googleusercontent.com/a/ACgxxx", "rat": 1234567890, "sub": "kp_1234567890", "updated_at": 1234567890, "user_properties": { "kp_usr_city": {} }}
idTokenRaw
/ getIdTokenRaw
Get the ID token
import {useKindeBrowserClient} from "@kinde-oss/kinde-auth-nextjs";
const {idTokenRaw, getIdTokenRaw} = useKindeBrowserClient();const idTokRaw = getIdTokenRaw();
console.log(idTokenRaw, idTokRaw);
eyJhxxx.eyJhdxxx.A4djjxxx
isLoading
Is Kinde data loading
import {useKindeBrowserClient} from "@kinde-oss/kinde-auth-nextjs";
const {user, isLoading} = useKindeBrowserClient();
if (isLoading) return <div>Loading...</div>;
return <div>Hello {user.given_name}</div>;
true
error
Error message if there is an error
import {useKindeBrowserClient} from "@kinde-oss/kinde-auth-nextjs";
const {user, isLoading, error} = useKindeBrowserClient();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>There was an error</div>;
return <div>Hello {user.given_name}</div>;
true
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. There are multiple ways you can protect pages with Kinde Auth.
On the page you want to protect, you can check if the user is authenticated and then handle it right then and there by grabbing the Kinde Auth data.
getKindeServerSession
helperuseKindeBrowserClient
helper// app/protected/page.tsx - Server Component
import { LoginLink } from "@kinde-oss/kinde-auth-nextjs/components";import { getKindeServerSession } from "@kinde-oss/kinde-auth-nextjs/server";
export default async function Protected() { const { isAuthenticated } = getKindeServerSession();
return (await isAuthenticated()) ? ( <div> This page is protected - but you can view it because you are authenticated </div> ) : ( <div> This page is protected, please <LoginLink>Login</LoginLink> to view it </div> );}
// app/protected/page.tsx - Client component"use client";
import { useKindeBrowserClient } from "@kinde-oss/kinde-auth-nextjs";import { LoginLink } from "@kinde-oss/kinde-auth-nextjs/components";
export default function Admin() { const { isAuthenticated, isLoading } = useKindeBrowserClient();
if (isLoading) return <div>Loading...</div>;
return isAuthenticated ? ( <div>Admin content</div> ) : ( <div> You have to <LoginLink>Login</LoginLink> to see this page </div> );}
In the example above, we show different content based on whether or not the user is authenticated. If you want to automatically send the user to the sign in screen, you can do something like the following:
// app/protected/page.tsx - Server Component
import {getKindeServerSession} from "@kinde-oss/kinde-auth-nextjs/server";import {redirect} from "next/navigation";
export default async function Protected() { const {isAuthenticated} = getKindeServerSession();
if (!(await isAuthenticated())) { redirect("/api/auth/login"); }
return <div>Protected content</div>;}
// app/protected/page.tsx - Client Component
// As of right now, this can't be done in Client Components because of how Next.js handles// navigation in client components with prefetching and caching.// But you can still achieve an automatic redirect with middleware
If you want the user to be redirected back to that route after signing in, you can set post_login_redirect_url
in the search params of the redirect.
if (!(await isAuthenticated())) { redirect("/api/auth/login?post_login_redirect_url=/protected");}
Our middleware will automatically refresh the tokens in your session in the background.
Sometimes, you may want to refresh these tokens yourself. An example of this is when you update Kinde data via the UI or with the Management API.
To have these updates immediately reflected in your app, you will need to get the most up-to-date Kinde data and then refresh the tokens in your session.
To get the most up-to-date Kinde data in your session, use the refreshTokens
helper function provided by getKindeServerSession
.
Due to limitations in Next.js, this will only work in a route handler or server action.
'use server'
const handleRefresh = async () => { const { refreshTokens } = getKindeServerSession(); await refreshTokens();}
...
<button onClick={handleRefresh}>Get up to date data</button>
To use our management API please see @kinde/management-api-js
Server Component example:
import { Roles, Users } from "@kinde/management-api-js";
export default async function Dashboard() { const { roles } = await Roles.getRoles(); const { users } = await Users.getUsers();
return ( <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> <section className="next-steps-section"> <h2 className="text-heading-1">Next steps for you</h2> </section>
<pre>{JSON.stringify(users, null, 2)}</pre> </div> );}
Route Handler example:
import {NextResponse} from "next/server";import {Users} from "@kinde/management-api-js";
export async function GET() { const {users} = await Users.getUsers();
return NextResponse.json({users});}
To create an organization from your app, you can use the CreateOrgLink
component.
import {CreateOrgLink} from "@kinde-oss/kinde-auth-nextjs/components";
<CreateOrgLink orgName="Hurlstone">Create org</CreateOrgLink>;
You can have your users sign in to a specific organization by setting the orgCode
param in the LoginLink
and RegisterLink
components.
import {LoginLink, RegisterLink} from "@kinde-oss/kinde-auth-nextjs/components";
<LoginLink orgCode="org_7392cf35a1e">Login</LoginLink><RegisterLink orgCode="org_7392cf35a1e">Register</RegisterLink>
If the orgCode
is not specified and the user belongs to multiple organizations, they will be prompted to choose which organization to sign in to during the login or register flow.
UTM tags can be used with the LoginLink
and RegisterLink
components to track auth traffic from its origin. You can then track the tags on the Analytics dashboard from within the Kinde app.
import {LoginLink} from "@kinde-oss/kinde-auth-nextjs/components";
<LoginLink authUrlParams={{ utm_source: "reddit", utm_medium: "social", utm_campaign: "redjune23", utm_term: "save90", utm_content: "desktop" }}> Login</LoginLink>;
You can set the language you wish your users to see when they hit the login flow by including the lang
attribute as a part of the authUrlParams
when using the LoginLink
and RegisterLink
components.
import {LoginLink} from "@kinde-oss/kinde-auth-nextjs/components";
<LoginLink authUrlParams={{ lang: "en-AU" }}> Login</LoginLink>;
An audience
is the intended recipient of an access token - for example the API for your application. The audience
argument can be passed to the Kinde client to request an audience be added to the provided token.
...KINDE_AUDIENCE=<your-api>
You can request multiple audiences by providing a white space separated list
...KINDE_AUDIENCE=<your-api-1> <your-api-2>
For details on how to connect, see Register an API.
In the case you have a custom domain and you would like to start the authentication flow from a URL like auth
.mysite.com
and you want to redirect to a URL like app
.mysite.com
, all you have to do is set the KINDE_COOKIE_DOMAIN
to match the domain.
...KINDE_COOKIE_DOMAIN=.mysite.com
If the URL you want to start the authentication flow from and the URL you want to redirect to don’t share the same domain, then this will not work.
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.
KINDE_POST_LOGIN_REDIRECT_URL
)next.config.js
file.Create a script that will run each time a new preview is deployed by Vercel, which will add the newly generated URL to Kinde.
You need to create a machine to machine (M2M) application to connect to the Kinde Management API.
Create a Machine to machine (M2M) app.
.env.local
file as KINDE_M2M_CLIENT_ID
and KINDE_M2M_CLIENT_SECRET
....
) and clicking Authorize application....
) again and this time select Manage scopes.create:application_redirect_uris
and create:application_logout_uris
.In your application source code, create a folder at the top level called scripts
.
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); } }})();
You can adapt the script above to use our Kinde Management API JS package. Please note that in this case you would have to add this package as a dependency in your project along with a few required environment variables. See configuration details.
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"}
Commit these changes. The next deploy will add the newly created preview URLs to your Kinde application.
To check your configuration, the SDK exposes an endpoint with your settings.
Note: The client secret will indicate only if the secret is set or not set correctly.
/api/auth/health
{ "apiPath": "/api/auth", "redirectURL": "http://localhost:3000/api/auth/kinde_callback", "postLoginRedirectURL": "http://localhost:3000/dashboard", "issuerURL": "https://<your_kinde_subdomain>.kinde.com", "clientID": "<your_kinde_client_id>", "clientSecret": "Set correctly", "postLogoutRedirectURL": "http://localhost:3000", "logoutRedirectURL": "http://localhost:3000"}
KINDE_POST_LOGIN_REDIRECT_URL
when working with vercel preview domains.
If you are using Vercel, you can set the KINDE_SITE_URL
and KINDE_POST_LOGIN_REDIRECT_URL
dynamically.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;
The State not found error
in production is usually a result of a mismatch between a few variables.
KINDE_SITE_URL
and/or KINDE_POST_LOGIN_REDIRECT_URL
your-app-projects.vercel.app
If you set KINDE_SITE_URL=https:// your-app-projects.vercel.app
and KINDE_POST_LOGIN_REDIRECT_URL=https:// your-app-projects.vercel.app/dashboard
.
And you also set your Callback URL to be your-app-\*.vercel.app/api/auth/kinde_callback
. You should be able to click login and complete the auth flow.
However if you start the auth flow from a Vercel preview domain your-app-PREVIEW-projects.vercel.app
and complete the auth flow, you will be redirected to your-app-projects.vercel.app/api/auth/kinde_callback
which is NOT the same as the domain you started the auth flow on.
The error happens because when you start the auth flow, a state
cookie is set which needs to be checked against when you return back to your app. In this case, you are NOT being redirect to the app you started the flow on, but rather another domain where the app is running which does not have the state
cookie.
Since there is a state
cookie mismatch, the auth flow is aborted for security reasons.
The reason why you are redirected to the wrong domain because is likely because your KINDE_POST_LOGIN_REDIRECT_URL
environment variable is static and is set for all your deployments/domains.
You should set the KINDE_POST_LOGIN_REDIRECT_URL
dynamically based on the domain you initiating the auth flow from.
In debug mode you will see more logs in your console that may help with debugging.
KINDE_DEBUG_MODE = true;
Changes when moving from the previous version.
handleAuth
- is now imported from “@kinde-oss/kinde-auth-nextjs/server”
import {handleAuth} from "@kinde-oss/kinde-auth-nextjs/server";export const GET = handleAuth();
getKindeServerSession
- functions returned from getKindeServerSession
now return promises
const {getUser} = getKindeServerSession();const user = await getUser();