Welcome to the Kinde community

Updated 2 months ago

Best way to access the management API from Nuxt server routes?

Hello, I am kinda new to working with Kinde and am planning on using it for my future projects as it has been a very nice experience till now.

I was trying to figure out the best way to access the management API from a Nuxt server route.

My current use case is to add a role to a user from a stripe subscription webhook.

It is written in the module's docs that the management API is a feature that has not yet seen the day but should still be accessible as it uses the Typescript SDK under the hood right?

Here is the project I am working on: https://github.com/ColinEspinas/starter

Have a wonderful day, Colin.
D
C
41 comments
Hi @Colin

Thanks for reaching out, great looking starter setup!

You're correct the Nuxt module does sit on top of our TS SDK, I will look at putting together and example for you and get back to you this week if thats ok?
Thanks for your answer, I'll probably also tinker with it a bit in the meantime waiting for your example.
@Colin Quick update from me, I have made some progress on getting this sorted, hoping to have a sharable solution soon (which could make it into the Module soon afterwards)
Hey, this would be great, would love to help and contribute to this module once I am a bit more used to kinde. I've got some sort of working prototype on my end but it is not very pretty
Hi @Colin

I have a solution for you which works with the whole Kinde Management API.

If you put the attached file the following location /server/utils/

You can then use it from any server route like this, there is no need to import, Nuxt auto imports will handle that for you.

Plain Text
  const kindeManagementApi = await useKindeManagementApi();
  const users = await kindeManagementApi.usersApi.getUsers();


Everything under kindeManagementApi is fully typed which should help you here.

Let me know how you get on,.
@Daniel_Kinde It looks a bit like what I've done but I couldn't get the token like you did.

Do you use the same app client and secret for the management api as the nuxt app or do you create a new app for that?

Here is what I've come up to for the user api (could transform in a general purpose one):
Plain Text
export async function useKindeUserApi(event: H3Event): Promise<UsersApi> {
  const { authDomain, machineClientId, machineClientSecret } = useRuntimeConfig().kinde
  const client = event.context.kinde as ({ sessionManager: SessionManager } & ACClient)

  const kindeApiClient = createKindeServerClient(GrantType.CLIENT_CREDENTIALS, {
    authDomain,
    clientId: machineClientId,
    clientSecret: machineSecretId,
    audience: `${authDomain}/api`,
    logoutRedirectURL: authDomain,
  })

  const token = await kindeApiClient.getToken(client.sessionManager)

  const config = new Configuration({
    basePath: authDomain,
    accessToken: token,
    headers: { Accept: 'application/json' },
  })

  return new UsersApi(config)
}
The issue here I think is that I am using the wrong session manager and can't get the token that way. I'll try your solution
@Colin Yea, I opted for going to the API directly as I only wanted to token at this point. When I come to add the module may approach differently.

The one issue with my solution right now is it will get a new token every time the API is hit and won't cache it. That is something that would need to be improved.

I was able to get the list of users all ok using my implementation, once you're happy its working ok I will work to make it part of the core module.
For the cache issue you could cache it with nitro's cached functions feature (https://nitro.unjs.io/guide/cache#function). I also recommend using the $fetch in nitro as it is made to play better with all serverless environments and runtimes (https://nitro.unjs.io/guide/fetch), not a very important change I guess but could prevent issues down the road.

In the module I suggest naming the returned properties without the "Api" word as it would make more sense to use.
Plain Text
const users = await kindeManagementApi.users.getUsers();
// Instead of:
const users = await kindeManagementApi.usersApi.getUsers();


What do you think?
I'll try to get it working with the cached function.

For the client and secret id, I tought we were supposed to create a machine to machine app and get different ids from here, is it not the case ?
Yes should 100% be using the $fetch, I was throwing together a solution for now to get you unblocked.

The token generated is a M2M token you're correct, a normal user token can also be used here. The difference being that the M2M token will have no context of a logged in user.

As for the naming of the Api modules, I have used the same names we use in the Next.js SDK for consistency, although I agree the Api part is redundant here as the unit composable is called kindeManagementApi, let me discuss with the team on naming here.
@Daniel_Kinde Got something that seems to work (at least to get the user list)

Plain Text
const { kinde: config } = useRuntimeConfig()

const getApiToken = defineCachedFunction(async () => {
  const response = await $fetch<{ access_token: string }>(`${config.authDomain}/oauth2/token`, {
    method: 'POST',
    headers: {
      'content-type': 'application/x-www-form-urlencoded',
    },
    body: new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: config.clientId,
      client_secret: config.clientSecret,
      audience: `${config.authDomain}/api`,
    }),
  })
  return response.access_token
}, {
  maxAge: 86400,
  name: 'kindeApiToken',
})

export async function useKindeManagementApi() {
  const apiToken = await getApiToken()

  const configuration = new Configuration({
    basePath: config.authDomain,
    accessToken: apiToken,
    headers: { Accept: 'application/json' },
  })

  return {
    users: new UsersApi(configuration),
    oauth: new OAuthApi(configuration),
    subscribers: new SubscribersApi(configuration),
    organizations: new OrganizationsApi(configuration),
    connectedApps: new ConnectedAppsApi(configuration),
    featureFlags: new FeatureFlagsApi(configuration),
    environments: new EnvironmentsApi(configuration),
    permissions: new PermissionsApi(configuration),
    roles: new RolesApi(configuration),
    business: new BusinessApi(configuration),
    industries: new IndustriesApi(configuration),
    timezones: new TimezonesApi(configuration),
    applications: new ApplicationsApi(configuration),
    callbacks: new CallbacksApi(configuration),
    apis: new APIsApi(configuration),
  }
}
What do you think ?
Token is cached for 86400 seconds as this is the default in the kinde dashboard but could be set as an env variable
I also removed the return type as it is infered in my setup and didn't want it to be too big on discord but I think it should definitely be added back if implemented in the module
Looks great. Use that for now and I will work to get it in the module
Thanks for the help, I'll make sure to check the PR for this
@Daniel_Kinde So I've been trying to add a role to a user from the management API and can't get it to work, I get a 400.

Here is the code for that:
Plain Text
const kindeManagementApi = await useKindeManagementApi()
const { roles } = await kindeManagementApi.roles.getRoles()
const proRoleId = roles?.find(role => role.key === 'pro')?.id
await kindeManagementApi.organizations.createOrganizationUserRole({
  orgCode: kinde.defaultOrg,
  userId,
  createOrganizationUserRoleRequest: {
    roleId: proRoleId,
  },
}).catch(error => console.log(error))
userId coming from the getUserProfile().id from the kinde client of the nitro app
I am looking into this @Colin , will get back to you once I have an answer.
@Colin Can I check something? Does the user you're adding the role to already have the role assigned to it?
@Daniel_Kinde Oh I think I see what is happening, Nuxt is calling my endpoint 2 times in a row (once server and once client side) the second time the role is already there so it throws
Is there any way I can check for that instead of receiving a generic bad request error ?
@Colin I am not seeing the double request happening. If I remove the role from the user, it works ok. If you have in the server utils and running from the API, it wouldn't fire from the client?
Because I am fetching my endpoint from the client right now with a useFetch()
I suppose I'll just check the roles on the user before trying to add the role, could be great to have a way to know the issue from the error.
So I basically just created a util to handle that:
Thanks for all the help you've provided could have been stuck on this for a long time haha
@Daniel_Kinde Okay I keep finding strange things, now on the front side, when I do client.getOrganization() or client.getUser() I get null when trying to get the org code, also when I do client.getPermissions() I get an empty array for permissions but with the right org code and finally client.getClaim('roles') has a value of null. I do have a role that adds a permission so it should be there
Maybe I should open new a new feed as this is not the same issue
Have you got any code examples you can share on how you're going about this?
Just like the docs
Plain Text
const kindeClient = useKindeClient()

const { data: permissions } = await useAsyncData(async () => {
  const { permissions } = (await kindeClient?.getPermissions()) ?? {}
  return permissions
})

console.log(permissions.value)
Replace the .getPermissions() by any other method and that is how I tested
Just to give more context, I checked the boxes to add the roles claim in the access token, my user does have a role and this role has a permission that should be granted
A bit more insight
Plain Text
const kindeClient = useKindeClient()

const { data: permissions } = await useAsyncData(async () => {
  const { permissions } = (await kindeClient?.getPermissions()) ?? {}
  return permissions
})
console.log(permissions.value)

const { data: hasAccess } = await useAsyncData(async () => {
  return (await kindeClient?.getPermission('unlimited-tasks')) ?? {}
})
console.log(hasAccess.value.isGranted)

const { data: orgs } = await useAsyncData(async () => {
  const orgs = (await kindeClient?.getUserOrganizations()) ?? {}
  return orgs
})

console.log(orgs.value)

const { data: org } = await useAsyncData(async () => {
  const org = (await kindeClient?.getOrganization()) ?? {}
  return org
})

console.log(org.value)

Returns:
Plain Text
[] <- permissions
false <- hasAccess.isGranted
null <- orgs
{ orgCode: 'org_4b7dc412b0c' } <- org

Seems like today getOrganization works, not changed anything and yesterday it was null
Also getUser does not have any data about the org anymore and returns the same thing as getUserProfile
Did you do an update of the API?
I mean we should probably take this to another thread right ?
So I think it was because I did not refresh my tokens
This is really a problem, I just logged in another account, cameback to the original one and roles and permissions are empty again
I created a new thread about this matter https://discord.com/channels/1070212618549219328/1206007767865888789 as my initial issue with the management API is fixed
Add a reply
Sign up and join the conversation on Discord