Compartir a través de


Uso de Graph API con Blazor WebAssembly de ASP.NET Core

Nota

Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión de .NET 9 de este artículo.

Advertencia

Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulte la directiva de compatibilidad de .NET y .NET Core. Para la versión actual, consulte la versión de .NET 9 de este artículo.

Importante

Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.

Para la versión actual, consulte la versión de .NET 9 de este artículo.

En este artículo se explica cómo usar Microsoft Graph en aplicaciones Blazor WebAssembly, que permite a las aplicaciones acceder a recursos de Microsoft Cloud.

Se tratan dos enfoques:

  • Graph SDK: los SDK de Microsoft Graph simplifican la creación de aplicaciones de alta calidad, eficaces y resistentes que acceden a Microsoft Graph. Selecciona el botón SDK de Graph situado en la parte superior de este artículo para adoptar este enfoque.

  • HttpClient con nombre con Graph API: un HttpClient nombrado puede emitir solicitudes de Microsoft Graph API directamente a Microsoft Graph. Selecciona el botón HttpClient con nombre con Graph API en la parte superior de este artículo para adoptar este enfoque.

La guía de este artículo no está diseñada para reemplazar la documentación de Microsoft Graph y la guía de seguridad de Azure en otros conjuntos de documentación de Microsoft. Evalúa las instrucciones de seguridad de la sección Recursos adicionales de este artículo antes de implementar Microsoft Graph en un entorno de producción. Sigue las procedimientos recomendados de Microsoft para limitar las vulnerabilidades de tus aplicaciones.

Los siguientes ejemplos de Microsoft Graph y Azure proporcionan enfoques adicionales para trabajar con Microsoft Graph y Blazor WebAssembly:

Para proporcionar comentarios sobre cualquiera de los dos ejemplos anteriores, abre un problema en el repositorio de GitHub del ejemplo. Si vas a abrir una incidencia para el ejemplo de Azure, proporciona un vínculo al ejemplo en el comentario de apertura porque el repositorio de ejemplo de Azure (Azure-Samples) contiene muchos ejemplos. Describe el problema en detalle e incluye código de ejemplo según sea necesario. Coloca una aplicación mínima en GitHub que reproduzca el problema o el error. Asegúrate de quitar los datos de configuración de la cuenta de Azure del ejemplo antes de confirmarlos en el repositorio público.

Para proporcionar comentarios o buscar ayuda con este artículo o ASP.NET Core, consulta aspectos básicos Blazor de ASP.NET Core.

Importante

Los escenarios descritos en este artículo se aplican al uso de Microsoft Entra (ME-ID) como proveedor de identity, no a AAD B2C. El uso de Microsoft Graph con una aplicación Blazor WebAssembly del lado cliente y el proveedor de identity de AAD B2C no se admite en este momento porque la aplicación requeriría un secreto de cliente, que no se puede proteger en la aplicación Blazor del lado cliente. Para una aplicación Blazor WebAssembly independiente de AAD B2C use Graph API, crea una API de servidor backend (web) para acceder a Graph API en nombre de los usuarios. La aplicación del lado cliente autentica y autoriza a los usuarios a llamar a la API web para acceder de forma segura a Microsoft Graph y devolver datos a la aplicación del lado cliente Blazor desde su API web basada en servidor. El secreto de cliente se mantiene de forma segura en la API web basada en servidor, no en la aplicación Blazor en el cliente. Nunca almacenes un secreto de cliente en una aplicación Blazor del lado cliente.

Se admite el uso de una aplicación Blazor WebAssembly hospedada, donde la aplicación Server usa el SDK de Graph o Graph API para proporcionar datos de Graph a la aplicación Client a través de la API web. Para más información, consulta la sección Soluciones Blazor WebAssembly hospedadas de este artículo.

Los ejemplos de este artículo aprovechan las nuevas características de .NET/C#. Al usar los ejemplos con .NET 7 o versiones anteriores, se requieren modificaciones menores. Sin embargo, los ejemplos de texto y código relacionados con la interacción con Microsoft Graph son los mismos para todas las versiones de ASP.NET Core.

Las instrucciones siguientes se aplican a Microsoft Graph v5.

El SDK de Microsoft Graph para su uso en aplicaciones Blazor se denomina Biblioteca cliente de .NET de Microsoft Graph.

Los ejemplos del SDK de Graph necesitan las siguientes referencias de paquete en la aplicación Blazor WebAssembly independiente. Ya se hace referencia a los dos primeros paquetes si la aplicación se ha habilitado para la autenticación MSAL, por ejemplo, al crear la aplicación siguiendo las instrucciones de Protección de una aplicación ASP.NET CoreBlazor WebAssembly independiente con Microsoft Entra ID.

Los ejemplos del SDK de Graph requieren las siguientes referencias de paquete en la aplicación Blazor WebAssembly independiente o en la aplicación Client de una solución Blazor WebAssembly hospedada. Ya se hace referencia a los dos primeros paquetes si la aplicación se ha habilitado para la autenticación MSAL, por ejemplo, al crear la aplicación siguiendo las instrucciones de Protección de una aplicación ASP.NET CoreBlazor WebAssembly independiente con Microsoft Entra ID.

Nota:

Para obtener instrucciones sobre cómo agregar paquetes a aplicaciones .NET, consulta los artículos de Instalación y administración de paquetes en Flujo de trabajo de consumo de paquetes (documentación de NuGet). Confirma las versiones correctas del paquete en NuGet.org.

En Azure Portal, concede permisos delegados (ámbitos)† para los datos de Microsoft Graph a los que la aplicación debe poder acceder en nombre de un usuario. En el ejemplo de este artículo, el registro de la aplicación debe incluir un permiso delegado para leer datos de usuario (ámbito Microsoft.Graph>User.Read en Permisos de API, Tipo: Delegado). El ámbito User.Read permite a los usuarios iniciar sesión en la aplicación y, a la aplicación, leer el perfil y la información empresarial de los usuarios que han iniciado sesión. Para obtener más información, consulta Información general sobre los permisos y el consentimiento en la Plataforma de identity de Microsoft e Información general sobre los permisos de Microsoft Graph.

Permisos y ámbitos significan lo mismo y se usan indistintamente en la documentación de seguridad y Azure Portal. A menos que el texto haga referencia a Azure Portal, en este artículo se usan ámbito/ámbitos al hacer referencia a los permisos de Graph.

Los ámbitos no distinguen mayúsculas de minúsculas, por lo que User.Read es igual que user.read. No dudes en usar cualquiera de los formatos, pero se recomienda una opción coherente entre el código de la aplicación.

Después de agregar los ámbitos de Microsoft Graph API al registro de la aplicación en Azure Portal, agrega la siguiente configuración de la aplicación al archivo wwwroot/appsettings.json de la aplicación, que incluye la dirección URL base de Graph, con la versión de Microsoft Graph y los ámbitos. En el siguiente ejemplo se especifica el ámbito User.Read para los ejemplos de secciones posteriores de este artículo. Los ámbitos no distinguen mayúsculas de minúsculas.

"MicrosoftGraph": {
  "BaseUrl": "https://graph.microsoft.com",
  "Version": "{VERSION}",
  "Scopes": [
    "user.read"
  ]
}

En el ejemplo anterior, el marcador de posición {VERSION} es la versión de Microsoft Graph API (por ejemplo: v1.0).

A continuación, se muestra un ejemplo de un archivo de configuración wwwroot/appsettings.json completo para una aplicación que usa ME-ID como proveedor de identity, donde se especifica la lectura de datos de usuario (ámbito user.read) para Microsoft Graph:

{
  "AzureAd": {
    "Authority": "https://login.microsoftonline.com/{TENANT ID}",
    "ClientId": "{CLIENT ID}",
    "ValidateAuthority": true
  },
  "MicrosoftGraph": {
    "BaseUrl": "https://graph.microsoft.com",
    "Version": "v1.0",
    "Scopes": [
      "user.read"
    ]
  }
}

En el ejemplo anterior, el marcador de posición {TENANT ID} es el identificador de directorio (inquilino) y el marcador de posición {CLIENT ID} es el identificador de aplicación (cliente). Para obtener más información, consulta Protección de una aplicación ASP.NET Core Blazor WebAssembly independiente con Microsoft Entra ID.

Agrega la siguiente clase GraphClientExtensions a la aplicación independiente. Los ámbitos se proporcionan a la propiedad Scopes de AccessTokenRequestOptions en el método AuthenticateRequestAsync.

Agrega la siguiente clase GraphClientExtensions a la aplicación independiente o la aplicación Client de una solución de Blazor WebAssembly hospedada. Los ámbitos se proporcionan a la propiedad Scopes de AccessTokenRequestOptions en el método AuthenticateRequestAsync.

Cuando no se obtiene un token de acceso, el código siguiente no establece un encabezado de autorización de portador para las solicitudes de Graph.

GraphClientExtensions.cs:

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.Authentication.WebAssembly.Msal.Models;
using Microsoft.Graph;
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Authentication;
using IAccessTokenProvider = 
    Microsoft.AspNetCore.Components.WebAssembly.Authentication.IAccessTokenProvider;

namespace BlazorSample;

internal static class GraphClientExtensions
{
    public static IServiceCollection AddGraphClient(
            this IServiceCollection services, string? baseUrl, List<string>? scopes)
    {
        if (string.IsNullOrEmpty(baseUrl) || scopes?.Count == 0)
        {
            return services;
        }

        services.Configure<RemoteAuthenticationOptions<MsalProviderOptions>>(
            options =>
            {
                scopes?.ForEach((scope) =>
                {
                    options.ProviderOptions.DefaultAccessTokenScopes.Add(scope);
                });
            });

        services.AddScoped<IAuthenticationProvider, GraphAuthenticationProvider>();

        services.AddScoped(sp =>
        {
            return new GraphServiceClient(
                new HttpClient(),
                sp.GetRequiredService<IAuthenticationProvider>(),
                baseUrl);
        });

        return services;
    }

    private class GraphAuthenticationProvider(IAccessTokenProvider tokenProvider, 
        IConfiguration config) : IAuthenticationProvider
    {
        private readonly IConfiguration config = config;

        public IAccessTokenProvider TokenProvider { get; } = tokenProvider;

        public async Task AuthenticateRequestAsync(RequestInformation request, 
            Dictionary<string, object>? additionalAuthenticationContext = null, 
            CancellationToken cancellationToken = default)
        {
            var result = await TokenProvider.RequestAccessToken(
                new AccessTokenRequestOptions()
                {
                    Scopes = 
                        config.GetSection("MicrosoftGraph:Scopes").Get<string[]>() ??
                        [ "user.read" ]
                });

            if (result.TryGetToken(out var token))
            {
                request.Headers.Add("Authorization", 
                    $"{CoreConstants.Headers.Bearer} {token.Value}");
            }
        }
    }
}

Importante

Consulta la sección DefaultAccessTokenScopes frente a AdditionalScopesToConsent para obtener una explicación sobre por qué el código anterior usa DefaultAccessTokenScopes para agregar los ámbitos en lugar de AdditionalScopesToConsent.

En el archivo Program, agrega los servicios y la configuración del cliente de Graph con el método de extensión AddGraphClient. El código siguiente tiene como valor predeterminado la dirección base de Microsoft Graph versión 1.0 y los ámbitos User.Read si no se encuentra esta configuración en el archivo de configuración de la aplicación:

var baseUrl = string.Join("/",
    builder.Configuration.GetSection("MicrosoftGraph")["BaseUrl"] ??
        "https://graph.microsoft.com",
    builder.Configuration.GetSection("MicrosoftGraph")["Version"] ??
        "v1.0");
var scopes = builder.Configuration.GetSection("MicrosoftGraph:Scopes")
    .Get<List<string>>() ?? [ "user.read" ];

builder.Services.AddGraphClient(baseUrl, scopes);

Llamada a Graph API desde un componente mediante el SDK de Graph

El siguiente componente UserData usa un GraphServiceClient insertado para obtener los datos de perfil de ME-ID del usuario y mostrar su número de teléfono móvil.

Para cualquier usuario de prueba que crees en ME-ID, asegúrate de proporcionar al perfil de ME-ID del usuario un número de teléfono móvil en Azure Portal.

UserData.razor:

@page "/user-data"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.Graph
@attribute [Authorize]
@inject GraphServiceClient Client

<PageTitle>User Data</PageTitle>

<h1>Microsoft Graph User Data</h1>

@if (!string.IsNullOrEmpty(user?.MobilePhone))
{
    <p>Mobile Phone: @user.MobilePhone</p>
}

@code {
    private Microsoft.Graph.Models.User? user;

    protected override async Task OnInitializedAsync()
    {
        user = await Client.Me.GetAsync();
    }
}

Agrega un vínculo a la página del componente en el componente NavMenu (Layout/NavMenu.razor):

<div class="nav-item px-3">
    <NavLink class="nav-link" href="user-data">
        <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> User Data
    </NavLink>
</div>

Sugerencia

Para agregar usuarios a una aplicación, consulta la sección Asignación de usuarios a un registro de aplicaciones con o sin roles de aplicación.

Al realizar pruebas con el SDK de Graph localmente, te recomendamos que utilices una nueva sesión de explorador privada/de incógnito para cada prueba para evitar que las cookies persistentes interfieran con las pruebas. Para obtener más información, consulta Protección de una aplicación ASP.NET Core Blazor WebAssembly independiente con Microsoft Entra ID.

Personalización de notificaciones de usuario con el SDK de Graph

En el siguiente ejemplo, la aplicación crea notificaciones de número de teléfono móvil y ubicación de oficina para un usuario a partir de los datos de su perfil de usuario de ME-ID. La aplicación debe tener el ámbito de Graph API User.Read configurado en ME-ID. Cualquier usuario de prueba para este escenario debe tener un número de teléfono móvil y una ubicación de oficina en su perfil de ME-ID, que se puede agregar a través de Azure Portal.

En la siguiente fábrica de cuentas de usuario personalizada:

  • Se incluye ILogger (logger) para mayor comodidad en caso de que desee registrar información o errores en el método CreateUserAsync.
  • En caso de que se produzca AccessTokenNotAvailableException, se redirige al usuario al proveedor de identity para iniciar sesión en su cuenta. Se pueden realizar acciones adicionales o diferentes cuando se produce un error al solicitar un token de acceso. Por ejemplo, la aplicación puede registrar AccessTokenNotAvailableException y crear una incidencia de soporte técnico para una investigación más detallada.
  • El RemoteUserAccount del marco representa la cuenta del usuario. Si la aplicación requiere una clase de cuenta de usuario personalizada que extienda RemoteUserAccount, intercambie la clase de cuenta de usuario personalizada con RemoteUserAccount en el código siguiente.

CustomAccountFactory.cs:

using System.Security.Claims;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;
using Microsoft.Graph;
using Microsoft.Kiota.Abstractions.Authentication;

public class CustomAccountFactory(IAccessTokenProviderAccessor accessor,
        IServiceProvider serviceProvider, ILogger<CustomAccountFactory> logger,
        IConfiguration config) 
    : AccountClaimsPrincipalFactory<RemoteUserAccount>(accessor)
{
    private readonly ILogger<CustomAccountFactory> logger = logger;
    private readonly IServiceProvider serviceProvider = serviceProvider;
    private readonly string? baseUrl = string.Join("/",
        config.GetSection("MicrosoftGraph")["BaseUrl"] ?? 
            "https://graph.microsoft.com",
        config.GetSection("MicrosoftGraph")["Version"] ??
            "v1.0");

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        RemoteUserAccount account,
        RemoteAuthenticationUserOptions options)
    {
        var initialUser = await base.CreateUserAsync(account, options);

        if (initialUser.Identity is not null &&
            initialUser.Identity.IsAuthenticated)
        {
            var userIdentity = initialUser.Identity as ClaimsIdentity;

            if (userIdentity is not null && !string.IsNullOrEmpty(baseUrl))
            {
                try
                {
                    var client = new GraphServiceClient(
                        new HttpClient(),
                        serviceProvider
                            .GetRequiredService<IAuthenticationProvider>(),
                        baseUrl);

                    var user = await client.Me.GetAsync();

                    if (user is not null)
                    {
                        userIdentity.AddClaim(new Claim("mobilephone",
                            user.MobilePhone ?? "(000) 000-0000"));
                        userIdentity.AddClaim(new Claim("officelocation",
                            user.OfficeLocation ?? "Not set"));
                    }
                }
                catch (AccessTokenNotAvailableException exception)
                {
                    exception.Redirect();
                }
            }
        }

        return initialUser;
    }
}

Configure la autenticación de MSAL para usar la fábrica de cuentas de usuario personalizada.

Confirme que el archivo Program usa el espacio de nombres Microsoft.AspNetCore.Components.WebAssembly.Authentication:

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

El ejemplo de esta sección se basa en el enfoque de leer la dirección URL base, la versión y los ámbitos de la configuración de la aplicación mediante la sección MicrosoftGraph del archivo wwwroot/appsettings.json. Las líneas siguientes ya deben estar presentes en el archivo Program de acuerdo a las instrucciones anteriores en este artículo:

var baseUrl = string.Join("/",
    builder.Configuration.GetSection("MicrosoftGraph")["BaseUrl"] ??
        "https://graph.microsoft.com",
    builder.Configuration.GetSection("MicrosoftGraph")["Version"] ??
        "v1.0");
var scopes = builder.Configuration.GetSection("MicrosoftGraph:Scopes")
    .Get<List<string>>() ?? [ "user.read" ];

builder.Services.AddGraphClient(baseUrl, scopes);

En el archivo Program, busque la llamada al método de extensión AddMsalAuthentication. Actualice el código a lo siguiente, que incluye una llamada a AddAccountClaimsPrincipalFactory que agrega una fábrica de entidades de seguridad de notificaciones de cuenta con CustomAccountFactory.

Si la aplicación usa una clase de cuenta de usuario personalizada que extiende RemoteUserAccount, intercambie la clase de cuenta de usuario personalizada para RemoteUserAccount en el código siguiente.

builder.Services.AddMsalAuthentication<RemoteAuthenticationState,
    RemoteUserAccount>(options =>
    {
        builder.Configuration.Bind("AzureAd", 
            options.ProviderOptions.Authentication);
    })
    .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, RemoteUserAccount,
        CustomAccountFactory>();

Puede usar el siguiente componente UserClaims para estudiar las notificaciones del usuario después de que el usuario se autentique con ME-ID:

UserClaims.razor:

@page "/user-claims"
@using System.Security.Claims
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
@inject AuthenticationStateProvider AuthenticationStateProvider

<h1>User Claims</h1>

@if (claims.Any())
{
    <ul>
        @foreach (var claim in claims)
        {
            <li>@claim.Type: @claim.Value</li>
        }
    </ul>
}
else
{
    <p>No claims found.</p>
}

@code {
    private IEnumerable<Claim> claims = Enumerable.Empty<Claim>();

    protected override async Task OnInitializedAsync()
    {
        var authState = await AuthenticationStateProvider
            .GetAuthenticationStateAsync();
        var user = authState.User;

        claims = user.Claims;
    }
}

Agrega un vínculo a la página del componente en el componente NavMenu (Layout/NavMenu.razor):

<div class="nav-item px-3">
    <NavLink class="nav-link" href="user-claims">
        <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> User Claims
    </NavLink>
</div>

Al realizar pruebas con el SDK de Graph localmente, te recomendamos que utilices una nueva sesión de explorador privada/de incógnito para cada prueba para evitar que las cookies persistentes interfieran con las pruebas. Para obtener más información, consulta Protección de una aplicación ASP.NET Core Blazor WebAssembly independiente con Microsoft Entra ID.

Las instrucciones siguientes se aplican a Microsoft Graph v4. Si va a actualizar una aplicación de SDK v4 a v5, consulte la guía de actualización y registro de cambios del SDK de .NET para Microsoft Graph v5.

El SDK de Microsoft Graph para su uso en aplicaciones Blazor se denomina Biblioteca cliente de .NET de Microsoft Graph.

Los ejemplos del SDK de Graph necesitan las siguientes referencias de paquete en la aplicación Blazor WebAssembly independiente. Ya se hace referencia a los dos primeros paquetes si la aplicación se ha habilitado para la autenticación MSAL, por ejemplo, al crear la aplicación siguiendo las instrucciones de Protección de una aplicación ASP.NET CoreBlazor WebAssembly independiente con Microsoft Entra ID.

Los ejemplos del SDK de Graph requieren las siguientes referencias de paquete en la aplicación Blazor WebAssembly independiente o en la aplicación Client de una solución Blazor WebAssembly hospedada. Ya se hace referencia a los dos primeros paquetes si la aplicación se ha habilitado para la autenticación MSAL, por ejemplo, al crear la aplicación siguiendo las instrucciones de Protección de una aplicación ASP.NET CoreBlazor WebAssembly independiente con Microsoft Entra ID.

Nota:

Para obtener instrucciones sobre cómo agregar paquetes a aplicaciones .NET, consulta los artículos de Instalación y administración de paquetes en Flujo de trabajo de consumo de paquetes (documentación de NuGet). Confirma las versiones correctas del paquete en NuGet.org.

En Azure Portal, concede permisos delegados (ámbitos)† para los datos de Microsoft Graph a los que la aplicación debe poder acceder en nombre de un usuario. En el ejemplo de este artículo, el registro de la aplicación debe incluir un permiso delegado para leer datos de usuario (ámbito Microsoft.Graph>User.Read en Permisos de API, Tipo: Delegado). El ámbito User.Read permite a los usuarios iniciar sesión en la aplicación y, a la aplicación, leer el perfil y la información empresarial de los usuarios que han iniciado sesión. Para obtener más información, consulta Información general sobre los permisos y el consentimiento en la Plataforma de identity de Microsoft e Información general sobre los permisos de Microsoft Graph.

Permisos y ámbitos significan lo mismo y se usan indistintamente en la documentación de seguridad y Azure Portal. A menos que el texto haga referencia a Azure Portal, en este artículo se usan ámbito/ámbitos al hacer referencia a los permisos de Graph.

Los ámbitos no distinguen mayúsculas de minúsculas, por lo que User.Read es igual que user.read. No dudes en usar cualquiera de los formatos, pero se recomienda una opción coherente entre el código de la aplicación.

Después de agregar los ámbitos de Microsoft Graph API al registro de la aplicación en Azure Portal, agrega la siguiente configuración de la aplicación al archivo wwwroot/appsettings.json de la aplicación, que incluye la dirección URL base de Graph, con la versión de Microsoft Graph y los ámbitos. En el siguiente ejemplo se especifica el ámbito User.Read para los ejemplos de secciones posteriores de este artículo. Los ámbitos no distinguen mayúsculas de minúsculas.

"MicrosoftGraph": {
  "BaseUrl": "https://graph.microsoft.com",
  "Version": "{VERSION}",
  "Scopes": [
    "user.read"
  ]
}

En el ejemplo anterior, el marcador de posición {VERSION} es la versión de Microsoft Graph API (por ejemplo: v1.0).

A continuación, se muestra un ejemplo de un archivo de configuración wwwroot/appsettings.json completo para una aplicación que usa ME-ID como proveedor de identity, donde se especifica la lectura de datos de usuario (ámbito user.read) para Microsoft Graph:

{
  "AzureAd": {
    "Authority": "https://login.microsoftonline.com/{TENANT ID}",
    "ClientId": "{CLIENT ID}",
    "ValidateAuthority": true
  },
  "MicrosoftGraph": {
    "BaseUrl": "https://graph.microsoft.com",
    "Version": "v1.0",
    "Scopes": [
      "user.read"
    ]
  }
}

En el ejemplo anterior, el marcador de posición {TENANT ID} es el identificador de directorio (inquilino) y el marcador de posición {CLIENT ID} es el identificador de aplicación (cliente). Para obtener más información, consulta Protección de una aplicación ASP.NET Core Blazor WebAssembly independiente con Microsoft Entra ID.

Agrega la siguiente clase GraphClientExtensions a la aplicación independiente. Los ámbitos se proporcionan a la propiedad Scopes de AccessTokenRequestOptions en el método AuthenticateRequestAsync. IHttpProvider.OverallTimeout se extiende desde el valor predeterminado de 100 segundos a 300 segundos para dar a HttpClient más tiempo para recibir una respuesta de Microsoft Graph.

Agregue la siguiente clase GraphClientExtensions a la aplicación independiente o la aplicación Client de una solución de Blazor WebAssembly hospedada. Los ámbitos se proporcionan a la propiedad Scopes de AccessTokenRequestOptions en el método AuthenticateRequestAsync. IHttpProvider.OverallTimeout se extiende desde el valor predeterminado de 100 segundos a 300 segundos para dar a HttpClient más tiempo para recibir una respuesta de Microsoft Graph.

Cuando no se obtiene un token de acceso, el código siguiente no establece un encabezado de autorización de portador para las solicitudes de Graph.

GraphClientExtensions.cs:

using System.Net.Http.Headers;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.Authentication.WebAssembly.Msal.Models;
using Microsoft.Graph;

namespace BlazorSample;

internal static class GraphClientExtensions
{
    public static IServiceCollection AddGraphClient(
        this IServiceCollection services, string? baseUrl, List<string>? scopes)
    {
        if (string.IsNullOrEmpty(baseUrl) || scopes?.Count == 0)
        {
            return services;
        }

        services.Configure<RemoteAuthenticationOptions<MsalProviderOptions>>(
            options =>
            {
                scopes?.ForEach((scope) =>
                {
                    options.ProviderOptions.DefaultAccessTokenScopes.Add(scope);
                });
            });

        services.AddScoped<IAuthenticationProvider, GraphAuthenticationProvider>();

        services.AddScoped<IHttpProvider, HttpClientHttpProvider>(sp =>
            new HttpClientHttpProvider(new HttpClient()));

        services.AddScoped(sp =>
        {
            return new GraphServiceClient(
                baseUrl,
                sp.GetRequiredService<IAuthenticationProvider>(),
                sp.GetRequiredService<IHttpProvider>());
        });

        return services;
    }

    private class GraphAuthenticationProvider(IAccessTokenProvider tokenProvider, 
        IConfiguration config) : IAuthenticationProvider
    {
        private readonly IConfiguration config = config;

        public IAccessTokenProvider TokenProvider { get; } = tokenProvider;

        public async Task AuthenticateRequestAsync(HttpRequestMessage request)
        {
            var result = await TokenProvider.RequestAccessToken(
                new AccessTokenRequestOptions()
                { 
                    Scopes = config.GetSection("MicrosoftGraph:Scopes").Get<string[]>()
                });

            if (result.TryGetToken(out var token))
            {
                request.Headers.Authorization ??= new AuthenticationHeaderValue(
                    "Bearer", token.Value);
            }
        }
    }

    private class HttpClientHttpProvider(HttpClient client) : IHttpProvider
    {
        private readonly HttpClient client = client;

        public ISerializer Serializer { get; } = new Serializer();

        public TimeSpan OverallTimeout { get; set; } = TimeSpan.FromSeconds(300);

        public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
        {
            return client.SendAsync(request);
        }

        public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
            HttpCompletionOption completionOption,
            CancellationToken cancellationToken)
        {
            return client.SendAsync(request, completionOption, cancellationToken);
        }

        public void Dispose()
        {
        }
    }
}

Importante

Consulte la sección DefaultAccessTokenScopes frente a AdditionalScopesToConsent para obtener una explicación sobre por qué el código anterior usa DefaultAccessTokenScopes para agregar los ámbitos en lugar de AdditionalScopesToConsent.

En el archivo Program, agregue los servicios y la configuración del cliente de Graph con el método de extensión AddGraphClient:

var baseUrl = string.Join("/",
    builder.Configuration.GetSection("MicrosoftGraph")["BaseUrl"] ??
        "https://graph.microsoft.com",
    builder.Configuration.GetSection("MicrosoftGraph")["Version"] ??
        "v1.0");
var scopes = builder.Configuration.GetSection("MicrosoftGraph:Scopes")
    .Get<List<string>>() ?? [ "user.read" ];

builder.Services.AddGraphClient(baseUrl, scopes);

Llamada a Graph API desde un componente mediante el SDK de Graph

El siguiente componente UserData usa un GraphServiceClient insertado para obtener los datos de perfil de ME-ID del usuario y mostrar su número de teléfono móvil. Para cualquier usuario de prueba que crees en ME-ID, asegúrate de proporcionar al perfil de ME-ID del usuario un número de teléfono móvil en Azure Portal.

UserData.razor:

@page "/user-data"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.Graph
@attribute [Authorize]
@inject GraphServiceClient Client

<PageTitle>User Data</PageTitle>

<h1>Microsoft Graph User Data</h1>

@if (!string.IsNullOrEmpty(user?.MobilePhone))
{
    <p>Mobile Phone: @user.MobilePhone</p>
}

@code {
    private Microsoft.Graph.User? user;

    protected override async Task OnInitializedAsync()
    {
        var request = Client.Me.Request();
        user = await request.GetAsync();
    }
}

Agrega un vínculo a la página del componente en el componente NavMenu (Layout/NavMenu.razor):

<div class="nav-item px-3">
    <NavLink class="nav-link" href="user-data">
        <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> User Data
    </NavLink>
</div>

Sugerencia

Para agregar usuarios a una aplicación, consulta la sección Asignación de usuarios a un registro de aplicaciones con o sin roles de aplicación.

Al realizar pruebas con el SDK de Graph localmente, te recomendamos que utilices una nueva sesión de explorador privada/de incógnito para cada prueba para evitar que las cookies persistentes interfieran con las pruebas. Para obtener más información, consulta Protección de una aplicación ASP.NET Core Blazor WebAssembly independiente con Microsoft Entra ID.

Personalización de notificaciones de usuario con el SDK de Graph

En el siguiente ejemplo, la aplicación crea notificaciones de número de teléfono móvil y ubicación de oficina para un usuario a partir de los datos de su perfil de usuario de ME-ID. La aplicación debe tener el ámbito de Graph API User.Read configurado en ME-ID. Cualquier usuario de prueba para este escenario debe tener un número de teléfono móvil y una ubicación de oficina en su perfil de ME-ID, que se puede agregar a través de Azure Portal.

En la siguiente fábrica de cuentas de usuario personalizada:

  • Se incluye ILogger (logger) para mayor comodidad en caso de que desee registrar información o errores en el método CreateUserAsync.
  • En caso de que se produzca AccessTokenNotAvailableException, se redirige al usuario al proveedor de identity para iniciar sesión en su cuenta. Se pueden realizar acciones adicionales o diferentes cuando se produce un error al solicitar un token de acceso. Por ejemplo, la aplicación puede registrar AccessTokenNotAvailableException y crear una incidencia de soporte técnico para una investigación más detallada.
  • El RemoteUserAccount del marco representa la cuenta del usuario. Si la aplicación requiere una clase de cuenta de usuario personalizada que extienda RemoteUserAccount, intercambie la clase de cuenta de usuario personalizada con RemoteUserAccount en el código siguiente.

CustomAccountFactory.cs:

using System.Security.Claims;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;
using Microsoft.Graph;

public class CustomAccountFactory(IAccessTokenProviderAccessor accessor, 
        IServiceProvider serviceProvider, ILogger<CustomAccountFactory> logger)
    : AccountClaimsPrincipalFactory<RemoteUserAccount>(accessor)
{
    private readonly ILogger<CustomAccountFactory> logger = logger;
    private readonly IServiceProvider serviceProvider = serviceProvider;

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        RemoteUserAccount account,
        RemoteAuthenticationUserOptions options)
    {
        var initialUser = await base.CreateUserAsync(account, options);

        if (initialUser.Identity is not null && 
            initialUser.Identity.IsAuthenticated)
        {
            var userIdentity = initialUser.Identity as ClaimsIdentity;

            if (userIdentity is not null)
            {
                try
                {
                    var client = ActivatorUtilities
                        .CreateInstance<GraphServiceClient>(serviceProvider);
                    var request = client.Me.Request();
                    var user = await request.GetAsync();

                    if (user is not null)
                    {
                        userIdentity.AddClaim(new Claim("mobilephone",
                            user.MobilePhone ?? "(000) 000-0000"));
                        userIdentity.AddClaim(new Claim("officelocation",
                            user.OfficeLocation ?? "Not set"));
                    }
                }
                catch (AccessTokenNotAvailableException exception)
                {
                    exception.Redirect();
                }
            }
        }

        return initialUser;
    }
}

Configure la autenticación de MSAL para usar la fábrica de cuentas de usuario personalizada.

Confirme que el archivo Program usa el espacio de nombres Microsoft.AspNetCore.Components.WebAssembly.Authentication:

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

El ejemplo de esta sección se basa en el enfoque de leer la dirección URL base, la versión y los ámbitos de la configuración de la aplicación mediante la sección MicrosoftGraph del archivo wwwroot/appsettings.json. Las líneas siguientes ya deben estar presentes en el archivo Program de acuerdo a las instrucciones anteriores en este artículo:

var baseUrl = string.Join("/",
    builder.Configuration.GetSection("MicrosoftGraph")["BaseUrl"] ??
        "https://graph.microsoft.com",
    builder.Configuration.GetSection("MicrosoftGraph")["Version"] ??
        "v1.0");
var scopes = builder.Configuration.GetSection("MicrosoftGraph:Scopes")
    .Get<List<string>>() ?? [ "user.read" ];

builder.Services.AddGraphClient(baseUrl, scopes);

En el archivo Program, busque la llamada al método de extensión AddMsalAuthentication. Actualice el código a lo siguiente, que incluye una llamada a AddAccountClaimsPrincipalFactory que agrega una fábrica de entidades de seguridad de notificaciones de cuenta con CustomAccountFactory.

Si la aplicación usa una clase de cuenta de usuario personalizada que extiende RemoteUserAccount, intercambie la clase de cuenta de usuario personalizada para RemoteUserAccount en el código siguiente.

builder.Services.AddMsalAuthentication<RemoteAuthenticationState,
    RemoteUserAccount>(options =>
    {
        builder.Configuration.Bind("AzureAd", 
            options.ProviderOptions.Authentication);
    })
    .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, RemoteUserAccount,
        CustomAccountFactory>();

Puede usar el siguiente componente UserClaims para estudiar las notificaciones del usuario después de que el usuario se autentique con ME-ID:

UserClaims.razor:

@page "/user-claims"
@using System.Security.Claims
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
@inject AuthenticationStateProvider AuthenticationStateProvider

<h1>User Claims</h1>

@if (claims.Any())
{
    <ul>
        @foreach (var claim in claims)
        {
            <li>@claim.Type: @claim.Value</li>
        }
    </ul>
}
else
{
    <p>No claims found.</p>
}

@code {
    private IEnumerable<Claim> claims = Enumerable.Empty<Claim>();

    protected override async Task OnInitializedAsync()
    {
        var authState = await AuthenticationStateProvider
            .GetAuthenticationStateAsync();
        var user = authState.User;

        claims = user.Claims;
    }
}

Agrega un vínculo a la página del componente en el componente NavMenu (Layout/NavMenu.razor):

<div class="nav-item px-3">
    <NavLink class="nav-link" href="user-claims">
        <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> User Claims
    </NavLink>
</div>

Al realizar pruebas con el SDK de Graph localmente, te recomendamos que utilices una nueva sesión de explorador privada/de incógnito para cada prueba para evitar que las cookies persistentes interfieran con las pruebas. Para obtener más información, consulta Protección de una aplicación ASP.NET Core Blazor WebAssembly independiente con Microsoft Entra ID.

En los ejemplos siguientes se usa un HttpClient con nombre para las llamadas de Graph API para obtener el número de teléfono móvil de un usuario para procesar una llamada o para personalizar las notificaciones de un usuario para incluir una notificación de número de teléfono móvil y una notificación de ubicación de la oficina.

En los ejemplos se necesita una referencia de paquete de Microsoft.Extensions.Http para la aplicación Blazor WebAssembly independiente.

Los ejemplos requieren una referencia de paquete para Microsoft.Extensions.Http para la aplicación Blazor WebAssembly independiente o la aplicación Client de una solución Blazor WebAssembly hospedada.

Nota

Para obtener instrucciones sobre cómo agregar paquetes a aplicaciones .NET, consulta los artículos de Instalación y administración de paquetes en Flujo de trabajo de consumo de paquetes (documentación de NuGet). Confirma las versiones correctas del paquete en NuGet.org.

En Azure Portal, concede permisos delegados (ámbitos)† para los datos de Microsoft Graph a los que la aplicación debe poder acceder en nombre de un usuario. En el ejemplo de este artículo, el registro de la aplicación debe incluir un permiso delegado para leer datos de usuario (ámbito Microsoft.Graph>User.Read en Permisos de API, Tipo: Delegado). El ámbito User.Read permite a los usuarios iniciar sesión en la aplicación y, a la aplicación, leer el perfil y la información empresarial de los usuarios que han iniciado sesión. Para obtener más información, consulta Información general sobre los permisos y el consentimiento en la Plataforma de identity de Microsoft e Información general sobre los permisos de Microsoft Graph.

Permisos y ámbitos significan lo mismo y se usan indistintamente en la documentación de seguridad y Azure Portal. A menos que el texto haga referencia a Azure Portal, en este artículo se usan ámbito/ámbitos al hacer referencia a los permisos de Graph.

Los ámbitos no distinguen mayúsculas de minúsculas, por lo que User.Read es igual que user.read. No dudes en usar cualquiera de los formatos, pero se recomienda una opción coherente entre el código de la aplicación.

Después de agregar los ámbitos de Microsoft Graph API al registro de la aplicación en Azure Portal, agrega la siguiente configuración de la aplicación al archivo wwwroot/appsettings.json de la aplicación, que incluye la dirección URL base de Graph, con la versión de Microsoft Graph y los ámbitos. En el siguiente ejemplo se especifica el ámbito User.Read para los ejemplos de secciones posteriores de este artículo. Los ámbitos no distinguen mayúsculas de minúsculas.

"MicrosoftGraph": {
  "BaseUrl": "https://graph.microsoft.com",
  "Version": "{VERSION}",
  "Scopes": [
    "user.read"
  ]
}

En el ejemplo anterior, el marcador de posición {VERSION} es la versión de Microsoft Graph API (por ejemplo: v1.0).

A continuación, se muestra un ejemplo de un archivo de configuración wwwroot/appsettings.json completo para una aplicación que usa ME-ID como proveedor de identity, donde se especifica la lectura de datos de usuario (ámbito user.read) para Microsoft Graph:

{
  "AzureAd": {
    "Authority": "https://login.microsoftonline.com/{TENANT ID}",
    "ClientId": "{CLIENT ID}",
    "ValidateAuthority": true
  },
  "MicrosoftGraph": {
    "BaseUrl": "https://graph.microsoft.com",
    "Version": "v1.0",
    "Scopes": [
      "user.read"
    ]
  }
}

En el ejemplo anterior, el marcador de posición {TENANT ID} es el identificador de directorio (inquilino) y el marcador de posición {CLIENT ID} es el identificador de aplicación (cliente). Para obtener más información, consulta Protección de una aplicación ASP.NET Core Blazor WebAssembly independiente con Microsoft Entra ID.

Crea la siguiente configuración de clase y proyecto GraphAuthorizationMessageHandler en el archivo Program para trabajar con Graph API. La dirección URL base y los ámbitos se proporcionan al controlador desde la configuración.

GraphAuthorizationMessageHandler.cs:

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

namespace BlazorSample;

public class GraphAuthorizationMessageHandler : AuthorizationMessageHandler
{
    public GraphAuthorizationMessageHandler(IAccessTokenProvider provider,
        NavigationManager navigation, IConfiguration config)
        : base(provider, navigation)
    {
        ConfigureHandler(
            authorizedUrls: [ 
                string.Join("/",
                    config.GetSection("MicrosoftGraph")["BaseUrl"] ??
                        "https://graph.microsoft.com",
                    config.GetSection("MicrosoftGraph")["Version"] ??
                        "v1.0")
            ],
            scopes: config.GetSection("MicrosoftGraph:Scopes")
                        .Get<List<string>>() ?? [ "user.read" ]);
    }
}

Se requiere la barra diagonal final (/) de la dirección URL autorizada. El código anterior compila la siguiente dirección URL autorizada desde la configuración de la aplicación o los valores predeterminados a la siguiente dirección URL autorizada si falta la configuración de la configuración de la aplicación: https://graph.microsoft.com/v1.0/.

En el archivo Program, configura la instancia de HttpClient con nombre para Graph API:

builder.Services.AddTransient<GraphAuthorizationMessageHandler>();

builder.Services.AddHttpClient("GraphAPI",
        client => client.BaseAddress = new Uri(
            string.Join("/",
                builder.Configuration.GetSection("MicrosoftGraph")["BaseUrl"] ??
                    "https://graph.microsoft.com",
                builder.Configuration.GetSection("MicrosoftGraph")["Version"] ??
                    "v1.0",
                string.Empty)))
    .AddHttpMessageHandler<GraphAuthorizationMessageHandler>();

En el ejemplo anterior, el elemento DelegatingHandler de GraphAuthorizationMessageHandler se registra como un servicio transitorio para AddHttpMessageHandler. Se recomienda el registro transitorio para IHttpClientFactory, que administra sus propios ámbitos de inserción de dependencias. Para obtener más información, consulta los siguientes recursos:

Se requiere una barra diagonal final (/) en la dirección base. En el código anterior, el tercer argumento de string.Join es string.Empty para asegurarse de que la barra diagonal final está presente: https://graph.microsoft.com/v1.0/.

Llamada a Graph API desde un componente mediante HttpClient con nombre

La clase UserInfo.cs designa las propiedades de perfil de usuario necesarias con el atributo JsonPropertyNameAttribute y el nombre JSON que usa ME-ID. En el ejemplo siguiente se configuran propiedades para el número de teléfono móvil y la ubicación de la oficina del usuario.

UserInfo.cs:

using System.Text.Json.Serialization;

namespace BlazorSample;

public class UserInfo
{
    [JsonPropertyName("mobilePhone")]
    public string? MobilePhone { get; set; }

    [JsonPropertyName("officeLocation")]
    public string? OfficeLocation { get; set; }
}

En el componente UserData siguiente, se crea un objeto HttpClient para que Graph API emita una solicitud para los datos de perfil del usuario. El recurso me (me) se agrega a la dirección URL base con la versión de la solicitud de Graph API. Los datos JSON devueltos por Graph se deserializan en las propiedades de clase UserInfo. En el ejemplo siguiente, se obtiene el número de teléfono móvil. Puedes agregar un código similar para incluir la ubicación de la oficina del perfil de ME-ID del usuario si lo deseas (userInfo.OfficeLocation). Si se produce un error en la solicitud de token de acceso, se redirige al usuario para que inicie sesión en la aplicación para un nuevo token de acceso.

UserData.razor:

@page "/user-data"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@attribute [Authorize]
@inject IConfiguration Config
@inject IHttpClientFactory ClientFactory

<PageTitle>User Data</PageTitle>

<h1>Microsoft Graph User Data</h1>

@if (!string.IsNullOrEmpty(userInfo?.MobilePhone))
{
    <p>Mobile Phone: @userInfo.MobilePhone</p>
}

@code {
    private UserInfo? userInfo;

    protected override async Task OnInitializedAsync()
    {
        try
        {
            var client = ClientFactory.CreateClient("GraphAPI");

            userInfo = await client.GetFromJsonAsync<UserInfo>("me");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }
    }
}

Agrega un vínculo a la página del componente en el componente NavMenu (Layout/NavMenu.razor):

<div class="nav-item px-3">
    <NavLink class="nav-link" href="user-data">
        <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> User Data
    </NavLink>
</div>

Sugerencia

Para agregar usuarios a una aplicación, consulta la sección Asignación de usuarios a un registro de aplicaciones con o sin roles de aplicación.

En la secuencia siguiente se describe el nuevo flujo de usuario para ámbitos de Graph API:

  1. El nuevo usuario inicia sesión en la aplicación por primera vez.
  2. El usuario da su consentimiento para usar la aplicación en la interfaz de usuario de consentimiento de Azure.
  3. El usuario accede a una página de componentes que solicita datos de Graph API por primera vez.
  4. Se redirige al usuario a la interfaz de usuario de consentimiento de Azure para dar su consentimiento a los ámbitos de Graph API.
  5. Se devuelven los datos del usuario de Graph API.

Si prefieres que el aprovisionamiento de ámbitos (consentimiento para ámbitos de Graph API) tenga lugar al iniciar sesión por primera vez, proporciona los ámbitos a la autenticación MSAL como ámbitos de tokens de acceso predeterminados en el archivo Program:

+ var scopes = builder.Configuration.GetSection("MicrosoftGraph:Scopes")
+     .Get<List<string>>() ?? [ "user.read" ];

builder.Services.AddMsalAuthentication(options =>
{
    builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);

+   foreach (var scope in scopes)
+   {
+       options.ProviderOptions.DefaultAccessTokenScopes.Add(scope);
+   }
});

Importante

Consulta la sección DefaultAccessTokenScopes frente a AdditionalScopesToConsent para obtener una explicación sobre por qué el código anterior usa DefaultAccessTokenScopes para agregar los ámbitos en lugar de AdditionalScopesToConsent.

Cuando se realizan los cambios anteriores en la aplicación, el flujo de usuario adopta la siguiente secuencia:

  1. El nuevo usuario inicia sesión en la aplicación por primera vez.
  2. El usuario da su consentimiento para usar los ámbitos de Graph API y la aplicación en la interfaz de usuario de consentimiento de Azure.
  3. El usuario accede a una página de componentes que solicita datos de Graph API por primera vez.
  4. Se devuelven los datos del usuario de Graph API.

Al realizar pruebas con Graph API de forma local, se recomienda utilizar una nueva sesión de explorador privada o de incógnito para cada prueba a fin de evitar que las cookies persistentes interfieran con las pruebas. Para obtener más información, consulta Protección de una aplicación ASP.NET Core Blazor WebAssembly independiente con Microsoft Entra ID.

Personalización de notificaciones de usuario mediante HttpClient con nombre

En el siguiente ejemplo, la aplicación crea notificaciones de número de teléfono móvil y ubicación de oficina para un usuario a partir de los datos de su perfil de usuario de ME-ID. La aplicación debe tener el ámbito de Graph API User.Read configurado en ME-ID. Las cuentas de usuario de prueba de ME-ID requieren una entrada para el número de teléfono móvil y la ubicación de la oficina, que se pueden agregar a través de Azure Portal a sus perfiles de usuario.

Si aún no has agregado la clase UserInfo a la aplicación siguiendo las instrucciones anteriores de este artículo, agrega la siguiente clase y designa las propiedades de perfil de usuario necesarias con el atributo JsonPropertyNameAttribute y el nombre JSON usado por ME-ID. En el ejemplo siguiente se configuran propiedades para el número de teléfono móvil y la ubicación de la oficina del usuario.

UserInfo.cs:

using System.Text.Json.Serialization;

namespace BlazorSample;

public class UserInfo
{
    [JsonPropertyName("mobilePhone")]
    public string? MobilePhone { get; set; }

    [JsonPropertyName("officeLocation")]
    public string? OfficeLocation { get; set; }
}

En la siguiente fábrica de cuentas de usuario personalizada:

  • Se incluye ILogger (logger) para mayor comodidad en caso de que desee registrar información o errores en el método CreateUserAsync.
  • En caso de que se produzca AccessTokenNotAvailableException, se redirige al usuario al proveedor de identity para iniciar sesión en su cuenta. Se pueden realizar acciones adicionales o diferentes cuando se produce un error al solicitar un token de acceso. Por ejemplo, la aplicación puede registrar AccessTokenNotAvailableException y crear una incidencia de soporte técnico para una investigación más detallada.
  • El RemoteUserAccount del marco representa la cuenta del usuario. Si la aplicación requiere una clase de cuenta de usuario personalizada que extienda RemoteUserAccount, intercambie la clase de cuenta de usuario personalizada con RemoteUserAccount en el código siguiente.

CustomAccountFactory.cs:

using System.Net.Http.Json;
using System.Security.Claims;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomAccountFactory(IAccessTokenProviderAccessor accessor,
        IHttpClientFactory clientFactory,
        ILogger<CustomAccountFactory> logger)
    : AccountClaimsPrincipalFactory<RemoteUserAccount>(accessor)
{
    private readonly ILogger<CustomAccountFactory> logger = logger;
    private readonly IHttpClientFactory clientFactory = clientFactory;

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        RemoteUserAccount account,
        RemoteAuthenticationUserOptions options)
    {
        var initialUser = await base.CreateUserAsync(account, options);

        if (initialUser.Identity is not null && 
            initialUser.Identity.IsAuthenticated)
        {
            var userIdentity = initialUser.Identity as ClaimsIdentity;

            if (userIdentity is not null)
            {
                try
                {
                    var client = clientFactory.CreateClient("GraphAPI");

                    var userInfo = await client.GetFromJsonAsync<UserInfo>("me");

                    if (userInfo is not null)
                    {
                        userIdentity.AddClaim(new Claim("mobilephone",
                            userInfo.MobilePhone ?? "(000) 000-0000"));
                        userIdentity.AddClaim(new Claim("officelocation",
                            userInfo.OfficeLocation ?? "Not set"));
                    }
                }
                catch (AccessTokenNotAvailableException exception)
                {
                    exception.Redirect();
                }
            }
        }

        return initialUser;
    }
}

La autenticación de MSAL está configurada para usar la fábrica de cuentas de usuario personalizada. Para empezar, confirma que el archivo Program usa el espacio de nombres Microsoft.AspNetCore.Components.WebAssembly.Authentication:

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

En el archivo Program, busca la llamada al método de extensión AddMsalAuthentication. Actualiza el código a lo siguiente, que incluye una llamada a AddAccountClaimsPrincipalFactory que agrega una fábrica de entidades de seguridad de notificaciones de cuenta con CustomAccountFactory.

Si la aplicación usa una clase de cuenta de usuario personalizada que extiende RemoteUserAccount, intercambie la clase de cuenta de usuario personalizada de la aplicación para RemoteUserAccount en el código siguiente.

builder.Services.AddMsalAuthentication<RemoteAuthenticationState, 
    RemoteUserAccount>(options =>
    {
        builder.Configuration.Bind("AzureAd", 
            options.ProviderOptions.Authentication);
    })
    .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, RemoteUserAccount, 
        CustomAccountFactory>();

El ejemplo anterior corresponde a una aplicación que usa autenticación de ME-ID con MSAL. Existen patrones similares para la autenticación de API y OIDC. Para obtener más información, consulta los ejemplos de la sección Personalización del usuario con una notificación de carga del artículo Escenarios de seguridad adicionales de ASP.NET Core Blazor WebAssembly.

Puedes usar el siguiente componente UserClaims para estudiar las notificaciones del usuario después de que el usuario se autentique con ME-ID:

UserClaims.razor:

@page "/user-claims"
@using System.Security.Claims
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
@inject AuthenticationStateProvider AuthenticationStateProvider

<h1>User Claims</h1>

@if (claims.Any())
{
    <ul>
        @foreach (var claim in claims)
        {
            <li>@claim.Type: @claim.Value</li>
        }
    </ul>
}
else
{
    <p>No claims found.</p>
}

@code {
    private IEnumerable<Claim> claims = Enumerable.Empty<Claim>();

    protected override async Task OnInitializedAsync()
    {
        var authState = await AuthenticationStateProvider
            .GetAuthenticationStateAsync();
        var user = authState.User;

        claims = user.Claims;
    }
}

Agrega un vínculo a la página del componente en el componente NavMenu (Layout/NavMenu.razor):

<div class="nav-item px-3">
    <NavLink class="nav-link" href="user-claims">
        <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> User Claims
    </NavLink>
</div>

Al realizar pruebas con Graph API de forma local, se recomienda utilizar una nueva sesión de explorador privada o de incógnito para cada prueba a fin de evitar que las cookies persistentes interfieran con las pruebas. Para obtener más información, consulta Protección de una aplicación ASP.NET Core Blazor WebAssembly independiente con Microsoft Entra ID.

Asignación de usuarios a un registro de aplicaciones con o sin roles de aplicación

Puedes agregar usuarios a un registro de aplicaciones y asignar roles a estos mediante los pasos siguientes en Azure Portal.

Para agregar un usuario, selecciona Usuarios en el área ME-ID de Azure Portal:

  1. Selecciona Nuevo usuario>Crear nuevo usuario.
  2. Usa la plantilla Crear usuario.
  3. Proporciona la información del usuario en el área Identity.
  4. Puedes generar o asignar una contraseña inicial que el usuario cambiará al iniciar sesión por primera vez. Si usas la contraseña generada por el portal, anótala ahora.
  5. Selecciona Crear para crear el usuario. Cuando se cierre la interfaz Crear un usuario nuevo, selecciona Actualizar para actualizar la lista de usuarios y mostrar el usuario nuevo.
  6. En los ejemplos de este artículo, asigne un número de teléfono móvil al nuevo usuario; para ello, selecciona su nombre en la lista de usuarios, elige Propiedades y edita la información de contacto para proporcionar un número de teléfono móvil.

Para asignar usuarios a la aplicación sin roles de aplicación:

  1. En el área ME-ID de Azure Portal, abre Aplicaciones empresariales.
  2. Selecciona la aplicación en la lista.
  3. Selecciona Usuarios y grupos
  4. Seleccione Agregar usuario o grupo.
  5. Selecciona un usuario.
  6. Selecciona el botón Asignar.

Para asignar usuarios a la aplicación con roles de aplicación:

  1. Agrega roles al registro de la aplicación en Azure Portal de acuerdo con las instrucciones de ASP.NET Core Blazor WebAssembly con grupos y roles de Microsoft Entra ID.
  2. En el área ME-ID de Azure Portal, abre Aplicaciones empresariales.
  3. Selecciona la aplicación en la lista.
  4. Selecciona Usuarios y grupos
  5. Selecciona Agregar usuario o grupo.
  6. Selecciona un usuario y su rol para acceder a la aplicación. Para asignar varios roles a un usuario, se repite el proceso de agregar el usuario a la aplicación hasta que se hayan asignado todos los roles para este. Los usuarios con varios roles se enumeran una vez por cada rol asignado en la lista Usuarios y grupos de usuarios de la aplicación.
  7. Selecciona el botón Asignar.

Diferencias entre DefaultAccessTokenScopes y AdditionalScopesToConsent

Los ejemplos de este artículo aprovisionan ámbitos de Graph API con DefaultAccessTokenScopes, no AdditionalScopesToConsent.

AdditionalScopesToConsent no se usa porque no puede aprovisionar ámbitos de Graph API para los usuarios cuando inician sesión en la aplicación por primera vez con MSAL a través de la interfaz de usuario de consentimiento de Azure. Cuando el usuario intenta acceder a Graph API por primera vez con el SDK de Graph, se encuentra con una excepción:

Microsoft.Graph.Models.ODataErrors.ODataError: Access token is empty.

Después de que un usuario aprovisione los ámbitos de Graph API proporcionados a través de DefaultAccessTokenScopes, la aplicación puede usar AdditionalScopesToConsent para un inicio de sesión de usuario posterior. Sin embargo, cambiar el código de aplicación no tiene sentido para una aplicación de producción que requiera la adición periódica de nuevos usuarios con ámbitos de Graph delegados o la adición de nuevos ámbitos delegados de Graph API a la aplicación.

La explicación anterior sobre cómo aprovisionar ámbitos para el acceso a Graph API cuando el usuario inicie sesión en la aplicación por primera vez solo es válida para:

  • Aplicaciones que adoptan el SDK de Graph.
  • Aplicaciones que usan un HttpClient con nombre para el acceso de Graph API que pide a los usuarios su consentimiento de los ámbitos de Graph al iniciar sesión por primera vez en la aplicación.

Cuando se usa un HttpClient con nombre que no pide a los usuarios su consentimiento de los ámbitos de Graph en su primer inicio de sesión, se redirige a los usuarios a la interfaz de usuario de consentimiento de Azure para el consentimiento de los ámbitos de Graph API cuando soliciten acceso a Graph API por primera vez a través del DelegatingHandler del HttpClient preconfigurado con nombre. Cuando los ámbitos de Graph no se consientan inicialmente con el enfoque de HttpClient con nombre, la aplicación no llama a DefaultAccessTokenScopes ni a AdditionalScopesToConsent. Para obtener más información, consulta el apartado sobre con nombre HttpClient en este artículo.

Soluciones Blazor WebAssembly hospedadas

Los ejemplos de este artículo se refieren al uso del SDK de Graph o un HttpClient con nombre con Graph API directamente desde una aplicación Blazor WebAssembly independiente o directamente desde la aplicación Client de una Blazor WebAssemblysolución hospedada. Un escenario adicional que no se trata en este artículo es que una aplicación Client de una solución hospedada llame a la aplicación Server de la solución a través de la API web y, a continuación, la aplicación Server use el SDK de Graph o Graph API para llamar a Microsoft Graph y devolver datos a la aplicación Client. Aunque se trata de un enfoque compatible, no se trata en este artículo. Si deseas adoptar este enfoque:

  • Sigue las instrucciones de Llamada a una API web desde una aplicación de ASP.NET Core Blazor para los aspectos de la API web sobre la emisión de solicitudes a la aplicación Server desde la aplicación Client y la devolución de datos a la aplicación Client.
  • Sigue las instrucciones de la documentación principal de Microsoft Graph para usar el SDK de Graph con una aplicación de ASP.NET Core típica, que en este escenario es la aplicación Server de la solución. Si usas la plantilla de proyecto Blazor WebAssembly para crear la solución Blazor WebAssembly hospedada (ASP.NET Core hospedada/-h|--hosted) con autorización organizativa (organización única/SingleOrg o varias organizaciones/MultiOrg) y la opción Microsoft Graph (Plataforma de identity de Microsoft>Servicios conectados>Agregar permisos de Microsoft Graph en Visual Studio o la opción --calls-graph con el comando dotnet new de la CLI de .NET), la aplicación Server de la solución se configura para usar el SDK de Graph cuando se crea la solución a partir de la plantilla de proyecto.

Recursos adicionales

Instrucciones generales

  • Documentación de Microsoft Graph
  • Aplicación de ejemplo de Microsoft GraphBlazor WebAssembly: en este ejemplo se muestra cómo usar el SDK de .NET de Microsoft Graph para acceder a datos de Office 365 desde aplicaciones Blazor WebAssembly.
  • Tutorial de compilación de aplicaciones .NET con Microsoft Graph y Aplicación de ASP.NET Core de ejemplo de Microsoft Graph: estos recursos son más adecuados para las soluciones hospedadasBlazor WebAssembly, donde la aplicación Server está configurada para acceder a Microsoft Graph como una aplicación ASP.NET Core típica en nombre de la aplicación Client. La aplicación Client usa la API web para realizar solicitudes a la aplicación Server para los datos de Graph. Aunque estos recursos no se aplican directamente a la llamada a Graph desde aplicaciones del lado clienteBlazor WebAssembly, la configuración de la aplicación de ME-ID y los procedimientos de codificación de Microsoft Graph en los recursos vinculados son pertinentes para las aplicaciones Blazor WebAssembly independientes y deben consultarse para ejecutar procedimientos recomendados generales.

Guía de seguridad