Connecting Azure AD B2C to Kinde via OIDC

Rory Braybrook
The new control plane
4 min readSep 24, 2024
Image showing “Kinde”

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.

Image showing config: scope is openid, response type is code, response mode is form_post, user Id is sub, display name is name, given name is given_name, surname is family_name, email is email
See above

Note the metadata address is:

https://subdomain.kinde.com/.well-known/openid-configuration

Run the user flow and select Kinde as your external IDP.

Image showing Kinde email login

I have an email/password for this case, so enter those.

You are asked for user details:

Image asking for user details — email, display name, given name, surname

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:

Image showing Kinde username login

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!

--

--

The new control plane
The new control plane

Published in The new control plane

“Identity is the new control plane”. Articles around Microsoft Identity, Auth0 and identityserver. Click the “Archive” link at the bottom for more posts.

Rory Braybrook
Rory Braybrook

Written by Rory Braybrook

NZ Microsoft Identity dude and MVP. Azure AD/B2C/ADFS/Auth0/identityserver. StackOverflow: https://bit.ly/2XU4yvJ Presentations: http://bit.ly/334ZPt5

No responses yet