Connecting Azure AD B2C to Kinde via OIDC
A client asked me about Kinde and whether it could be federated with B2C.
I couldn’t find any relevant documentation, hence this post.
You can read more about Kinde here.
This is not intended to be a deep dive into Kinde, but it follows the usual federation configuration flow.
Create some users, decide how you want to authenticate (email/username, etc.), create an application, and then get the clientID and secret key.
Note the allowed callback URL in the Kinde application is:
https://tenant.b2clogin.com/tenant.onmicrosoft.com/oauth2/authresp
A Kinde ID token is of the form:
{
"at_hash": "VZ6cU0Ay0RKB5EosbWuTCQ",
"aud": [
"https://<your_subdomain>.kinde.com"
],
"auth_time": 1692361334,
"azp": "dee7f3c57b3c47e8b96edde2c7ecab7d",
"email": "jane.smith@gmail.com",
"exp": 1693288799,
"family_name": "Smith",
"given_name": "Jane",
"iat": 1693285199,
"iss": "https://<your_subdomain>.kinde.com",
"jti": "fcxf6xd3-8c75-402x-a4cb-1659fb8c555d",
"name": "Jane Smith",
"org_codes": [
"org_xxxxxxxxxxx"
],
"picture": "https://lh3.googleusercontent.com/a/google-url",
"provided_id": "<user_id_in_your_system>",
"sub": "kp_xxxxxxxxxxxxxxxxxxxx",
"updated_at": 1692009540
}
Note that there is no “oid,” so use “sub,” and the “aud” is inside an array. B2C won’t accept an “aud” in this form.
Also, note that the “sub” is not numeric but starts with “kp_”.
In B2C, there are two ways to federate, viz., user flows and custom policies.
User flow
Turn off email verification on B2C to help with testing if you want.
Kinde requires email verification in this case.
In B2C, create a generic OIDC provider and add this to your user flow.
Use the clientID and secret from the application.
Note the metadata address is:
https://subdomain.kinde.com/.well-known/openid-configuration
Run the user flow and select Kinde as your external IDP.
I have an email/password for this case, so enter those.
You are asked for user details:
The JWT you get back looks like this:
{
"exp": 1727140116,
"nbf": 1727136516,
"ver": "1.0",
"iss": "https://tenant.b2clogin.com/65...16/v2.0/",
"sub": "26a...95f",
"aud": "7bd...760",
"nonce": "defaultNonce",
"iat": 1727136516,
"auth_time": 1727136516,
"idp": "https://subdomain.kinde.com",
"name": "NZ Pc",
"given_name": "NZ",
"family_name": "Pc",
"tfp": "B2C_1_Kinde"
}
The identity array for the user in B2C looks like this:
federated
https://subdomain.kinde.com kp_d7f…5d6
userPrincipalName
tenant.onmicrosoft.com cpim_4d6…c7a@tenant.onmicrosoft.com
So it all works nicely 😃
Custom policy
As usual, the policy is in a gist.
For custom policies, you have to store the secret in a policy key store.
The claims mapping is:
<OutputClaim ClaimTypeReferenceId="issuerUserId" PartnerClaimType="sub"/>
<OutputClaim ClaimTypeReferenceId="tenantId" PartnerClaimType="azp"/>
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
<OutputClaim ClaimTypeReferenceId="surName" PartnerClaimType="family_name" />
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
<OutputClaim ClaimTypeReferenceId="authenticationSource"
DefaultValue="socialIdpAuthentication" AlwaysUseDefaultValue="true" />
<OutputClaim ClaimTypeReferenceId="identityProvider" PartnerClaimType="iss" />
This policy is connected to a different Kinde application that logs in via a username. I also used a different user.
Use the clientID and secret from the application.
When we run the policy and select Kinde as the external IDP, we get:
After signup up, we get the following JWT:
{
"exp": 1726964778,
"nbf": 1726961178,
"ver": "1.0",
"iss": "https://tenant.b2clogin.com/65...16/v2.0/",
"sub": "kp_6ae...320",
"aud": "7bd...760",
"acr": "b2c_1a_signup_signin_kinde",
"nonce": "defaultNonce",
"iat": 1726961178,
"auth_time": 1726961178,
"tid": "23f...ba6",
"given_name": "B2C",
"family_name": "Test",
"name": "B2C Test",
"idp": "https://subdomain.kinde.com"
}
By using the custom policy debugging, we can see the JWT that Kinde passes to B2C.
{
"at_hash": "7AQf0XnPt6OaZ9oT878PWg",
"aud": [
"23f...ba6"
],
"auth_time": 1726961175,
"azp": "23f...ba6",
"exp": 1726964778,
"family_name": "Test",
"given_name": "B2C",
"iat": 1726961178,
"iss": "https://subdomain.kinde.com",
"jti": "76f...486",
"name": "B2C Test",
"nonce": "sVUJ9WJflKE/jJIENHCzUg==",
"org_codes": [
"org_033...3bb"
],
"preferred_username": "b2ctest",
"rat": 1726961175,
"sub": "kp_6ae...320",
"updated_at": 1726960150
}
Note that the custom policy requires a tenantId.
Kinde doesn’t supply one (this is different to the issuer) so I’ve used “azp”.
“Authorized party — an azp
claim specifies the client ID of the party to which the ID Token was originally issued.”
The identity array for the user in B2C looks like this:
federated
https://subdomain.kinde.com
kp_6ae...320
userPrincipalName
tenant.onmicrosoft.com
cpim_cba...a3e@tenant.onmicrosoft.com
This all works nicely 😃
All good!