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:
  • Il browser dell'utente
  • L'identity provider IdP Entra Id
  • Il sito con la custom API
OAuth 2.0 implicit grant flow
OAuth 2.0 implicit grant flow
il flusso di autenticazione è questo
OAuth 2.0 authorize
OAuth 2.0 authorize

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
description
description

Andare in Expose an API:
  • Premere Add
  • in Application ID URI accettare il default composto da api://<clientId>
  • Premere Save
Application ID URI
Application ID URI
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
Scope
Scope
Il nome dello scope può essere scelto liberamente.
il nuovo scope sarà simile al seguente, salvo il guid che cambia ad ogni app registration
api://78ed870c-xxxx-xxxx-xxxx-db65722fb9d4/user_impersonation
Scope
Scope

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 part

Gli 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 SPFx

DOS / 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).
App Catalog
App Catalog

fare l'upload del package nell'App Catalog, comparirà questo popup per abilitare la solution
Enable app
Enable app
subito dopo compare un altro popup con l'avviso per approvare le autorizzazioni
Warning permissions
Warning permissions
premendo Go to API access page si va alla pagina API access, selezionare la permission e approvarla
API access approve
API access approve

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.

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 => {...
ma soprattutto nell'appsettings.json della API va aggiunto il parametro Audience

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
Groups
Groups
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"
        },
...
]
ovvero con il guid del gruppo, che è l'identificativo univoco del gruppo, non il nome.
Tags:
SPFx18 SharePoint Online77 C#237
Potrebbe interessarti anche: