Skip to content
  • SDKs and APIs
  • Front end SDKs

JavaScript SDK

Kinde JavaScript SDK for single-page JavaScript apps.

Set up a Kinde application

Link to this section
  1. Sign in to your Kinde dashboard and select Add application

  2. Enter a name for the application (e.g., “My Single Page App”)

  3. Select Front-end and mobile as the application type, then select Save

  4. On the Quick start page, select JavaScript from the list of Front-end SDKs, then select Save

  5. Go to the Details page and copy the Domain and Client ID. You will need these values to configure your project

Using non-production environments

Link to this section

Kinde allows you to have one production environment and multiple non-production environments. If you would like to use different environments as part of your development process, learn more about environments here.

Option 1: Use the Starter kit

Link to this section
  1. On the Quick start page, select Set for both callback URL and logout URL. This will set http://localhost:3000 as your default URLs. If you want to use a different URL, you can change it in the Details page.

  2. Download the JavaScript starter kit

  3. Go to the root of your project and install the dependencies:

    Terminal window
    cd javascript-starter-kit
    npm install
  4. Create an environment variable file:

    Terminal window
    cp .env.example .env
  5. Copy the Domain and Client ID into the following variables in the .env file and save your changes.

    .env
    VITE_KINDE_CLIENT_ID=<your_kinde_client_id>
    VITE_KINDE_DOMAIN=https://<your_kinde_subdomain>.kinde.com
  6. Start the development server:

    Terminal window
    npm run dev
  7. Open the browser and navigate to http://localhost:3000 to see the application.

  8. Sign up for a new user.

  9. Go to your Kinde dashboard > Users to find your newly created user.

Option 2: Install for an existing project

Link to this section
  1. On the Quick start page, select Existing codebase and edit your project URL (defaults to http://localhost:3000)

  2. Select Set for both callback URL and logout URL.

  3. Open your project in your terminal and install the Kinde dependency with the following command:

    Terminal window
    npm i @kinde-oss/kinde-auth-pkce-js
  4. Initialize the Kinde Auth client. It must be the first thing that happens before you initialize your app. We recommend using the async/await pattern instead of the .then() chaining.

    Replace the your_kinde_client_id and https://<your_kinde_subdomain>.kinde.com with the values you found from the application Details page above.

    import createKindeClient from "@kinde-oss/kinde-auth-pkce-js";
    (async () => {
    const kinde = await createKindeClient({
    client_id: "<your_kinde_client_id>",
    domain: "https://<your_kinde_subdomain>.kinde.com",
    redirect_uri: window.location.origin
    });
    })();

Authentication

Link to this section

Sign-in and sign-up

Link to this section

The Kinde client provides login and register methods you can use in your SPA.

Add button elements:

<div id="logged_out_view">
<button id="login" type="button">Sign in</button>
<button id="register" type="button">Register</button>
</div>

You can then bind events to buttons:

document.getElementById("login").addEventListener("click", async () => {
await kinde.login();
});
document.getElementById("register").addEventListener("click", async () => {
await kinde.register();
});

Clicking either of these buttons redirects your user to Kinde, where they authenticate before being redirected back to your site.

Callback events

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.

const kinde = await createKindeClient({
client_id: <your_kinde_client_id>,
domain: <your_kinde_domain>,
redirect_uri: <redirect_uri>,
on_redirect_callback: (user, appState) => {
console.log({user, appState});
if (user) {
// render logged in view
} else {
// render logged out view
}
},
});

This is implemented in much the same way as signing in or registering. The Kinde single-page application client already includes a sign-out method.

<div id="logged_in_view">
<button id="logout" type="button">Sign out</button>
</div>
document.getElementById("logout").addEventListener("click", async () => {
await kinde.logout();
});

View user profile

Link to this section

Use the getUser() helper function to request the user information from Kinde.

const user = kinde.getUser();

The user object:

{
id: "kp_0123456789abcdef0123456789abcdef",
given_name: "Billy",
family_name: "Hoyle",
email: "billy@example.com",
picture: "https://link_to_avatar_url.kinde.com"
}

Additionally, you can use the getUserProfile() async function to request the latest user information from the server.

const user = await kinde.getUserProfile();

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

(async () => {
try {
const token = await kinde.getToken();
if (!token) {
return;
}
const response = await fetch(YOUR_API, {
headers: new Headers({
Authorization: "Bearer " + token
})
});
const data = await response.json();
console.log({data});
} catch (err) {
console.log(err);
}
})();

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

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

Create an organization

Link to this section

To create a new organization within your application, you will need to run a similar function below.

document.getElementById("createOrganization").addEventListener("click", async () => {
await kinde.createOrg();
});

Sign up or sign in users to organizations

Link to this section

Kinde has a unique code for every organization. You’ll have to pass this code through when you register a new user.

Example function:

kinde.register({org_code: "org_1234"});

If you want a user to sign in to a particular organization, pass this code with the login method.

kinde.login({org_code: "org_1234"});

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": [],
"exp": 1658475930,
"iat": 1658472329,
"iss": "https://your_subdomain.kinde.com",
"jti": "123457890",
"org_code": "org_1234",
"permissions": ["read:todos", "create:todos"],
"scp": [
"openid",
"profile",
"email",
"offline"
],
"sub": "kp_123457890"
}

The id_token will also contain an array of organizations that a user belongs to - this is useful if you want to build out an organization switcher for example.

{
...
"org_codes": ["org_1234", "org_4567"]
...
}

There are two helper functions you can use to extract information:

kinde.getOrganization();
// {orgCode: "org_1234"}
kinde.getUserOrganizations();
// {orgCodes: ["org_1234", "org_abcd"]}

User permissions

Link to this section

When a user signs in to an organization, the access token your product receives contains a custom claim with an array of permissions for that user.

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

"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:

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

A practical example in code might look something like:

if (kinde.getPermission("create:todos").isGranted) {
// show Create Todo button in UI
}

When a user signs in, the access token your product receives contains a custom claim called feature_flags, which is an object describing 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.
*/
kinde.getFlag(code, defaultValue, flagType);
/* Example usage */
kinde.getFlag("theme");
/*
{
"code": "theme",
"type": "string",
"value": "pink",
"is_default": false
}
*/
kinde.getFlag("create_competition", { defaultValue: false });
/*
{
"code": "create_competition",
"value": false,
"is_default": true
}
*/

We also provide type-specific wrapper functions that build on 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}
*/
kinde.getBooleanFlag(code, defaultValue);
/* Example usage */
kinde.getBooleanFlag("is_dark_mode");
// true
kinde.getBooleanFlag("is_dark_mode", false);
// true
kinde.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}
*/
kinde.getStringFlag(code, defaultValue);
/**
* 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}
*/
kinde.getIntegerFlag(code, defaultValue);

An audience is the intended recipient of an access token—for example, the API for your application. Pass the audience option to the Kinde client to request that value be included on the issued token.

const kinde = await createKindeClient({
audience: "<your_api>",
// ...other options
});

To request multiple audiences, pass them separated by spaces. For example:

const kinde = await createKindeClient({
audience: "<your_api1> <your_api2>",
// ...other options
});

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

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 to createKindeClient.

const kinde = await createKindeClient({
client_id: "<your_kinde_client_id>",
domain: "https://<your_kinde_subdomain>.kinde.com",
redirect_uri: "http://localhost:3000",
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:

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

JavaScript SDK FAQs

Link to this section

How to prevent authentication state from being lost on page refresh or new tab?

Link to this section

You will find that when you refresh the browser while using a front-end SDK, 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 you can provide to the Kinde Client is_dangerously_use_local_storage. 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.

How to persist application state after authentication?

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 on_redirect_callback 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:

kinde.login({
app_state: {
redirectTo: window.location.pathname,
},
});

Redirect handler:

const kinde = await createKindeClient({
client_id: "<your_kinde_client_id>",
domain: "https://<your_kinde_subdomain>.kinde.com",
redirect_uri: "http://localhost:3000",
on_redirect_callback: (user, appState) => {
if (appState?.redirectTo) {
window.location.assign(appState.redirectTo);
}
},
});

How to store the refresh token 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, the token is wiped from memory and the sign-in button must be clicked to re-authenticate. There are two ways to prevent this behavior:

  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: is_dangerously_use_local_storage. This absolutely should not be used in production and we highly recommend you use a custom domain. This will store only the refresh token in local storage and is used to silently re-authenticate.
const kinde = await createKindeClient({
client_id: "[YOUR_KINDE_CLIENT_ID]",
domain: "[YOUR_KINDE_DOMAIN]",
redirect_uri: window.location.origin,
is_dangerously_use_local_storage: true
});

When does the JavaScript SDK refresh access tokens in the background?

Link to this section

The PKCE client refreshes the access token in two main situations:

  • Initial load: If you use Kinde’s httpOnly cookie refresh flow (custom domain) or local storage for the refresh token (is_dangerously_use_local_storage), initialization may call the token endpoint to restore a session.
  • When you call getToken() or getIdToken(): If the cached access token is missing or the SDK treats it as no longer active (it uses a short buffer before the JWT exp so the token is refreshed before it actually expires), the client attempts a refresh using the refresh token.

The React SDK uses @kinde/js-utils for additional timer- and focus-driven refresh on the provider. With this JavaScript client, calling getToken() before API requests is the reliable way to obtain a token that has been refreshed when needed.

Does getToken throw or return an error object when refresh fails?

Link to this section

No. getToken() resolves to Promise<string | undefined>. You receive the JWT string on success and undefined if the token cannot be obtained (for example, refresh failed or there is no refresh token). It does not throw for those cases and does not return a structured error object. Always check for a missing value before using the token in an Authorization header.

API References - createKindeClient

Link to this section

The unique ID of your application in Kinde.

Type: string (required)

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

Type: string (required)

The URL that the user will be returned to after authentication. This must match an allowed callback URL for the application in Kinde.

Type: string (required)

The audience claim for the JWT.

Type: string

Required: No

For background, multiple audiences, and registering APIs, see Audience.

The scopes to be requested from Kinde.

Type: string

Required: No

Default: openid profile email offline. For the full list, overrides, and examples, see Overriding scope.

Where your user will be redirected when they sign out.

Type: string

Required: No

Default: the same value as redirect_uri when omitted.

on_redirect_callback

Link to this section

A callback function that will be called when the user is redirected back to your site from Kinde.

Type: function

Required: No

Use with app_state from login and register; see Persisting application state.

Usage:

const kinde = await createKindeClient({
// ...other options,
on_redirect_callback: (user, appState) => {
console.log({user, appState});
if (user) {
// render logged in view
} else {
// render logged out view
}
}
});

on_error_callback

Link to this section

A callback function that will be called when an error occurs.

Type: function

Required: No

Usage:

const kinde = await createKindeClient({
// ...other options,
on_error_callback: (errorProps) => {
console.log(errorProps);
}
});

is_dangerously_use_local_storage

Link to this section

An escape hatch for storing the refresh token in local storage. Recommended for local development only, and not production.

Type: boolean

Required: No

Default: false

See Token storage in the authentication state and Persisting authentication state on page refresh or new tab.

proxy_redirect_uri

Link to this section

By default, when code or error is present in the query string, the SDK checks whether the current page URL (protocol, host, and path) matches redirect_uri. If your setup means that check would fail even though Kinde correctly sent the user to this page—for example some proxy or hosting arrangements—set proxy_redirect_uri to the same string as redirect_uri so the SDK still treats this response as the OAuth callback.

Type: string

Required: No

API References - kindeClient methods

Link to this section

isAuthenticated

Link to this section

Returns whether the user is currently authenticated.

Type: Promise<boolean>

Usage:

await kinde.isAuthenticated();

Sample output:

true

Constructs the redirect URL and sends the user to Kinde to sign in.

Arguments:

Optional options uses AuthOptions:

type AuthOptions = {
org_code?: string;
app_state?: Record<string, unknown>;
authUrlParams?: object;
};
  • org_code — Sign the user in to a specific organization. Pass that organization’s Kinde code.
  • app_state — Data returned to your app after redirect. Read it from the second argument of on_redirect_callback (see Persisting application state).
  • authUrlParams — Optional.

Usage:

await kinde.login();
await kinde.login({ org_code: "org_1234" });
await kinde.login({
app_state: { redirectTo: window.location.pathname },
});

Constructs the redirect URL and sends the user to Kinde to sign up.

Arguments:

Same optional AuthOptions as login.

Usage:

await kinde.register();

Logs the user out of Kinde.

Usage:

await kinde.logout();

Constructs the redirect URL and sends the user to Kinde to sign up and create a new organization for your business.

Arguments:

options?: OrgOptions

Usage:

await kinde.createOrg();

Sample output:

redirect;

Returns the access token JWT. If the cached token is missing or inactive, the client attempts a silent refresh via the refresh token before resolving.

Type: Promise<string | undefined> — JWT string on success, undefined if no token can be returned. Failures do not throw and do not yield an error object.

Usage:

await kinde.getToken();

Sample output:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Use the Kinde Online JWT decoder to decode this token.

For background refresh timing and the difference from the React SDK’s getAccessToken(), see When does the JavaScript SDK refresh access tokens in the background? and Does getToken throw or return an error object when refresh fails?.

Returns the raw ID token from memory.

Type: Promise<string | undefined>

Usage:

await kinde.getIdToken();

Sample output:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Returns the profile for the current user from the client.

Type: KindeUser

Usage:

kinde.getUser();

Sample output:

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

getUserProfile

Link to this section

Returns the latest user profile from the server.

Type: Promise<KindeUser | undefined>

Usage:

await kinde.getUserProfile();

Sample output:

{
id: "kp_0123456789abcdef0123456789abcdef",
given_name: "Billy",
family_name: "Hoyle",
email: "billy@example.com",
picture: "https://link_to_avatar_url.kinde.com"
}

Gets a claim from an access or ID token. Defaults to the access token unless you pass a different tokenKey.

Arguments:

claim: string, tokenKey?: ClaimTokenKey

Usage:

kinde.getClaim("aud");
kinde.getClaim("given_name", "id_token");

Sample output:

{
name: "given_name",
value: "David",
}

getOrganization

Link to this section

Get details for the organization your user is signed in to.

Usage:

kinde.getOrganization();

Sample output:

{
orgCode: "org_1234",
}

getUserOrganizations

Link to this section

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

Usage:

kinde.getUserOrganizations();

Sample output:

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

getPermissions

Link to this section

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

Usage:

kinde.getPermissions();

Sample output:

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

Returns the state of a given permission.

Arguments:

key: string

Usage:

kinde.getPermission("read:todos");

Sample output:

{
orgCode: "org_1234",
isGranted: true,
}

Returns a feature flag from the access token’s feature_flags claim. When you pass flagType, the return type is narrowed.

Arguments:

code: string, defaultValue?: KindeFlagValueType[T], flagType?: T

(T extends KindeFlagTypeCode.)

Usage:

kinde.getFlag("theme");
kinde.getFlag("competitions_limit", 5, "i");

Sample output:

{
code: "theme",
type: "string",
value: "pink",
is_default: false,
}

getBooleanFlag

Link to this section

Gets a boolean feature flag from the access token.

Arguments:

code: string, defaultValue?: boolean

Returns: boolean | Error

Usage:

kinde.getBooleanFlag("is_dark_mode");
kinde.getBooleanFlag("new_feature", false);

Sample output:

true

Gets a string feature flag from the access token. defaultValue is required.

Arguments:

code: string, defaultValue: string

Returns: string | Error

Usage:

kinde.getStringFlag("theme", "default");

Sample output:

"pink"

getIntegerFlag

Link to this section

Gets an integer feature flag from the access token. defaultValue is required.

Arguments:

code: string, defaultValue: number

Returns: number | Error

Usage:

kinde.getIntegerFlag("competitions_limit", 0);

Sample output:

5

Reach out to support@kinde.com if you need help getting Kinde connected.