Chiamare una custom API .NET 8 in SPFx
Questo esempio mostra come creare una API .NET 8, protetta da autenticazione integrata con Entra ID, e come chiamarla da una web part SPFx passando il contesto dell'utente usando le delegate permission.
L'autenticazione tra API e SPFx si basa su OAuth 2.0 implicit grant flow.
I soggetti coinvolti sono:
Andare in Expose an API:
Aggiungere uno scope con nome user_impersonation
api://78ed870c-xxxx-xxxx-xxxx-db65722fb9d4/user_impersonation
La registrazione della app è finita, copiarsi i valori di
Gli step per configurare l'API autenticata sono:
In appsettings.json inserire i valori di:
Gli step per configurare la web part sono:
Nel package-solution.json inserire:
Nel metodo onInit inizializzare il client aadHttpClientFactory passando come parametro l'Application ID URI
Infine la chiamata all'API custom, ad esempio https://localhost:7101/demo
Caricare il package nell'admin di SharePoint Online nell'App Catalog https://tenantName.sharepoint.com/sites/AppCatalog/_layouts/15/tenantAppCatalog.aspx/manageApps (sostituire TenantName).
fare l'upload del package nell'App Catalog, comparirà questo popup per abilitare la solutionsubito dopo compare un altro popup con l'avviso per approvare le autorizzazionipremendo Go to API access page si va alla pagina API access, selezionare la permission e approvarla
A questo punto il setup di tutte le parti e completo, si può fare debug della soluzions SPFx con
Vedi solution completa su GitHub.
In realtà è possibile cambiarlo con qualcosa di più mnemonico che ricordi l'API, ad esempio api://spfx.api/78ed870c-b931-442e-91ff-db65722fb9d4 oppure una qualsiasi altra stringa che rappresenti una Uri.
A questo punto, nella web part SPFx, la chiamata per avere il token di autorizzazione diventa:
ma soprattutto nell'appsettings.json della API va aggiunto il parametro Audience
Se dovessero servire, si può modificare l'app registration andando in Token configuration, Add groups claim.
Qui configurare la tipologia di gruppi che servono e premere Add
Le informazioni dei gruppi, presenti nel claims utente, saranno tipo questi:
ovvero con il guid del gruppo, che è l'identificativo univoco del gruppo, non il nome.
L'autenticazione tra API e SPFx si basa su OAuth 2.0 implicit grant flow.
I soggetti coinvolti sono:
- Il browser dell'utente
- L'identity provider IdP Entra Id
- Il sito con la custom API
DEMO
I passaggi per creare una solution completa sono questi:- Creare una App Registration in Entra ID
- Creare una API in .NET 8
- Creare una nuova web part SPFx
- Nella SharePoint Admin approvare i permessi dell'App Registration
Creare una App Registration in Entra ID
Andare in Entra ID sulla pagina delle App Registration creare una nuova app New registration e inserire:- Name / Display name = SgartSPFxDelegateDemoApp2
- Supported accounnt type = Accounts in this organizational directory only (sgart only - Single tenant)
- Redirect URI = vuoto
- Premere Register
Andare in Expose an API:
- Premere Add
- in Application ID URI accettare il default composto da api://<clientId>
- Premere Save
Attenzione non cambiare il formato del parametro Application ID URI, accettare il default.
Aggiungere uno scope con nome user_impersonation
- Premere Add a scope
- Scope name = user_impersonation
- Who can consent? = Admins only
- Admin consent display name = qualsiasi testo
- Admin consent descripton = qualsiasi testo
- State = Enabled
- Premere Add scope
Il nome dello scope può essere scelto liberamente.
il nuovo scope sarà simile al seguente, salvo il guid che cambia ad ogni app registrationapi://78ed870c-xxxx-xxxx-xxxx-db65722fb9d4/user_impersonation
La registrazione della app è finita, copiarsi i valori di
- Display Name
- Application (client) ID
- Directory (tenant) ID
- Application ID URI
Creare una API in .NET 8
Aprire Visual Studio 2022 e creare un nuovo progetto ASP.NET Core Web API con il framework .NET 8.Gli step per configurare l'API autenticata sono:
- In Program.cs aggiungere l'autenticazione
- In Program.cs aggiungere il middleware Cors
- in appsettings.json aggiungere la sezione AzureAd con i relativi parametri dell'app registration e valore Cors
- Creare un controller protetto con [Authorize]
C#: Programm.cs
var builder = WebApplication.CreateBuilder(args);
...
var settingsSection = builder.Configuration.GetSection(AppSettings.KEY_NAME);
builder.Services.Configure<AppSettings>(settingsSection);
...
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
...
var app = builder.Build();
...
app.UseCors(policy =>
{
string[] urls = settingsSection.Get<AppSettings>()?.Cors ?? throw new Exception("appsettings Cors is null");
policy.WithOrigins(urls)
.AllowAnyMethod()
.AllowAnyHeader();
});
In appsettings.json inserire i valori di:
- Cors inserire la url del tenant SharePoint Online
- Domain
- TenantId
- ClientId
- Scopes lo scope custom user_impersonation definito nell'app registraion
JSON: appsettings.json
{
"AppSettings": {
"Cors": [
/* senza slash finale */
"https://tenantName.sharepoint.com"
]
},
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "tenantName.sharepoint.com",
"TenantId": "xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxx",
"ClientId": "xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxx",
"Scopes": "user_impersonation",
"CallbackPath": "/signin-oidc"
}
...
}
Attenzione nella sezione Cors iserire la root url del tenant SharePoint Online senza la slash finale.
C#: DemoController.cs
[Authorize]
[ApiController]
[Route("[controller]")]
[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]
public class DemoController(ILogger<DemoController> logger, MainService main) : ControllerBase
{
[HttpGet()]
public async Task<DemoResponse> GetAsync()
{
string userName = User.Identity?.Name ?? string.Empty;
return new DemoResponse
{
Now = DateTime.Now,
Data = await main.GetAsync(userName),
IsAuthenticated = User.Identity?.IsAuthenticated ?? false,
UserName = userName,
Claims = User.Claims.Select(x => new ClaimValue { Type = x.Type, Value = x.Value })
};
}
}
Da notare la decorazione con l'attributo [Authorize] e [RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")].
Creare una nuova web part SPFx
In Visual Studio Code generare un nuovo progetto di tipo web partGli step per configurare la web part sono:
- nel file package-solution.json aggiungere la sezione webApiPermissionRequests
- inizializzare il client aadHttpClientFactory
- eseguire una chiamata REST all'API custom in .NET 8
Nel package-solution.json inserire:
- resource = Display name dell'app registration
- scope = lo scope custom creato nell'app registration user_impersonation
JSON: package-solution.json
{
...
"solution": {
...
"webApiPermissionRequests": [
{
"resource": "SgartSPFxDelegateDemoApp2",
"scope": "user_impersonation"
}
],
...
}
Nel metodo onInit inizializzare il client aadHttpClientFactory passando come parametro l'Application ID URI
TypeScript: SgartDemoDelegateWebPart.ts
private apiClient: AadHttpClient;
protected onInit(): Promise<void> {
// inizializzo il client
return new Promise<void>((resolve: () => void, reject: (error: any) => void): void => {
this.context.aadHttpClientFactory
.getClient("api://78ed870c-xxxx-xxxx-xxxx-db65722fb9d4")
.then((client: AadHttpClient): void => {
this.apiClient = client;
console.log('Sgart: client:', this.apiClient);
resolve();
}, (err: any) => {
console.error("Sgart: getClient", err);
return reject(err)
});
});
}
l'Application ID URI deve essere nella forma api://<clientiId> senza il suffisso /user_impersonation.
Infine la chiamata all'API custom, ad esempio https://localhost:7101/demo
TypeScript
this.apiClient
.get('https://localhost:7101/demo', AadHttpClient.configurations.v1)
.then((res: HttpClientResponse): Promise<any> => {
return res.json();
})
.then((data: any): void => {
console.log("Sgart: data", data);
}, (err: any): void => {
console.error("Sgart: err", err);
});
Nella SharePoint Admin approvare i permessi dell'App Registration
Questo è uno step molto importante.Affinchè la chiamata alla custom API funzioni è necessario installare la web part nel tenant per approvare le permission specificate nel file package-solution.json.
Creare il package SPFxDOS / Batch file
gulp clear; gulp bundle --ship; gulp package-solution --ship
Caricare il package nell'admin di SharePoint Online nell'App Catalog https://tenantName.sharepoint.com/sites/AppCatalog/_layouts/15/tenantAppCatalog.aspx/manageApps (sostituire TenantName).
fare l'upload del package nell'App Catalog, comparirà questo popup per abilitare la solutionsubito dopo compare un altro popup con l'avviso per approvare le autorizzazionipremendo Go to API access page si va alla pagina API access, selezionare la permission e approvarla
A questo punto il setup di tutte le parti e completo, si può fare debug della soluzions SPFx con
DOS / Batch file
gulp serve
Nota: per il debug non è necessario fare l'upload della solution ad ogni modifica ne installare la app nel sito.
L'installazione è servita solo per poter approvare le permission utilizzate.
Si può tranquillamente utilizzare la pagina del workbench ( https://sgart.sharepoint.com/_layouts/15/workbench.aspx ) per le normali attività di debug.
L'installazione è servita solo per poter approvare le permission utilizzate.
Si può tranquillamente utilizzare la pagina del workbench ( https://sgart.sharepoint.com/_layouts/15/workbench.aspx ) per le normali attività di debug.
Vedi solution completa su GitHub.
Audience
Nei passaggi precedenti, ho sottolineato che, nella creazione della App Registration, il parametro Application ID Uri non doveva essere modificato rispetto al default proposto.In realtà è possibile cambiarlo con qualcosa di più mnemonico che ricordi l'API, ad esempio api://spfx.api/78ed870c-b931-442e-91ff-db65722fb9d4 oppure una qualsiasi altra stringa che rappresenti una Uri.
A questo punto, nella web part SPFx, la chiamata per avere il token di autorizzazione diventa:
TypeScript
this.context.aadHttpClientFactory
.getClient("api://spfx.api/78ed870c-xxxx-xxxx-xxxx-db65722fb9d4")
.then((client: AadHttpClient): void => {...
JSON: appsettings.json Audience
{
...
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "tenantName.sharepoint.com",
"TenantId": "xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxx",
"ClientId": "78ed870c-xxxx-xxxx-xxxx-db65722fb9d4",
"Audience": "api://spfx.api/78ed870c-xxxx-xxxx-xxxx-db65722fb9d4",
"Scopes": "user_impersonation",
"CallbackPath": "/signin-oidc"
}
...
}
Note
Nel claims che viene passato all'API non ci sono le informazioni relative ai gruppi di appartenenza.Se dovessero servire, si può modificare l'app registration andando in Token configuration, Add groups claim.
Qui configurare la tipologia di gruppi che servono e premere Add
Le informazioni saranno disponibili dopo pochi secondi facendo refresh della pagina.
Le informazioni dei gruppi, presenti nel claims utente, saranno tipo questi:
JSON: claims
[
{
"type": "groups",
"value": "7122f732-xxxx-xxxx-xxxx-5bb1c071c9ab"
},
{
"type": "groups",
"value": "709bc756-xxxx-xxxx-xxxx-cb7f8d0a0a49"
},
...
]