> ## Content Index
> Fetch the complete content index at: https://www.azurebrasil.cloud/llms.txt
> Use this file to discover other available public pages before exploring further.

# Como utilizar o Azure Resource Graph REST API
- URL: https://www.azurebrasil.cloud/blog/como-utilizar-o-azure-resource-graph-api/
- Published: 2023-06-16T18:46:13.000Z
- Updated: 2023-06-16T18:47:53.000Z
- Author: Talles Valiatti

Neste post, iremos consumir a [API do Azure Resource Graph](https://learn.microsoft.com/en-us/rest/api/azure-resourcegraph/?ref=azurebrasil.cloud) por meio de uma aplicação [Blazor](https://learn.microsoft.com/en-my/aspnet/core/blazor/?WT.mc%5Fid=dotnet-35129-website&view=aspnetcore-7.0&ref=azurebrasil.cloud). A chamada a API irá retornar as [regiões do Azure](https://azure.microsoft.com/en-us/explore/global-infrastructure/geographies/?ref=azurebrasil.cloud#overview) em que a [*subscription*](https://techcommunity.microsoft.com/t5/azure/understanding-azure-account-subscription-and-directory/td-p/34800?ref=azurebrasil.cloud#:~:text=An%20Azure%20subscription%20is%20a,the%20subscription%20it%20belongs%20to.) selecionada possui recursos de nuvem, junto com suas respectivas quantidades. Para que isso seja possível, iremos implementar a autenticação do aplicativo web com a ajuda do Azure AD. Nosso web app possuirá duas partes: *server* e *client.*

Sobre o Azure AD, eu já escrevi [alguns artigos](https://tallesvaliatti.com/como-proteger-suas-aplica%C3%A7%C3%B5es-com-o-microsoft-identity-platform-parte-1-6375adb44995?ref=azurebrasil.cloud) sobre essa ferramenta. Recomendo a leitura! 

Vamos criar o [registro de aplicativo para a parte do *server*](https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-protected-web-api-overview?ref=azurebrasil.cloud) no nosso Azure AD.

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/06/Group-1024.png)

 Para que o *server* faça a chamada ao Azure Resource Graph em nome do usuário logado (no *client*), precisaremos criar um [*secret*](https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app?ref=azurebrasil.cloud#add-a-client-secret)para esse registro de aplicativo.

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/06/Group-1025-1.png)

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/06/Screen-Shot-2023-05-31-at-09.21.png)

Como mencionado anteriormente, o *server* irá fazer a chamada a uma API protegida do Azure, por isso será necessário adicionar o *[user\_impersonation scope](https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-configure-app-access-web-apis?ref=azurebrasil.cloud#add-permissions-to-access-your-web-api)* por meio do menu "*API permissions*".

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/06/Group-1026.png)

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/06/Group-1027.png)

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/06/Group-1028.png)

Além disso, vamos [consentir](https://learn.microsoft.com/en-us/azure/active-directory/develop/application-consent-experience?ref=azurebrasil.cloud) (*[admin consent](https://learn.microsoft.com/en-us/azure/active-directory/manage-apps/grant-admin-consent?pivots=portal&ref=azurebrasil.cloud)*) essa permissão para os todos os usuários da nossa *tenant.*

Para finalizar, devemos [criar um *scope*](https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-configure-app-expose-web-apis?ref=azurebrasil.cloud)para que outros registros de aplicativos possam pedir [autorização para consumir](https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-configure-app-access-web-apis?ref=azurebrasil.cloud) a nossa aplicação "*app-server-api*"*.*

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/06/Group-1030.png)

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/06/Group-1031-1.png)

Vamos agora criar o registro de aplicativo para o *client.* Como iremos utilizar o blazor como *client,* devemos prover uma *[redirect URI](https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app?ref=azurebrasil.cloud#add-a-redirect-uri).* 

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/06/Group-1032.png)

Com o registro criado, podemos ir ao menu "*API permissions*" e adicionar a permissão (*scope*) criada na etapa anterior, a "*Access.Server".*

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/06/Group-1034--1-.png)

Dessa forma, quando o usuário acessar o *client* pela primeira vez, a página de *login* do Azure AD irá perguntar se o usuário permite que o *client* acesse o *Access.Server scope* em seu nome. 

Com os dados prontos, já podemos criar o projeto blazor. Vale a pena dar uma lida na documentação oficial para entender melhor sobre os parâmetros do comando abaixo.

```
dotnet new blazorwasm -au SingleOrg \
--api-client-id "{SERVER API APP CLIENT ID}" \
--app-id-uri "{SERVER API APP ID URI GUID}" \
--client-id "{CLIENT APP CLIENT ID}" \ 
--default-scope "{DEFAULT SCOPE}" \
--domain "{TENANT DOMAIN}" \ 
-ho -o {PROJECT NAME} \
--tenant-id "{TENANT ID}"
```

Toda a parte de configuração da autenticação foi finalizada, vamos preparar agora a chamada à API do Azure Resource Graph. Iremos inicialmente criar os modelos retornados pela API dentro do projeto *Shared*.

```
using System.Text.Json.Serialization;

namespace ResourceGraphApp.Shared;

public class Content
{
    [JsonPropertyName("location")]
    public string Location { get; set; } = default!;

    [JsonPropertyName("qtd")]
    public int Qtd { get; set; }
}

public class CloudRegion
{
    [JsonPropertyName("totalRecords")]
    public int TotalRecords { get; set; }

    [JsonPropertyName("count")]
    public int Count { get; set; }

    [JsonPropertyName("data")]
    public List<Content> Data { get; set; } = default!;

    [JsonPropertyName("facets ")]
    public List<object> Facets { get; set; } = default!;

    [JsonPropertyName("resultTruncated")]
    public string ResultTruncated { get; set; } = default!;
}
```

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/06/Screen-Shot-2023-06-12-at-17.15.35.png)

No projeto *server*, vamos criar a controladora *CloudResourcesController* com o seguinte conteúdo:

```
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.Resource;
using ResourceGraphApp.Shared;

namespace ResourceGraphApp.Server.Controllers;

[Authorize]
[ApiController]
[Route("[controller]")]
[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]
public class CloudResourcesController : ControllerBase
{
    private readonly IDownstreamWebApi _downstreamWebApi;
    
    public CloudResourcesController(IDownstreamWebApi downstreamWebApi)
    {
        _downstreamWebApi = downstreamWebApi;
    }

    [HttpGet]
    [AuthorizeForScopes(ScopeKeySection = "ResourceGraphAPI:Scopes")]
    public async Task<IActionResult> GetAsync()
    {
       
        var value = await _downstreamWebApi.PostForUserAsync<CloudRegion, object>(
            "ResourceGraphAPI",
            "/providers/Microsoft.ResourceGraph/resources?api-version=2021-03-01",
            new
            {
                subscriptions = new List<string> { "3a1cf685-3342-4c07-a8f7-07bb6008e146" },
                query = "Resources | summarize qtd=count() by location"
            });

        return Ok(value);
    }
}
```

Alguns pontos sobre essa controladora:

- Possui o atributo [Authorize](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/simple?view=aspnetcore-7.0&ref=azurebrasil.cloud) que apenas permite requisições autenticadas à nossa controladora;
- Possui o atributo *[RequiredScope](https://learn.microsoft.com/en-us/dotnet/api/microsoft.identity.web.resource.requiredscopeattribute?view=msal-model-dotnet-latest&ref=azurebrasil.cloud)* que permite que apenas requisições com o *Access.Server scope* ([via *appsettings.json*](https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-protected-web-api-verification-scope-app-roles?tabs=aspnetcore&ref=azurebrasil.cloud#verify-scopes-in-apis-called-on-behalf-of-users)) consentido pelo usuário;
- Utiliza a interface *[IDownstreamWebApi](https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-web-api-call-api-call-api?tabs=aspnetcore&ref=azurebrasil.cloud#option-2-call-a-downstream-web-api-with-the-helper-class)* para fazer um *POST* a API do azure Resource Graph em nome do usuário logado. Toda a parte de obtenção de um novo token de acesso é feita de forma automática! Veja a [documentação oficial](https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-web-api-call-api-acquire-token?tabs=aspnetcore&ref=azurebrasil.cloud);
- Fazemos a chamada a API, passando como parâmetro, a *query* em [KUSTO](https://learn.microsoft.com/pt-br/azure/data-explorer/kusto/query/?ref=azurebrasil.cloud): *"Resources | summarize qtd=count() by location".*

Para configurar as requisições as *downstream* APIs em nome do usuário, precisaremos incluir no *program.cs* as extensões mostradas abaixo*:*

- *EnableTokenAcquisitionToCallDownstreamApi;*
- *AddDownstreamWebApi;*
- AddInMemoryTokenCaches:

```
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamWebApi("ResourceGraphAPI", builder.Configuration.GetSection("ResourceGraphAPI"))
    .AddInMemoryTokenCaches();
```

Para finalizar a parte do *server*, vamos apenas incluir uma nova seção de configuração ao *appsettings.json*, a *ResourceGraphAPI.* Basicamente estamos dizendo a URL da APIe qual os *scopes* que serão requeridos para os *access tokens* que serão gerados dentro da nossa API*.*

```
{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "<YOUR-DOMAIN>",
    "TenantId": "<YOUR-TENANT-ID>",
    "ClientId": "<YOUR-CLIENT-ID>",
    "Scopes": "Access.Server",
    "CallbackPath": "/signin-oidc",
    "ClientSecret": ""<YOUR-CLIENT-SECRET>""
  },
  "ResourceGraphAPI": {
    "BaseUrl": "https://management.azure.com/",
    "Scopes": "https://management.azure.com/user_impersonation"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

```

Com isso fechamos o desenvolvimento do *server.*

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/06/Screen-Shot-2023-06-12-at-17.19.56.png)

Já na parte do *client,* vamos inicialmente criar a página *CloudRegions.razor* com o seguinte conteúdo:

```
@page "/cloudregions"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using ResourceGraphApp.Shared
@attribute [Authorize]
@inject HttpClient Http

<PageTitle>Cloud Regions</PageTitle>

<h1>Cloud Regions</h1>

<p>This component demonstrates fetching data from the server.</p>

@if (_cloudRegion == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Region</th>
                <th>Qtd of Resources</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var cloudRegion in _cloudRegion.Data)
            {
                <tr>
                    <td>@cloudRegion.Location</td>
                    <td>@cloudRegion.Qtd</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private CloudRegion? _cloudRegion;

    protected override async Task OnInitializedAsync()
    {
        _cloudRegion = await Http.GetFromJsonAsync<CloudRegion>("CloudResources");
    }
}

```

Alguns detalhes sobre essa página:

- Ela é protegida pelo atributo *Authorize*;
- Ela é exibida quando chamamos a [rota](https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/routing?view=aspnetcore-7.0&ref=azurebrasil.cloud) "*/cloudregions*";
- Faz uma requisição à controladora criada anteriormente;
- Exibe os dados retornados pela chamada HTTP em uma tabela;

Vamos modificar o *[MainLayout.razor](https://learn.microsoft.com/pt-br/aspnet/core/blazor/components/layouts?view=aspnetcore-7.0&ref=azurebrasil.cloud)* para que essa nova rota fique disponível no menu do web app.

```
<div class="top-row ps-3 navbar navbar-dark">
    <div class="container-fluid">
        <a class="navbar-brand" href="">ResourceGraphApp</a>
        <button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
            <span class="navbar-toggler-icon"></span>
        </button>
    </div>
</div>

<div class="@NavMenuCssClass nav-scrollable" @onclick="ToggleNavMenu">
    <nav class="flex-column">
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="cloudregions">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Cloud Regions
            </NavLink>
        </div>
    </nav>
</div>

@code {
    private bool collapseNavMenu = true;

    private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}

```

Feito essas modificações, podemos executar o projeto *server* com o comando *[donet run](https://learn.microsoft.com/pt-br/dotnet/core/tools/dotnet-run?ref=azurebrasil.cloud)*:

```
dotnet run
```

Com o *browser* aberto, vamos clicar em "*Log in"* e fazer o login no registro de aplicativo *(client)* criado anteriormente.

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/06/Screen-Shot-2023-06-01-at-10.22.png)

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/06/Screen-Shot-2023-06-01-at-10.22--1-.png)

Caso você possua o [MFA](https://tallesvaliatti.com/habilitando-o-azure-ad-mfa-d65dffef34e3?ref=azurebrasil.cloud) configurado para sua *tenant*, ou para usuários específicos, é aqui que você utilizará essa ferramenta*.* 

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/06/Screen-Shot-2023-06-01-at-10.23.png)

Como estamos acessando o web app pela primeira vez, vamos consentir que a aplicação *client* acesse a aplicação *server* em nosso nome.

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/06/Group-1036.png)

Navegando à rota "*/cloudregions",* temos o seguinte resultado!

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/06/Group-1035.png)

Você já pode baixar o projeto por esse [](https://github.com/TallesValiatti/AnomalyDetectorUnivariateApi?ref=azurebrasil.cloud)[](https://github.com/TallesValiatti/AzureResourceGraph?ref=azurebrasil.cloud)[link](https://github.com/TallesValiatti/AzureResourceGraph/tree/main?ref=azurebrasil.cloud), e não esquece de me seguir no [LinkedIn](https://www.linkedin.com/in/tallesvaliatti/?ref=azurebrasil.cloud)!

Até a próxima, abraços!