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.
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
.
# npm
npm i @kinde-oss/kinde-auth-react
# yarn
yarn add @kinde-oss/kinde-auth-react
# pnpm
pnpm i @kinde-oss/kinde-auth-react
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 the URLs in Kinde so that after your user signs up, signs in, or signs out, they will be redirected back to your application.
- In Kinde, go to Settings > Applications > [your app] > View details.
- 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. - 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
- Allowed callback URLs (also known as redirect URIs) - for exampleÂ
- 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.
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>
…
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});
}}
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.
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.
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>
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.
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;
}
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"
...
>
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"}
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.
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;
}
}}
>
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:
- 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.
- 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'}
...
>
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
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
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";
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
}
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";
}
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;
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.
Developer tools