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.
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):
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.
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)
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),
}
}
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:
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
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
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:
[] <- 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