K
Kinde•3w ago
Tito

Authenticating API calls with Kinde

I have a react native app and a backend NextJS app both running Kinde. How to authenticate API calls from the app to the backend to authenticate a user? Authorization: Bearer {token} in http headers doesn't work with idToken or accessToken
28 Replies
Tito
TitoOP•3w ago
i registered the API as described here https://docs.kinde.com/developer-tools/your-apis/register-manage-apis/ I am using the expo-startkit and I am not sure where to pass the audience parameter. exchangeCodeAsync, request.promptAsync and authRequest do not accept that parameter
Kinde docs
Register and manage APIs
Our developer tools provide everything you need to get started with Kinde.
Ages
Ages•3w ago
Hi @Tito , Thank you for reaching out. To ensure that your API calls are authenticated correctly, it's essential to include the appropriate audience parameter when initializing the Kinde SDK in your React Native application. This audience parameter specifies the intended recipient of the token, typically your backend API. In your React Native application, when setting up the Kinde SDK, include the audience parameter in the configuration. Here's an example of how to do this:
import { KindeSDK } from '@kinde-oss/react-native-sdk-0-7x'; const client = new KindeSDK( 'YOUR_KINDE_DOMAIN', 'YOUR_REDIRECT_URI', 'YOUR_CLIENT_ID', 'YOUR_POST_LOGOUT_REDIRECT_URI', 'profile email openid', { audience: 'https://yourapi.example.com', } ); export default client;
Ensure that the audience value matches the Audience URL you specified when registering your API in the Kinde dashboard. https://docs.kinde.com/developer-tools/sdks/native/expo-react-native After initializing the SDK and obtaining the access token, include it in the Authorization header of your HTTP requests from the React Native app to your Next.js backend. Example:
const response = await fetch('https://yourapi.example.com/endpoint', { method: 'GET', headers: { Authorization: Bearer ${accessToken}, }, });
On your Next.js backend, validate the incoming token to ensure it's valid and has the correct audience claim. Additional Considerations: - Ensure that you're using a compatible version of the Kinde React Native SDK with your Expo setup. https://docs.kinde.com/developer-tools/sdks/native/react-native-sdk Token Validation: On your backend, verify the access token's aud claim to ensure it matches your API's audience identifier. This step is crucial for security to confirm that the token was intended for your API. If you encounter further issues or have additional questions, please don't hesitate to reach out
Kinde docs
Expo and React Native SDK
Our developer tools provide everything you need to get started with Kinde.
Kinde docs
React Native SDK
Our developer tools provide everything you need to get started with Kinde.
Tito
TitoOP•3w ago
I wasn't able to use @kinde-oss/react-native-sdk-0-7x (doesn't use expo-storage properly) nor @kinde/expo (doesn't work, probably because it doesn't redirect back to app correctly), so I had to use https://github.com/kinde-starter-kits/expo-starter-kit/ directly. Where should I pass the audience parameter there?
GitHub
GitHub - kinde-starter-kits/expo-starter-kit
Contribute to kinde-starter-kits/expo-starter-kit development by creating an account on GitHub.
Ages
Ages•3w ago
Hi Tito,
Hope you're doing well! 😊
To pass the audience parameter in your Expo Starter Kit setup, you’ll need to update the authentication request inside app/index.tsx.

Find this section in your authenticate function and modify it like this: const request = new AuthRequest({
clientId: process.env.EXPO_PUBLIC_KINDE_CLIENT_ID!,
redirectUri,
scopes: ['openid', 'profile', 'email', 'offline'],
responseType: "code",
extraParams: {
has_success_page: "true",
audience: "your-api-audience", // Add this line
...mapLoginMethodParamsForUrl(options),
},
});
Make sure to replace "your-api-audience" with the actual audience value (usually your API URL).
This ensures your tokens are issued for the correct API. Let me know if you have any questions—happy to help! 🚀
Tito
TitoOP•3w ago
Thanks, doesn't work. I have no error message to show, it simply doesn't find the user when I call getUser on the server I "activated" the registered API from the Kinde dashboard (for both the front-end and the back-end apps) i doubled checked the API URLs that is regsitered and that I pass in audience (both including https://) I logged out and logged in again to make sure the right token is used Any github repo to look for an example of client and servers talking to each other (outside of NextJS server actions that work out of the box)?
Zaki
Zaki•3w ago
Hi Tito, just to confirm—did the user successfully log in on the client side, and is the issue occurring when calling getUser on the Next.js server side?
Tito
TitoOP•3w ago
yes anything else i can call on server side other than getUser to get more info on why it's failing? maybe i should refresh the accessToken on the client side first? i tried several times to logout and login to use a new accessToken, same problem
Zaki
Zaki•3w ago
After the user successfully logs in on Expo, an access token is obtained and stored. When making a request to the Next.js endpoint, you should pass the stored access token in the Authorization header as a Bearer token. Next.js should then validate this token and use it to fetch the required data.
Tito
TitoOP•3w ago
i see the token is correctly transmitted and received server side. getUser just returns null
import { getKindeServerSession } from "@kinde-oss/kinde-auth-nextjs/server";

export async function POST(request: Request) {
try {
const headers = request.headers;
headers.forEach((value, key) => {
console.log(`[api/profiles] POST: Header: ${key}, Value: ${value}`);
});
const { getUser } = getKindeServerSession();
const user = await getUser();
if(!user) {
console.warn(`[api/profiles] POST: user not authenticated`);
throw new Error(`User not authenticated`);
}
const data = await request.json();
const profileId = await createProfile(data);
return NextResponse.json({ ok: true, profileId });
} catch (e) {
console.error(`[api/profiles] POST error`, e);
return NextResponse.json({ ok: false, error: e instanceof Error ? e.message : 'Unknown error' }, { status: 500 });
}
}
import { getKindeServerSession } from "@kinde-oss/kinde-auth-nextjs/server";

export async function POST(request: Request) {
try {
const headers = request.headers;
headers.forEach((value, key) => {
console.log(`[api/profiles] POST: Header: ${key}, Value: ${value}`);
});
const { getUser } = getKindeServerSession();
const user = await getUser();
if(!user) {
console.warn(`[api/profiles] POST: user not authenticated`);
throw new Error(`User not authenticated`);
}
const data = await request.json();
const profileId = await createProfile(data);
return NextResponse.json({ ok: true, profileId });
} catch (e) {
console.error(`[api/profiles] POST error`, e);
return NextResponse.json({ ok: false, error: e instanceof Error ? e.message : 'Unknown error' }, { status: 500 });
}
}
Daniel_Kinde
Daniel_Kinde•3w ago
Hi @Tito, Dropping into this conversation.
With the starter kit, it uses the ExpoSecureStore from our @kinde/js-utils library. This unlocked all our helper methods. One of which is getUserProfile which will get the information form the idToken. This work for you? As for the getUser not working, your NextJS site is only receiving the access token, not the id token, so it will only know the rights and not the who it is. What are you lookign to achieve here?
Tito
TitoOP•3w ago
Client-side, the login is working and i can get the user profile. From the client app, I want to call an api on my NextJS backend i call it with fetch and pass the accessToken in the Authorization: Bearer header on the nextjs server side, shouldn't it pick it up using the above code? i would like the server-side to recognize the accessToken and identify the user calling based on the accessToken should i pass the idToken instead in Authorization: Bearer {idToken} ? That doesn't work either In both cases, if I pass idToken or accessToken in the Authorization header from the client, on the server isAuthenticated returns false and getUser returns null the nextjs app works fine for users logged in on nextjs
Daniel_Kinde
Daniel_Kinde•3w ago
Because you're not using the authentication from NextJS, it does not know the context and not setup for the user. Within the access token you have a sub claim which contains the users Kinde ID. From this you can call the Kinde API to get the users details. For this you will have to create a M2M application within Kinde and setup the relevant scopes for access. Then use the JS Management package (https://github.com/kinde-oss/management-api-js) to request the users details.
GitHub
GitHub - kinde-oss/management-api-js: javascript package for intera...
javascript package for interacting with the Kinde Management API - kinde-oss/management-api-js
Tito
TitoOP•3w ago
Thanks. I previously managed to use M2M API to get a user by email. How do I get users details given an accessToken? I managed. This should be a very common use case that could be documented or have some utility libraries to actually use it. I am now facing the question of how to get a long-lived token for the M2M application. I initially struggled because my token had expired
Daniel_Kinde
Daniel_Kinde•3w ago
We have made lots of changes and updates to simplify the JS ecosystem over the last few months, docs are catching up.
import {Users, init} from "@kinde/manangement-api-js"

...
init()
const user = await Users.getUserData({ id: 'kp_xxx' });
import {Users, init} from "@kinde/manangement-api-js"

...
init()
const user = await Users.getUserData({ id: 'kp_xxx' });
Tito
TitoOP•3w ago
thanks ok docs always talk about "test token" but how do i get a server-to-server token that lasts longer than an hour?
Daniel_Kinde
Daniel_Kinde•3w ago
The kinde managment-api-js package will handle that for you within your application.
Tito
TitoOP•3w ago
The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Consider writing a dynamic 'import("@kinde/management-api-js")' call instead. To convert this file to an ECMAScript module, change its file extension to '.mts', or add the field "type": "module" to '/Users/.../streckenheldnext/package.json'. Cursor warning on the import statement i played around, i can't use this management-api-js module, not sure if the npm package is the latest version i don't have the issue with the other kinde packages
Daniel_Kinde
Daniel_Kinde•3w ago
I have just tried the package locally on a NextJS project and works with no errors. Could you let me know what NextJS version and package manager you're using please?
corwin
corwin•3w ago
WAIT I LITERALLY AM HAVING THE SAME ISSUE AS YOU. Trying to get my expo app to talk to my nextjs for auth came in here today and searched "Expo" btw i had to use secure store for this but a bit confused that kinde now has an expo package since last august but not linked in documentaiton anywhere? just found it on github. https://github.com/kinde-oss/expo
Daniel_Kinde
Daniel_Kinde•3w ago
We are holding off fully pushing the new Expo SDK, there are a few edge cases which are causing frustrations, we are workign with Expo directly along side partners to get this resolved. We have the Expo starter kit which is a unpackaged version of the SDK which works correctly. Does what is talked about here help you @corwin ?
corwin
corwin•3w ago
about to read thru the entire thread and test it out!
Tito
TitoOP•3w ago
Using npm 10.1.0, react 18.2.0, next 14.1.0 And:
"@kinde-oss/kinde-auth-nextjs": "^2.3.10",
"@kinde/management-api-js": "^0.11.0",
"@kinde-oss/kinde-auth-nextjs": "^2.3.10",
"@kinde/management-api-js": "^0.11.0",
For posterity, this is how I call Kinde Management APIs from a NextJS backend, given a user's accessToken (originally obtained by having the user login on an expo react native app):
export async function getKindeUserByAccessToken(accessToken: string): Promise<KindeUser | null> {
try {
// Decode the JWT token (it's base64 encoded)
const [, payload] = accessToken.split('.');
const decodedPayload = JSON.parse(Buffer.from(payload, 'base64').toString());
const kindeId = decodedPayload.sub;

if (!kindeId) {
throw new Error('No sub claim found in access token');
} else {
console.debug(`[kinde] Found Kinde ID ${kindeId} in access token`);
}

// Fetch the full user details using the Kinde ID
const userResponse = await fetch(
`${process.env.KINDE_ISSUER_URL}/api/v1/user?id=${kindeId}`,
{
headers: {
Authorization: `Bearer ${process.env.KINDE_API_KEY}`,
Accept: "application/json",
},
}
)

if (!userResponse.ok) {
const json = await userResponse.json();
console.warn(`[kinde] Failed to fetch Kinde user: ${userResponse.statusText}:\n`, json);
return null;
}

const userData = await userResponse.json()
return userData;
} catch (error) {
console.error("Error fetching Kinde user by access token:", error)
return null
}
}
export async function getKindeUserByAccessToken(accessToken: string): Promise<KindeUser | null> {
try {
// Decode the JWT token (it's base64 encoded)
const [, payload] = accessToken.split('.');
const decodedPayload = JSON.parse(Buffer.from(payload, 'base64').toString());
const kindeId = decodedPayload.sub;

if (!kindeId) {
throw new Error('No sub claim found in access token');
} else {
console.debug(`[kinde] Found Kinde ID ${kindeId} in access token`);
}

// Fetch the full user details using the Kinde ID
const userResponse = await fetch(
`${process.env.KINDE_ISSUER_URL}/api/v1/user?id=${kindeId}`,
{
headers: {
Authorization: `Bearer ${process.env.KINDE_API_KEY}`,
Accept: "application/json",
},
}
)

if (!userResponse.ok) {
const json = await userResponse.json();
console.warn(`[kinde] Failed to fetch Kinde user: ${userResponse.statusText}:\n`, json);
return null;
}

const userData = await userResponse.json()
return userData;
} catch (error) {
console.error("Error fetching Kinde user by access token:", error)
return null
}
}
corwin
corwin•3w ago
i got mine working… i send bearer to my backend and use well-known jwks to verify. but now my expo client can see data from my backend and i have my app working on expo and nextjs web app too
Zaki
Zaki•3w ago
Hi @corwin, great to hear it’s working for you! @Tito, could you confirm if it’s resolved on your end as well? Feel free to reach out if anything else comes up—we’re here to help!
Tito
TitoOP•3w ago
Three outstanding issues for me: - I can extract user info using m2m from backend, but I don't think the JWT token is "Verified". How do I verify validity of the token from the backend? - I couldn't figure out how to have a long-lived access token for the M2M api, i only see references to "test-tokens" - I can't import management-api-js due to an error (see above), for now I am just using REST API directly The problem was maybe related to the fact that I initially started by NextJS backend app by editing [email protected] which defines a next.config.js with require syntax instead of import i also had to add "type": "module" to package.json But the issue is still that Module '"@kinde/management-api-js"' has no exported member 'Users'. Actually I had to revert it back because there is a chain reaction of other Next packages that can't be imported any more. I can't import management-api-js because it creates some sort of confusion between ES module and CommonJS module.
Daniel_Kinde
Daniel_Kinde•3w ago
In your TS config can you update these settings
"module": "esnext",
"moduleResolution": "node",
"module": "esnext",
"moduleResolution": "node",
Tito
TitoOP•2w ago
Thank you, it works now. Notice that in https://github.com/kinde-oss/management-api-js, you should change the docs in Configuration KINDE_DOMAIN e.g. https://mybusiness.kinde.com as the https:// is required. I would call it KINDE_URL.
Ages
Ages•2w ago
Great to hear it worked! Let us know if you need anything else—we're here to help.

Did you find this page helpful?