Search code examples
azureazure-active-directoryazure-ad-b2c

Azure B2C invalid_grant error when requesting token with app registration created with Graph API


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.


Solution

  • Initially, I registered one application for Web API in my B2C tenant and added Application ID URI:

    enter image description here

    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"
    }],
    

    enter image description here

    I registered one Azure AD B2C application named test1 from Portal and granted access to above Web API roles like this:

    enter image description here

    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
    

    enter image description here

    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"
    }
    

    enter image description here

    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"
        }
    }
    

    enter image description here

    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
    

    enter image description here

    When you create an application from Graph API, API permissions won't be added by default:

    enter image description here

    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"
                    }
                ]
            }
        ]
    }
    

    enter image description here

    Make sure to grant admin consent to above API permissions:

    enter image description here

    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
    

    enter image description here

    To confirm that, I decoded the above token in jwt.ms website and got below scp claim with Web API permissions:

    enter image description here

    Reference: Set up OAuth 2.0 client credentials flow - Azure AD B2C | Microsoft