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.
Initially, I registered one application for Web API in my B2C tenant and added Application ID URI:
Now, I defined Web API roles by adding below JSON in appRoles
setting of Manifest file:
"appRoles": [
{
"allowedMemberTypes": ["Application"],
"displayName": "Read",
"id": "xxxxxxxx",
"isEnabled": true,
"description": "Readers have the ability to read tasks.",
"value": "app.read"
},
{
"allowedMemberTypes": ["Application"],
"displayName": "Write",
"id": "xxxxxxxxx",
"isEnabled": true,
"description": "Writers have the ability to create tasks.",
"value": "app.write"
}],
I registered one Azure AD B2C application named test1
from Portal and granted access to above Web API roles like this:
When I make call with application created via Portal, I got token successfully:
POST https://srib2caadtenant.b2clogin.com/srib2caadtenant.onmicrosoft.com/B2C_1A_SIGNUP_SIGNIN/oauth2/v2.0/token
client_id:appID
client_secret:secret
grant_type:client_credentials
scope: https://srib2caadtenant.onmicrosoft.com/api/.default
Now I tried to achieve the same by creating application from Graph API with below call:
POST https://graph.microsoft.com/v1.0/applications
Authorization: Bearer {token from graph API application}
Content-Type: application/json
{
"displayName": "test2"
}
I ran below call to create client secret for above application:
POST https://graph.microsoft.com/v1.0/applications/appObjectID/addPassword
Authorization: Bearer {token from Graph API}
Content-Type: application/json
{
"passwordCredential": {
"displayName" : "clientSecret"
}
}
When I make call with application created via API, I got same error saying invalid_grant
like this:
POST https://srib2caadtenant.b2clogin.com/srib2caadtenant.onmicrosoft.com/B2C_1A_SIGNUP_SIGNIN/oauth2/v2.0/token
client_id:appID
client_secret:secret
grant_type:client_credentials
scope: https://srib2caadtenant.onmicrosoft.com/api/.default
When you create an application from Graph API, API permissions won't be added by default:
To add Web API roles in the application via API call, you can make call to below PATCH request:
PATCH https://graph.microsoft.com/v1.0/applications/appObjID/
Authorization: Bearer {token from Graph API}
Content-Type: application/json
{
"requiredResourceAccess": [
{
"resourceAppId": "web API appID",
"resourceAccess": [
{
"id": "xxxxxxx",
"type": "Role"
},
{
"id": "xxxxxxx",
"type": "Role"
}
]
}
]
}
Make sure to grant admin consent to above API permissions:
Note that, it will take up to 5 minutes to reflect changes after granting admin consent to API permissions.
When I make call again after few minutes with application created via API, I got token successfully like below:
POST https://srib2caadtenant.b2clogin.com/srib2caadtenant.onmicrosoft.com/B2C_1A_SIGNUP_SIGNIN/oauth2/v2.0/token
client_id:appID
client_secret:secret
grant_type:client_credentials
scope: https://srib2caadtenant.onmicrosoft.com/api/.default
To confirm that, I decoded the above token in jwt.ms website and got below scp
claim with Web API permissions:
Reference: Set up OAuth 2.0 client credentials flow - Azure AD B2C | Microsoft