Web API 4.8 protette con Azure Client Credential Flow
Un esempio di come mettere in sicurezza delle Web API sviluppate con .NET 4.8.1 proteggendole con Azure Client Credential Flow.
Registrazione su Azure
Primo step creare su Azure una nuova App Registration New App Registration creare un client secretNew Client Secret
Copiarlo subito perchè successivamente non sarà più accessibile .
aggiungere lo scope di defaultScopenella shermata di riepilogo si possono trovale le altre informazioni quali Tenant ID, Client ID e Scope (Application ID Uri)Scope
Web API
Creare un nuovo progetto con l'autenticazione Template Web ASP.NET Framework scegliendo il .NET Framework 4.8 Scelgo il .NET 4.8.1 scegli come tipo di progetto API Web e l'autenticazione Microsoft Identity PlatformAuthenticazione Microsoft Identity Platform A questo punto si apre un dialogo dove puoi scegliere l'App Registration su Azure Scelgo l'APP Registration verifica e conferma le scelteRiepilogo
Passaggi manuali
Se non si vuole fare la configurazione con il wizard, questi sono gli step necessari
using Owin;
namespace WebApplication1
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
}
}
Nella Startup.Auth.cs, definisco l'autenticazione tramite Token JWT
C#: Startup.Auth.cs
using System.Configuration;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin.Security.ActiveDirectory;
using Owin;
namespace WebApplication1
{
public partial class Startup
{
// Per altre informazioni su come configurare l'autenticazione, vedere https://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:TenantId"],
TokenValidationParameters = new TokenValidationParameters {
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
},
});
}
}
}
i paramtri di configurazione per validare il token JWT sono nel file web.config
Nel parametro ida:Audience indico solo lo scope senza nessun suffisso.
Il parametro ida:Audience deve corrisponde al valore aud presente nel token JWTJWF Scope/Audience Esempio di controller protetto tramite la decorazione [Autorize] che sarà accessibile solo con un token JWT valido
C#: ValuesController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace WebApplication1.Controllers
{
[Authorize]
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
public string Get(int id)
{
return "value";
}
// POST api/values
public void Post([FromBody]string value)
{
}
// PUT api/values/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
public void Delete(int id)
{
}
}
}
Test
Per testare il funzionamento si può usare questa console application
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Headers;
using System.Net.Http;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace ConsoleApp1
{
class Program
{
static async Task Main(string[] args)
{
string tenantId = "b32dxxxx-xxxx-xxxx-xxxx-xxxxxxxxdca9";
string clientId = "5881xxxx-xxxx-xxxx-xxxx-xxxxxxxx03e3";
string clientSecret = "FAFxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxadV";
string scope = "api://5881xxxx-xxxx-xxxx-xxxx-xxxxxxxx03e3/.default";
string token = await GetTokenAsync(tenantId, clientId, clientSecret, scope);
string result = await CallApi(token);
Console.WriteLine(result);
}
Per eseguire la chiamata alla API protette serviranno i valori di: tenantId, clientId, secret e scope.
Nella variabile scope deve essere aggiunto il suffisso /.default.
Tramite il ClientID e il ClientSecret richiedo il token JWT di autenticazione da usare nelle chiamte alle API
C#: Esempio GetToken
private static async Task<string> GetTokenAsync(string tenantId, string clientId, string clientSecret, string scope)
{
// endpoint di autenticazione per ottenere il token
string tokenUrl = $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token";
var content = new FormUrlEncodedContent(new Dictionary<string, string> {
{ "client_id",clientId },
{ "client_secret", clientSecret},
{ "grant_type", "client_credentials" },
{ "scope", scope },
});
using (var httpClient = new HttpClient())
using (var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, new Uri(tokenUrl)))
{
httpRequestMessage.Content = content;
using (var response = await httpClient.SendAsync(httpRequestMessage))
{
if (response.IsSuccessStatusCode)
{
var responseStream = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseStream);
var t = JsonConvert.DeserializeObject<TokenResponse>(responseStream);
// ho il token JWT necessario per le chiamate verso le API protette
return t.AccessToken;
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
}
}
a questo punto posso chiamare l'API protetta, aggiungendo nell'header della chiamata HTTP la riga:
private static async Task<string> CallApi(string token)
{
string apiUrl = $"https://localhost:44369/api/values";
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
using (var response = await httpClient.GetAsync(apiUrl))
{
if (response.IsSuccessStatusCode)
{
var responseStream = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseStream);
return responseStream;
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
}
}
Per la GetToken è necessaria questa classe di supporto
C#
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
internal class TokenResponse
{
//"token_type"
//{"token_type":"Bearer",
//"expires_in":3599,
//"ext_expires_in":3599,
//"access_token"":"eyJ0eXAiOiJKV1
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("access_token")]
public string AccessToken { get; set; }
}
}