Proteger Web API com Azure AD no modelo RBAC

This post has been republished via RSS; it originally appeared at: Microsoft Tech Community - Latest Blogs - .

Granularizando nossas permissões com Role Based Access Control

 

A plataforma de identidade da Microsoft pode ser utilizada em dois tipos de fluxo: o fluxo de usuário e o fluxo de aplicação. No primeiro caso, temos um usuário logado para fornecer suas credenciais e aprovar consentimentos e no segundo caso temos aplicações falando diretamente umas com as outras onde o administrador do diretório quem aprova os consentimentos.

Para controlar as permissões desses fluxos acima, usamos escopos e roles para definir uma função na qual o usuário/aplicação poderá acessar, que por sua vez são representados por chaves de texto que são associadas aos tokens de acesso. Com essas chaves podemos controlar o processo de autorização das aplicações protegidas em níveis extremamente granulares chegando no nível de instruções específicas do código.

O Azure AD precisa mapear as aplicações em seu serviço, esse modelo deve representar suas aplicações com o máximo de fidelidade possível, assim teremos melhor governança das aplicações e visibilidade das relações entre elas.

 

wdossantos_0-1659379844811.jpeg

 

 

Imagem com modelos básicos de representação de duas aplicações protegidas pelo Azure Ad.

No nosso cenário vamos construir um fluxo de usuário onde nossa aplicação servidora expõem um conjunto de roles e um escopo, as roles serão atribuídas em usuários e grupos e os escopos serao usados para criar uma camada a mais de segurança garantindo que apenas as aplicações desejadas acessem um recurso de backend desejado, vamos definir dois conceitos importantes para prosseguirmos;

 

App roles

 

Funções de aplicativos são funções personalizadas para atribuir permissões a usuários ou aplicativos. O aplicativo define e publica as funções do aplicativo e as interpreta como permissões durante a autorização.

 

Escopos

 

Escopos definidos por API, cria escopos personalizados para restringir o acesso aos dados e funcionalidades protegidas pela API. Um aplicativo que requer acesso a partes desta API pode solicitar que um usuário ou administrador consinta com uma ou mais delas.

 

Vamos começar registrando as aplicações no Azure AD

 

  1. No menu Registro de Aplicações clique em Novo Registro
  2. Cria uma aplicação para representar seu backend
  3. Repita esse passo e crie as aplicações para representar seus clientes, nesse caso é importante definir a plataforma, temos duas aplicações clientes uma aplicação de plataforma web, que precisa de uma url de retorno https://localhost:44343/signin-oidc e uma aplicação de plataforma Mobile e desktop com url de retorno http://localhost
  4. Anote o Id do Aplicativo (client_id) de cada aplicação
  5. A aplicação Cliente precisa de uma (client_secret) Segredo , faça isso no menu Certificados e Segredos, Novo segredo do Cliente na aba segredos do cliente
  6. Agora vamos na aplicação servidora e vamos adicionar as Funções (Roles) no menu Funções do Aplicativos, essas funções são para usuários
  7. Além das funções vamos no menu Expor uma API e vamos expor um ponto de extremidade de API e um escopo que representa a permissão de acesso nessa aplicação
  8. Agora na aplicação cliente vamos em Permissões de APIs e clicamos em adicionar uma Permissão, escolhemos Minhas APIs em permissões delegadas escolhemos nossas permissões para a aplicação web, e em permissões de aplicação para a aplicação console.
  9. Damos consentimento de Administrador nessa relação
  10. Para finalizar a configuração vamos agora acessar o Aplicativo Empresarial Relacionado a essa App, elas tem o mesmo clientId e o mesmo nome, depois de entrar nela vamos até o menu Usuários e Grupos e associamos as funções dessa App à um usuário ou grupo.

Abaixo temos algumas telas do Azure AD, as configurações das roles, escopos e permissões.

wdossantos_1-1659379844874.png

 

Tela do Azure Ad onde associamos roles secretdata.access e secretdata.read os usuários ou grupos

wdossantos_2-1659379844878.png

 

Tela de Permissões onde podemos ver a aplicação cliente com acesso a aplicação servidora com consentimento do Administrador do diretório., isso garante uma camada extra de segurança.

wdossantos_3-1659379844889.png

 

Tela de Permissões onde podemos ver a aplicação cliente com acesso a aplicação servidora com consentimento do Administrador do diretório., isso garante uma camada extra de segurança.

wdossantos_4-1659379844917.png

 

 Adicionando permissões para a aplicação console, também faça isso para a aplicação web

Caso o administrador remova esse consentimento. vamos receber uma resposta de acesso negado, bloqueando instantaneamente aquele cliente de acessar o backend. Para isso funcionar conforme o exemplo é importante que o escopo inicial passado na configuração do startup seja o escopo de acesso da aplicação cliente para a aplicação servidora.

wdossantos_5-1659379844812.png

 

Imagem do Azure AD bloqueando o acesso do usuário por motivo da revogação do consentimento do Administrador do Diretório.

 

MSAL

 

A MSAL (Biblioteca de Autenticação da Microsoft) é um SDK que pode ser utilizado para abstrair o processo OAuth2.0 facilitando a implementação de fluxos complexos de autenticação.

Dependendo da plataforma e do estilo do projeto o pacote muda, no caso desse exemplo estamos usando aplicações .NET Core e por isso vamos instalar alguns pacotes via nuget para ter acesso aos recursos do MSAL. Para saber mais sobre o MSAL e quais plataformas são suportadas clique aqui

o principal pacote a ser instalado é o Microsoft.Identity.Web, mas na aplicação que faz o processo de login (Signin) precisamos também do pacote Microsoft.Identity.Web.UI

Install-Package Microsoft.Identity.Web  
Install-Package Microsoft.Identity.Web.UI

 

No Backend

 

Na classe Startup método ConfigureServices

public void ConfigureServices(IServiceCollection services)  
{
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)  
   .AddMicrosoftIdentityWebApi(Configuration, "AzureAd");services.AddControllers();  
}

No método Configure

app.UseAuthentication();  
app.UseAuthorization();

Na controller verificar a role antes de entrar nas implementações

[Authorize(Roles = "secretdata.access")]  
public class WeatherForecastController : ControllerBase  
{  
  ...  
}

Ou durante a implementação

User.IsInRole

Vamos dar uma olhada na implementação completa.

using Microsoft.AspNetCore.Authentication.JwtBearer;  
using Microsoft.AspNetCore.Authorization;  
using Microsoft.AspNetCore.Mvc;namespace Sample_api_users.Controllers  
{
    [ApiController]  
    [Route("[controller]")]  
    [Authorize(Roles = "secretdata.access")]  
    public class SecretDataController : ControllerBase  
    {  
        [HttpGet]  
        public IActionResult Index()  
        {
            if (!this.User.IsInRole("secretdata.read"))  
                return Forbid();return Ok(new  
                {  
                    msg = "My Secret"  
                });  
            }  
    }  
}

No Appsettings.json

"AzureAd": {  
 "Instance": "https://login.microsoftonline.com/",  
 "Domain": "wilsonsantosnetgmail.onmicrosoft.com",  
 "ClientId": "47b1d030-f6ff-41a8-8933-c52235a2651b",  
 "TenantId": "779811d8-4753-4c34-baeb-6b53957d52e3",  
 "CallbackPath": "/signin-oidc"  
}

 

Cliente

 

Na classe Startup método ConfigureServices

public void ConfigureServices(IServiceCollection services)  
{
    var scope = Configuration["Roles:Scope"].Split(" ");  
    
    services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)  
         .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"))  
         .EnableTokenAcquisitionToCallDownstreamApi(scope)  
         .AddInMemoryTokenCaches();

    services.AddHttpClient();
  
    services.AddControllersWithViews()  
        .AddMicrosoftIdentityUI();  
}

No método Configure

app.UseAuthentication();  
app.UseAuthorization();

No Appsettings.json

"AzureAd": {  
 "Instance": "https://login.microsoftonline.com/",  
 "Domain": "wilsonsantosnetgmail.onmicrosoft.com",  
 "ClientId": "15aeb957-bc74-42e1-926f-90310dad28bb",  
 "TenantId": "779811d8-4753-4c34-baeb-6b53957d52e3",  
 "CallbackPath": "/signin-oidc",  
 "ClientSecret": "biE.Tj~6Ng_a1__8.u48S5bDfHgF0Iw-QC"  
},  
"Roles": {  
 "Scope": "api://47b1d030-f6ff-41a8-8933-c52235a2651b/access_api",  
}

Existem diversos cenários, variando a forma como registramos as aplicações no Azure AD em função dos tipos de clientes e da estratégia de segurança, mas o exemplo acima toca nos principais detalhes envolvidos nesse processo.

wdossantos_6-1659379844813.png

 

Ao subir a aplicação cliente temos essa tela

 

Aqui podemos ver as roles no token

 

{  
  "typ": "JWT",  
  "alg": "RS256",  
  "x5t": "2ZQpJ3UpbjAYXYGaXEJl8lV0TOI",  
  "kid": "2ZQpJ3UpbjAYXYGaXEJl8lV0TOI"  
}.{  
  "aud": "api://47b1d030-f6ff-41a8-8933-c52235a2651b",  
  "iss": "https://sts.windows.net/779811d8-4753-4c34-baeb-6b53957d52e3/",  
  "iat": 1658155374,  
  "nbf": 1658155374,  
  "exp": 1658160456,  
  "acr": "1",  
  "aio": "ATQAy/8TAAAAy9YQuDDcTFcPCYobl9G/UQL3cPwdZfJ2QdCx9lG5XaQAsSChH2gy3CDoswxiL9sw",  
  "amr": [  
    "pwd"  
  ],  
  "appid": "15aeb957-bc74-42e1-926f-90310dad28bb",  
  "appidacr": "1",  
  "family_name": "identity",  
  "given_name": "ms",  
  "ipaddr": "186.207.64.242",  
  "name": "msindentity",  
  "oid": "5e768b93-88b3-40b9-84ff-ae40c6c563a3",  
  "rh": "0.ATQA2BGYd1NHNEy662tTlX1S4zDQsUf_9qhBiTPFIjWiZRs0AG8.",  
  "roles": [  
    "secretdata.read",  
    "secretdata.access"  
  ],  
  "scp": "access_api",  
  "sub": "3ZojiIPz2UonsWM2Yw_Z9rSxFvzOXwcRjbkh5CK8--I",  
  "tid": "779811d8-4753-4c34-baeb-6b53957d52e3",  
  "unique_name": "mailto:msindentity@wilsonsantosnetgmail.onmicrosoft.com",  
  "upn": "mailto:msindentity@wilsonsantosnetgmail.onmicrosoft.com",  
  "uti": "E_cgNvhkX0mUaX_H2tU4AA",  
  "ver": "1.0"  
}.[Signature]

Podemos usar esse token para acessar a API do backend com o código abaixo. primeiro obtemos um token

var accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(new string[] { "api://47b1d030-f6ff-41a8-8933-c52235a2651b/access_api" });

Depois criamos um método que via Cliente http fazemos uma chamada no backend e passamos o token.

private async Task<JArray> BackAD(string accessToken, string API)  
{  
 var client = _clientFactory.CreateClient();  
 client.BaseAddress = new Uri(API);  
 client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);  
 client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));  
 var response = await client.GetAsync($"api/WeatherForecast");  
 response.EnsureSuccessStatusCode();  
 var responseContent = await response.Content.ReadAsStringAsync();  
 return JArray.Parse(responseContent);  
}

E agora basta passar o o token e a url do backend para o método.

var responseA = await BackAD(accessToken, "https://localhost:44316");

 

Fluxo de Aplicativo

 

Por fim podemos criar uma aplicação console que acessa a mesma API, https://localhost:44316, no entanto agora vamos usar apenas as roles sem o escopo.

Vou usar um código http puro que é mais simples, em uma próxima oportunidade falo apenas dessas implementações relacionados aos fluxos de aplicativos.

no escopo por ser um fluxo de client_credencial passamos dessa forma “api://47b1d030-f6ff-41a8–8933-c52235a2651b/.default”

private static void Client_CredencialHttp()  
{
        var client = new HttpClient();  
        var response = client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest  
        {  
            Address = $"https://login.microsoftonline.com/{_tenantId}/oauth2/v2.0/token",  
            ClientId = _clientId,  
            ClientSecret = _clientSecret,  
            Scope = _scope
        }).Result;

        var accessToken = response.AccessToken;
        
        using (HttpClient httpClient = new HttpClient())  
        {  
            var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:44316/api/WeatherForecast");  
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);  
            HttpResponseMessage responseApi = httpClient.SendAsync(request).Result;  
            responseApi.EnsureSuccessStatusCode();  
            string responseBody = responseApi.Content.ReadAsStringAsync().Result;
            
            Console.WriteLine("Token:");  
            Console.WriteLine(accessToken);Console.WriteLine("Response:");  
            Console.WriteLine(responseBody);  
        }

        Console.WriteLine("Hello World!");  
    }  
}

A saída do meu console foi essa.

Token:
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IjJaUXBKM1VwYmpBWVhZR2FYRUpsOGxWMFRPSSIsImtpZCI6IjJaUXBKM1VwYmpBWVhZR2FYRUpsOGxWMFRPSSJ9.eyJhdWQiOiJhcGk6Ly80N2IxZDAzMC1mNmZmLTQxYTgtODkzMy1jNTIyMzVhMjY1MWIiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83Nzk4MTFkOC00NzUzLTRjMzQtYmFlYi02YjUzOTU3ZDUyZTMvIiwiaWF0IjoxNjU4MTYzMjc5LCJuYmYiOjE2NTgxNjMyNzksImV4cCI6MTY1ODE2NzE3OSwiYWlvIjoiRTJaZ1lEalc2aDR1dTlkNWJrMzhET1c2K2JIM0FBPT0iLCJhcHBpZCI6ImIzODY2ZTUwLTUzNGYtNDYxYS1hNzZlLWQ1YWVhYzEyNmE2MyIsImFwcGlkYWNyIjoiMSIsImlkcCI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0Lzc3OTgxMWQ4LTQ3NTMtNGMzNC1iYWViLTZiNTM5NTdkNTJlMy8iLCJvaWQiOiI5ZGJhZTgyZC1iMzVkLTQ2OTQtYjMwMC04OWZlNjRhZjMyYzAiLCJyaCI6IjAuQVRRQTJCR1lkMU5ITkV5NjYydFRsWDFTNHpEUXNVZl85cWhCaVRQRklqV2laUnMwQUFBLiIsInJvbGVzIjpbInNlY3JldGRhdGEucmVhZCIsInNlY3JldGRhdGEuYWNjZXNzIl0sInN1YiI6IjlkYmFlODJkLWIzNWQtNDY5NC1iMzAwLTg5ZmU2NGFmMzJjMCIsInRpZCI6Ijc3OTgxMWQ4LTQ3NTMtNGMzNC1iYWViLTZiNTM5NTdkNTJlMyIsInV0aSI6InRyLU1NajB3c1VDckRYNjRfNW9xQUEiLCJ2ZXIiOiIxLjAifQ.H-gnwjbMI2ZIb3e3MUzJ6AidEUB90V5OLNh32Lg5HNnjcIl5RP19fpBYkq6v9l9ISt9hvDu77QrI6Ex2W8YImDEeY06GX4tuSe8hTxn2rUNI025N7mCP60Y0ZNDYw7AmlKbgSuN\_p9slsrknNzot-jcZYobDNTV0D\_g6tFCTLaS0GgCa36DCevvSwYUjD-WrHydNBKudHAfV-VXAdS5LzGWCzjtMPnGdZ1ezdAOctoxC29zTv-_qq2gmU19xFWjFz8XPhcscaGw5mJBRmkyu66zg1k96eYVy70SE5__BSmGBlBe0r7uH5-ZKXsgmpzPzwDa4GoykWyEAwCg3nwmgxA  
Response:  
[{"date":"2022-07-19T13:59:39.509767-03:00","temperatureC":44,"temperatureF":111,"summary":"Hot"},{"date":"2022-07-20T13:59:39.5100491-03:00","temperatureC":-9,"temperatureF":16,"summary":"Freezing"},{"date":"2022-07-21T13:59:39.5100593-03:00","temperatureC":37,"temperatureF":98,"summary":"Hot"},{"date":"2022-07-22T13:59:39.5100597-03:00","temperatureC":52,"temperatureF":125,"summary":"Hot"},{"date":"2022-07-23T13:59:39.51006-03:00","temperatureC":-8,"temperatureF":18,"summary":"Bracing"}]  
Hello World!

 

Referências

 

  1. Adicionar funções de aplicativo e obtê-las de um token — Microsoft identity platform | Microsoft Docs
  2. ms-identity-javascript-angular-tutorial/5-AccessControl at main · Azure-Samples/ms-identity-javascript-angular-tutorial (github.com)
  3. Documentação da plataforma de identidade da Microsoft | Microsoft Docs

GIT

Vocês podem baixar esses código do git hub abaixo em azuread-authentication-msal-samples

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.