JavaScript SDK
Kinde JavaScript SDK for single-page JavaScript apps.
What you need
Link to this section- A Kinde account (Sign up for free)
- An existing JavaScript single-page app. Try the JavaScript starter kit
Set up a Kinde application
Link to this section-
Sign in to your Kinde dashboard and select Add application
-
Enter a name for the application (e.g., “My Single Page App”)
-
Select Front-end and mobile as the application type, then select Save
-
On the Quick start page, select JavaScript from the list of Front-end SDKs, then select Save
-
Go to the Details page and copy the Domain and Client ID. You will need these values to configure your project
No client secretJavaScript single page apps do not have a client secret for security reasons.
Using non-production environments
Link to this sectionKinde 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-
On the Quick start page, select Set for both callback URL and logout URL. This will set
http://localhost:3000as your default URLs. If you want to use a different URL, you can change it in the Details page. -
Download the JavaScript starter kit
-
Go to the root of your project and install the dependencies:
Terminal window cd javascript-starter-kitnpm install -
Create an environment variable file:
Terminal window cp .env.example .env -
Copy the Domain and Client ID into the following variables in the
.envfile and save your changes..env VITE_KINDE_CLIENT_ID=<your_kinde_client_id>VITE_KINDE_DOMAIN=https://<your_kinde_subdomain>.kinde.com -
Start the development server:
Terminal window npm run dev -
Open the browser and navigate to http://localhost:3000 to see the application.
-
Sign up for a new user.
-
Go to your Kinde dashboard > Users to find your newly created user.
Option 2: Install for an existing project
Link to this section-
On the Quick start page, select Existing codebase and edit your project URL (defaults to
http://localhost:3000) -
Select Set for both callback URL and logout URL.
-
Open your project in your terminal and install the Kinde dependency with the following command:
Terminal window npm i @kinde-oss/kinde-auth-pkce-jsTerminal window yarn add @kinde-oss/kinde-auth-pkce-jsTerminal window pnpm add @kinde-oss/kinde-auth-pkce-js -
Initialize the Kinde Auth client. It must be the first thing that happens before you initialize your app. We recommend using the
async/awaitpattern instead of the.then()chaining.Replace the
your_kinde_client_idandhttps://<your_kinde_subdomain>.kinde.comwith 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 sectionSign-in and sign-up
Link to this sectionThe 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 sectionOnce 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 } },});Log out
Link to this sectionThis 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 sectionUse 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();Call your API
Link to this sectionThe 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.
Organizations
Link to this sectionFor general information about using organizations, see Kinde organizations for developers.
Create an organization
Link to this sectionTo 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 sectionKinde 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 sectionWhen 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}Feature flags
Link to this sectionWhen 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);Audience
Link to this sectionAn 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 sectionBy default the JavaScript SDK requests the following scopes:
profileemailofflineopenid
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 sectionWe 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 sectionHow to prevent authentication state from being lost on page refresh or new tab?
Link to this sectionYou 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 sectionThe 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 sectionBy 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:
- 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:
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 sectionThe 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()orgetIdToken(): If the cached access token is missing or the SDK treats it as no longer active (it uses a short buffer before the JWTexpso 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 sectionNo. 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 sectionclient_id
Link to this sectionThe unique ID of your application in Kinde.
Type: string (required)
domain
Link to this sectionEither your Kinde instance URL, e.g https://yourapp.kinde.com or your custom domain.
Type: string (required)
redirect_uri
Link to this sectionThe 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)
audience
Link to this sectionThe audience claim for the JWT.
Type: string
Required: No
For background, multiple audiences, and registering APIs, see Audience.
scope
Link to this sectionThe 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.
logout_uri
Link to this sectionWhere 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 sectionA 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 sectionA 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 sectionAn 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 sectionBy 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 sectionisAuthenticated
Link to this sectionReturns whether the user is currently authenticated.
Type: Promise<boolean>
Usage:
await kinde.isAuthenticated();Sample output:
truelogin
Link to this sectionConstructs 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 ofon_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 },});register
Link to this sectionConstructs the redirect URL and sends the user to Kinde to sign up.
Arguments:
Same optional AuthOptions as login.
Usage:
await kinde.register();logout
Link to this sectionLogs the user out of Kinde.
Usage:
await kinde.logout();createOrg
Link to this sectionConstructs the redirect URL and sends the user to Kinde to sign up and create a new organization for your business.
Arguments:
options?: OrgOptionsUsage:
await kinde.createOrg();Sample output:
redirect;getToken
Link to this sectionReturns 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_adQssw5cUse 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?.
getIdToken
Link to this sectionReturns the raw ID token from memory.
Type: Promise<string | undefined>
Usage:
await kinde.getIdToken();Sample output:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...getUser
Link to this sectionReturns 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 sectionReturns 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"}getClaim
Link to this sectionGets a claim from an access or ID token. Defaults to the access token unless you pass a different tokenKey.
Arguments:
claim: string, tokenKey?: ClaimTokenKeyUsage:
kinde.getClaim("aud");kinde.getClaim("given_name", "id_token");Sample output:
{ name: "given_name", value: "David",}getOrganization
Link to this sectionGet details for the organization your user is signed in to.
Usage:
kinde.getOrganization();Sample output:
{ orgCode: "org_1234",}getUserOrganizations
Link to this sectionGets an array of all organizations the user has access to.
Usage:
kinde.getUserOrganizations();Sample output:
{ orgCodes: ["org_1234", "org_5678"],}getPermissions
Link to this sectionReturns 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"],}getPermission
Link to this sectionReturns the state of a given permission.
Arguments:
key: stringUsage:
kinde.getPermission("read:todos");Sample output:
{ orgCode: "org_1234", isGranted: true,}getFlag
Link to this sectionReturns 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 sectionGets a boolean feature flag from the access token.
Arguments:
code: string, defaultValue?: booleanReturns: boolean | Error
Usage:
kinde.getBooleanFlag("is_dark_mode");kinde.getBooleanFlag("new_feature", false);Sample output:
truegetStringFlag
Link to this sectionGets a string feature flag from the access token. defaultValue is required.
Arguments:
code: string, defaultValue: stringReturns: string | Error
Usage:
kinde.getStringFlag("theme", "default");Sample output:
"pink"getIntegerFlag
Link to this sectionGets an integer feature flag from the access token. defaultValue is required.
Arguments:
code: string, defaultValue: numberReturns: number | Error
Usage:
kinde.getIntegerFlag("competitions_limit", 0);Sample output:
5Reach out to support@kinde.com if you need help getting Kinde connected.