Search code examples
c#azure-active-directoryazure-functionsazure-web-app-serviceazure-management-api

Azure Management API: HTTP Post StatusCode: 403, ReasonPhrase: 'Forbidden'


I am receiving StatusCode: 403, ReasonPhrase: 'Forbidden' for an HTTP Post:

URL: $"https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{appName}/functions/{functionName}/listkeys?api-version=2022-03-01";

StatusCode: 403, ReasonPhrase: 'Forbidden'

Client:

The following is my client code:

let tenantId = "<some_tenant_id>"
let clientId = "<some_client_id>"
let secret   = "<some_secret>"
let scope    = "<some_scope>"

let token = BearerToken.Create(tenantId, clientId, secret, scope).Result

let tokenRequestBody = Dictionary<string, string>() 
tokenRequestBody.Add("grant_type"   , "client_credentials")
tokenRequestBody.Add("client_id"    , clientId)
tokenRequestBody.Add("client_secret", secret)
tokenRequestBody.Add("scope"        , scope)

let content = new FormUrlEncodedContent(tokenRequestBody);

let httpKeysClient = new HttpClient();
httpKeysClient.DefaultRequestHeaders.Authorization <- new AuthenticationHeaderValue("Bearer", token);
httpKeysClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

let subscriptionId    = "<some_scubscription_id>"
let resourceGroupName = "<some_resource_group_name>"
let appName           = "<some_function_app_name>"
let functionName      = "<some_function_name>"

let apiKeyUrl = $"https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{appName}/functions/{functionName}/listkeys?api-version=2022-03-01";

let response = httpKeysClient.PostAsync(apiKeyUrl, content).Result;

response.IsSuccessStatusCode |> should equal true // ** StatusCode: 403, ReasonPhrase: 'Forbidden' **

Appendix:

The code for creating an authorization token works:

public static class BearerToken
{
    public async static Task<string> Create(string tenantId, string clientId, string clientSecret, string scope)
    {
        var tokenRequestBody = new Dictionary<string, string> {

            { "grant_type"   , "client_credentials" },
            { "client_id"    , clientId },
            { "client_secret", clientSecret },
            { "scope"        , scope }
            };

        var url      = $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token";
        var client   = new HttpClient() { BaseAddress = new Uri(url) };
        var content  = new FormUrlEncodedContent(tokenRequestBody);
        var response = await client.PostAsync("", content);

        if (response.IsSuccessStatusCode)
        {
            var tokenResponse = await response.Content.ReadAsStringAsync();
            var valueFor      = JsonConvert.DeserializeObject<JsonSupport.AccessToken.Root>(tokenResponse);

            return valueFor.access_token;
        }

        throw new Exception(response.ReasonPhrase);
    }
}

Solution

  • I tried to reproduce the same in my environment via Postman and got below results

    I have one service principal having Reader role under my subscription like below:

    enter image description here

    Now I generated access token using client credentials flow with below parameters:

    POST https://login.microsoftonline.com/<tenantID>/oauth2/v2.0/token
    
    grant_type:client_credentials
    client_id: <appID>
    client_secret: <secret_value>
    scope: https://management.azure.com/.default
    

    Response:

    enter image description here

    When I used the above token to list keys, I got 403 Forbidden error like below:

    POST https://management.azure.com/subscriptions/<subID>/resourceGroups/Sri/providers/Microsoft.Web/sites/<name>/functions/<functionname>/listkeys?api-version=2022-03-01
    Authorization: Bearer <token>
    

    Response:

    enter image description here

    To resolve the error, I assigned Contributor role to that service principal like below:

    Go to Azure Portal -> Subscriptions -> Your Subscription -> Access control (IAM) -> Add role assignment

    enter image description here

    After assigning that role, I generated access token again and got the results successfully when I used that in below query:

    POST https://management.azure.com/subscriptions/<subID>/resourceGroups/Sri/providers/Microsoft.Web/sites/<name>/functions/<functionname>/listkeys?api-version=2022-03-01
    Authorization: Bearer <token>
    

    Response:

    enter image description here

    In your case, make sure to assign Contributor role to your service principal that resolves the issue.