I am setting up applications using the Client Credentials flow in Azure B2C. When I create an application through the Azure Portal, I am able to assign it a secret and API permissions, then call the token endpoint to retrieve an access token. However, when I create an app via the Graph API and assign the same permissions and secret, it returns the following error:
{
"error": "invalid_grant",
"error_description": "AADB2C90085: The service has encountered an internal error. Please reauthenticate and try again.\r\nCorrelation ID: a73a0250-9ee7-4cde-9754-715460bc5fe6\r\nTimestamp: 2023-07-18 20:10:01Z\r\n"
}
Both applications have identical manifests besides GUIDs and names being different. I also checked the service principals via the Graph API and they both look the same besides GUIDs and names. Admin consent has been granted to the permissions of both applications through clicking the "Grant admin consent for {tenant}" button in the API permissions pane of the portal. I have found earlier stackoverflow posts stating that applications created by the Graph API are not eligible for B2C integration, but that does not seem to be the case anymore based on the documentation here: https://learn.microsoft.com/en-us/azure/active-directory-b2c/microsoft-graph-operations#applications
Below are the steps I took for creating the application via the Graph API:
POST /v1.0/applications HTTP/1.1
Host: graph.microsoft.com
Authorization: Bearer {token from graph API application}
Content-Type: application/json
Content-Length: 29
{
"displayName": "test2"
}
This creates an application registration under the All Applications tab in the App Registrations pane of the Azure portal. Here is the manifest for that created app:
{
"id": "{id}",
"acceptMappedClaims": null,
"accessTokenAcceptedVersion": 2,
"addIns": [],
"allowPublicClient": null,
"appId": "{appId}",
"appRoles": [],
"oauth2AllowUrlPathMatching": false,
"createdDateTime": "2023-07-18T19:35:48Z",
"description": null,
"certification": null,
"disabledByMicrosoftStatus": null,
"groupMembershipClaims": null,
"identifierUris": [],
"informationalUrls": {
"termsOfService": null,
"support": null,
"privacy": null,
"marketing": null
},
"keyCredentials": [],
"knownClientApplications": [],
"logoUrl": null,
"logoutUrl": null,
"name": "test2",
"notes": null,
"oauth2AllowIdTokenImplicitFlow": false,
"oauth2AllowImplicitFlow": false,
"oauth2Permissions": [],
"oauth2RequirePostResponse": false,
"optionalClaims": null,
"orgRestrictions": [],
"parentalControlSettings": {
"countriesBlockedForMinors": [],
"legalAgeGroupRule": "Allow"
},
"passwordCredentials": [
{
"customKeyIdentifier": null,
"endDate": "2024-01-14T21:05:11.199Z",
"keyId": "{keyId}",
"startDate": "2023-07-18T20:05:11.199Z",
"value": null,
"createdOn": "2023-07-18T20:05:14.7198454Z",
"hint": "rLR",
"displayName": "clientSecret"
}
],
"preAuthorizedApplications": [],
"publisherDomain": "{tenent}",
"replyUrlsWithType": [],
"requiredResourceAccess": [
{
"resourceAppId": "{API app ID}",
"resourceAccess": [
// records in here for api permissions that match those of the application I create through the portal
]
},
{
"resourceAppId": "00000003-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "7427e0e9-2fba-42fe-b0c0-848c9e6a8182",
"type": "Scope"
},
{
"id": "37f7f235-527c-4136-accd-4a02d197296e",
"type": "Scope"
}
]
}
],
"samlMetadataUrl": null,
"signInUrl": null,
"signInAudience": "AzureADandPersonalMicrosoftAccount",
"tags": [],
"tokenEncryptionKeyId": null
}
I have also created the secret through the API via the below endpoint, and in other testing also added that secret through the portal after the app has been registered with the call above. Neither path makes a difference.
POST /v1.0/applications/0a9334c7-6312-4912-86d4-31d8458cf9f6/addPassword HTTP/1.1
Host: graph.microsoft.com
Authorization: Bearer {token from Graph API}
Content-Type: application/json
Content-Length: 80
{
"passwordCredential": {
"displayName" : "clientSecret"
}
}
When I make the call to get the token, the application created in the portal works, and the application created by the API returns the invalid_grant error. Here is how I am making that call:
POST /{tenant}.onmicrosoft.com/{custom policy}/oauth2/v2.0/token?client_id={client_id}&grant_type=client_credentials&scope=https://{tenant}.onmicrosoft.com/{application id URI slug}/.default&client_secret={client_secret} HTTP/1.1
Host: devcloud9fx.b2clogin.com
When calling this in postman, the only things I change are the client_id and the secret.