The Kinde React SDK allows developers to quickly and securely integrate a new or an existing React application to the Kinde platform.

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

This SDK is optimized for React version 18.2.0.

Register for Kinde

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.

Configure React

Link to this section
# npm
npm i @kinde-oss/kinde-auth-react
# yarn
yarn add @kinde-oss/kinde-auth-react
# pnpm
pnpm i @kinde-oss/kinde-auth-react

Integrate with your app

Link to this section

Kinde uses a React Context Provider to maintain its internal state in your application.

Import the Kinde Provider component and wrap your application in it.

import {KindeProvider} from "@kinde-oss/kinde-auth-react";
const App = () => (
    <KindeProvider
        clientId="<your_kinde_client_id>"
        domain="<your_kinde_domain>"
        logoutUri={window.location.origin}
        redirectUri={window.location.origin}
    >
        <Routes />
    </KindeProvider>
);

Set callback and logout URLs

Link to this section

Set the URLs in Kinde so that after your user signs up, signs in, or signs out, they will be redirected back to your application.

  1. In Kinde, go to Settings > Applications > [your app] > View details.
  2. Replace the your_kinde_client_id and your_kinde_domain placeholders in the code block above with the the values from the App keys section.
  3. Add your callback URLs in the relevant fields. For example:
    • Allowed callback URLs (also known as redirect URIs) - for example https://localhost:3000/home/callback
    • Allowed logout redirect URLs - for example https://localhost:3000
  4. Select Save.

Tip: Make sure there are no hidden spaces and remove any ‘/’ forward slashes from the end of URLs.

If you would like to use different Environments as part of your development process, you will need to add them within your Kinde business first. You will also need to add the Environment subdomain to the code block above.

Sign in and sign up

Link to this section

Kinde provides a React hook for an easy to implement login / register flow.

Use the button examples below to redirect your users to Kinde, where they authenticate before being redirected back to your site.

import {useKindeAuth} from '@kinde-oss/kinde-auth-react';
...
const { login, register } = useKindeAuth();
...
<button onClick={register} type="button">Sign up</button>
<button onClick={login} type="button">Sign In</button>
…

Redirect after authentication

Link to this section

Once your user is redirected back to your site from Kinde, you can set a callback to take place. The callback automatically passes in the user object and any application state you set prior to the redirect.

onRedirectCallback={(user, app_state) => {
	console.log({user, app_state});
}}

Passing additional params to the auth url

Link to this section

Both the login and register methods accept and arbitrary authUrlParams object which will be passed to Kinde as part of the auth flow.

Some things you may wish to pass are:

  • login_hint this allows you to ask Kinde to prepopulate a users email address on the sign-up and sign-in screens.
  • lang if you offer multi-language support Kinde will automatically figure out the best language for your user based on their browser. However, if you want to force a language and override the users preference, you can do so by passing this attribute.
<button
    onClick={() =>
        login({
            authUrlParams: {
                login_hint: "jenny@example.com",
                lang: "ru"
            }
        })
    }
    type="button"
>
    Sign In
</button>

This is implemented in much the same way as signing in or registering. The Kinde React hook comes with a logout method.

const { logout } = useKindeAuth();
...
<button onClick={logout} type="button">Sign out</button>

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 React hook.

import {useKindeAuth} from "@kinde-oss/kinde-auth-react";
const SayHello = () => {
    const {user} = useKindeAuth();
    return <p>Hi {user.first_name}!</p>;
};

To be on the safe side we have also provided isAuthenticated and isLoading state to prevent rendering errors.

import {useKindeAuth} from "@kinde-oss/kinde-auth-react";

const UserProfile = () => {
	const { user, isAuthenticated, isLoading } = useKindeAuth();

	if (isLoading) {
		return <p>Loading</p>;
	}

	return (
		{
			isAuthenticated ?
				<div>
					<h2>{user.first_name}</h2>
					<p>{user.preferred_email}</p>
				</div> :
				<p>Please sign in or register!</p>
		}
	);
};

The getToken method lets you to securely call your API and pass the bearer token to validate that your user is authenticated.

const {getToken} = useKindeAuth();

const fetchData = async () => {
    try {
        const accessToken = await getToken();
        const res = await fetch(`<your-api>`, {
            headers: {
                Authorization: `Bearer ${accessToken}`
            }
        });
        const {data} = await res.json();
        console.log({data});
    } catch (err) {
        console.log(err);
    }
};

We recommend using our middleware on your back end to verify the token and protect endpoints. Our current implementation is Node/Express, but we’re working on more.

You can also use any open source JWT verification library for your language of choice.

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.

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

<KindeProvider
	audience="<your_api>"
	...
>

To request multiple audiences, pass them separated by white space.

<KindeProvider
	audience="<your_api1> <your_api2>"
	...
>

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

Create an organization

Link to this section

To create a new organization with the user registration, you can use the createOrg function to start the registration process:

import {useKindeAuth} from '@kinde-oss/kinde-auth-react';
...
const { createOrg } = useKindeAuth();
...
<button onClick={createOrg} type="button">Create Organization</button>

Sign up/sign in users to organizations

Link to this section

To sign up a user to a particular organization, you must pass the org_code from your Kinde account as the user is created. You can find the org_code on the Details page of each organization in Kinde.

Here’s an example function for registering or signing in:

<button onClick={() => register({org_code: ‘org_1234’})} type="button">Register</button>
<button onClick={() => login({org_code: ‘org_1234’})} type="button">Sign in</button>

Following authentication Kinde provides a json web token (jwt) to your application. Along with the standard information we also include the org_code and the permissions for that organization (this is important as a user can belong to multiple organizations and have different permissions for each). Example of a returned token:

{
    "aud": ["https://your_subdomain.kinde.com"],
    "exp": 1658475930,
    "iat": 1658472329,
    "iss": "https://your_subdomain.kinde.com",
    "jti": "123457890",
    "org_code": "org_1234",
    "permissions": ["read:todos", "create:todos"],
    "scp": ["openid", "offline"],
    "sub": "kp:123457890"
}

For more information about how organizations work in Kinde, see Kinde organizations for developers.

User Permissions

Link to this section

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

Configure permissions in Kinde first. Here 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} = useKindeAuth();

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:

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

When a user signs in, the access token your project/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 } = useKindeAuth();

/* 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} = useKindeAuth();

{
    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} = useKindeAuth();

/* 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} = useKindeAuth();

/**
 * 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} = useKindeAuth();

A practical example in code might look something like:

const {getBooleanFlag, getStringFlag} = useKindeAuth();

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

Overriding scope

Link to this section

By default the JavaScript SDK requests the following scopes:

  • profile
  • email
  • offline
  • openid

You can override this by passing scope into the <KindeProvider>.

<KindeProvider
	...
  scope="openid"
  ...
>

Getting claims

Link to this section

We have provided a helper to grab any claim from your ID or access tokens. The helper defaults to access tokens:

const {getClaim} = useKindeAuth();

getClaim("aud");
// {name: "aud", "value": ["api.yourapp.com"]}

getClaim("given_name", "id_token");
// {name: "given_name", "value": "David"}

Persisting authentication state on page refresh or new tab

Link to this section

You will find that when you refresh the browser using a front-end based SDK that the authentication state is lost. This is because there is no secure way to persist this in the front-end.

There are two ways to work around this.

  • (Recommended) use our Custom Domains feature which then allows us to set a secure, httpOnly first party cookie on your domain.
  • (Non-production solution only) If you’re not yet ready to add your custom domain, or for local development, we offer an escape hatch <KindeProvider> isDangerouslyUseLocalStorage. This will use local storage to store the refresh token. DO NOT use this in production.

Once you implement one of the above, you don’t need to do anything else.

Persisting application state

Link to this section

The options argument passed into the login and register methods accepts an app_state key where you can pass in the current application state prior to redirecting to Kinde. This is then returned to you in the second argument of the onRedirectCallback as seen above.

A common use case is to allow redirects to the page the user was trying to access prior to authentication. This could be achieved as follows:

Login handler:

<button
    onClick={() =>
        login({
            app_state: {
                redirectTo: location.state ? location.state?.from?.pathname : null
            }
        })
    }
/>

Redirect handler:

<KindeProvider
	onRedirectCallback={(user, app_state) => {
	  if (app_state?.redirectTo) {
		  window.location = app_state?.redirectTo;
     }
   }}
>

Token storage in the authentication state

Link to this section

By default the JWTs provided by Kinde are stored in memory. This protects you from both CSRF attacks (possible if stored as a client side cookie) and XSS attacks (possible if persisted in local storage).

The trade off with this approach however is that if a page is refreshed or a new tab is opened then the token is wiped from memory, and the sign in button would need to be clicked to re-authenticate. There are two ways to prevent this behaviour:

  1. Use the Kinde custom domain feature. We can then set a secure, httpOnly cookie against your domain containing only the refresh token which is not vulnerable to CSRF attacks.
  2. There is an escape hatch which can be used for local development: isDangerouslyUseLocalStorage. This SHOULD NOT be used in production. We recommend you use a custom domain. This will store only the refresh token in local storage and is used to silently re-authenticate.
<KindeProvider
	isDangerouslyUseLocalStorage={process.env.NODE_ENV === 'development'}
	...
>

API References - KindeProvider

Link to this section

The audience claim for the JWT.

Type: string

Required: No

The ID of your application as it appears in Kinde.

Type: string

Required: Yes

Either your Kinde instance url or your custom domain. e.g https://yourapp.kinde.com

Type: string

Required: Yes

Where your user will be redirected when they log out.

Type: string

Required: No

isDangerouslyUseLocalStorage

Link to this section

An escape hatch for storing the refresh in local storage for local development.

Type: boolean

Required: No

Default: false

The URL that the user will be returned to after authentication.

Type: string

Required: Yes

The scopes to be requested from Kinde.

Type: string

Required: No

Default: openid profile email offline

API References- useKindeAuth hook

Link to this section

Constructs redirect url and sends user to Kinde to sign up and create a new org for your business.

Usage:

Arguments:

org_name?: string;
app_state?: object;
authUrlParams?: object;
createOrg();

Sample:

redirect;

Gets a claim from an access or ID token.

Arguments:

claim: string, tokenKey?: string

Usage:

getClaim("given_name", "id_token");

Sample:

"David";

getOrganization

Link to this section

Get details for the organization your user is signed into.

Usage:

getOrganization();

Sample:

{
    orgCode: "org_1234";
}

Returns the state of a given permission.

Arguments:

key: string;

Usage:

getPermission("read:todos");

Sample:

{
	orgCode: "org_1234",
	isGranted: true
}

getPermissions

Link to this section

Returns all permissions for the current user for the organization they are signed into.

Usage:

getPermissions();

Sample:

{
	orgCode: "org_1234",
	permissions: [
		"create:todos",
		"update:todos",
		"read:todos"
	]
}

Returns the raw Access token from memory.

Usage:

getToken();

Sample:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c;

Returns the profile for the current user.

Usage:

getUser();

Sample:

{
    given_name: "Dave";
    id: "abcdef";
    family_name: "Smith";
    email: "dave@smith.com";
}

getUserOrgnizations

Link to this section

Gets an array of all organizations the user has access to.

Usage:

getUserOrganizations();

Sample:

{
    orgCodes: ["org_1234", "org_5678"];
}

Constructs redirect url and sends user to Kinde to sign in.

Arguments:

org_code?: string;
app_state?: object;
authUrlParams?: object;

Usage:

login();

Sample:

redirect;

Logs the user out of Kinde.

Argument:

org_code?: string

Usage:

logout();

Sample:

redirect;

Constructs redirect url and sends user to Kinde to sign up.

Arguments:

org_code?: string;
app_state?: object;
authUrlParams?: object;

Usage:

register();

Sample:

redirect;

API References- login

Link to this section

Gets a claim from an access or ID token.

Arguments:

claim: string, tokenKey?: string

Usage:

getClaim("given_name", "id_token");

Sample:

"David";

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


Talk to us

If you can’t find what you’re looking for in our help center — email our team

Contact support