Llamando a Graph API a través de ViewModel en la aplicación web MVC

Estoy tratando de usar Graph API para crear mi propia sección de “Perfil de usuario” de la barra de navegación de mi aplicación web. Para hacer esto, tengo una llamada AJAX a una acción GetUser de mi UserProfile Controller:

$.ajax({ type: "GET", url: "@Url.Action("GetUser", "UserProfile", null)", dataType: "json", success: function (data, status, xhr) { console.log("in AJAX"); $(".img-circle, .user-image").attr("src", data.Picture); $("#user-menu-expanded").text(data.User.DisplayName + " - " + data.User.JobTitle); $("#user-menu-spinner").remove(); console.log(data); }, error: function (ex) { console.log(ex); } }); 

El controlador devuelve mi UserProfileViewModel como un Json que uso para reemplazar los elementos anteriores como se muestra en mi función de éxito AJAX.

Controlador de perfil de usuario:

  public JsonResult GetUser() { var model = new UserProfileViewModel(); return Json(model, JsonRequestBehavior.AllowGet); } 

Mi UserProfileViewModel tiene este aspecto:

  public UserProfileViewModel() { var graphClient = GetAuthGraphClient(); GetPicture(graphClient); GetUserProfile(graphClient); } public GraphServiceClient GetAuthGraphClient() { string graphResourceID = "https://graph.microsoft.com/"; return new GraphServiceClient( new DelegateAuthenticationProvider((requestMessage) => { string accessToken = GetTokenForApplication(graphResourceID); requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken); return Task.FromResult(0); } )); } public string GetTokenForApplication(string graphResourceID) { string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value; string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value; string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value; string authority = "https://login.microsoftonline.com/" + tenantID; try { ClientCredential clientcred = new ClientCredential(clientId, appKey); // initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's database AuthenticationContext authenticationContext = new AuthenticationContext(authority); var token = authenticationContext.AcquireTokenAsync(graphResourceID, clientcred).Result.AccessToken; return token; } catch (Exception e) { // Capture error for handling outside of catch block ErrorMessage = e.Message; return null; } } public void GetPicture(GraphServiceClient graphClient) { Stream photo = Task.Run(async () => { return await graphClient.Me.Photo.Content.Request().GetAsync(); }).Result; using (var memoryStream = new MemoryStream()) { photo.CopyTo(memoryStream); var base64pic = Convert.ToBase64String(memoryStream.ToArray()); this.Picture = "data:image;base64," + base64pic; HttpContext.Current.Cache.Add("Pic", this.Picture, null, DateTime.Now.AddHours(5), Cache.NoSlidingExpiration, CacheItemPriority.AboveNormal, null); } } public void GetUserProfile(GraphServiceClient graphClient) { this.User = Task.Run(async () => { return await graphClient.Me.Request().GetAsync(); }).Result; } 

Recibo con éxito un token de acceso, sin embargo, mi llamada AJAX no está devolviendo ningún dato.
Token de acceso desde el registro de la consola de registro de IIS

Tengo dos preguntas (posiblemente 3):

  1. ¿Qué estoy haciendo mal?
  2. ¿Es posible usar el token de acceso de mi Startup.Auth para crear un cliente gráfico autenticado? Si es así, ¿cómo voy a hacer eso?

      // This is the resource ID of the AAD Graph API. We'll need this to request a token to call the Graph API. string graphResourceId = "https://graph.microsoft.com"; //https://graph.windows.net public void ConfigureAuth(IAppBuilder app) { ApplicationDbContext db = new ApplicationDbContext(); app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType); app.UseKentorOwinCookieSaver(); app.UseCookieAuthentication(new CookieAuthenticationOptions()); app.UseOpenIdConnectAuthentication( new OpenIdConnectAuthenticationOptions { ClientId = clientId, Authority = Authority, PostLogoutRedirectUri = postLogoutRedirectUri, Notifications = new OpenIdConnectAuthenticationNotifications() { // If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away. AuthorizationCodeReceived = (context) => { var code = context.Code; ClientCredential credential = new ClientCredential(clientId, appKey); string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value; AuthenticationContext authContext = new AuthenticationContext(Authority, new ADALTokenCache(signedInUserID)); AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode( code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId); HttpContext.Current.Cache.Add("Token", result.AccessToken, null, DateTime.Now.AddHours(5), Cache.NoSlidingExpiration, CacheItemPriority.AboveNormal, null); return Task.FromResult(0); } } }); } } 

Código actualizado por comentario abajo

  public string GetTokenForApplication(string graphResourceID) { string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value; string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value; string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value; string authority = "https://login.microsoftonline.com/" + tenantID; try { // get a token for the Graph without triggering any user interaction (from the cache, via multi-resource refresh token, etc) ClientCredential clientcred = new ClientCredential(clientId, appKey); // initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's database AuthenticationContext authenticationContext = new AuthenticationContext(Startup.Authority, new ADALTokenCache(userObjectID)); var result = authenticationContext.AcquireTokenSilent(graphResourceID, clientcred, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId)); return result.AccessToken; } catch (Exception e) { // Capture error for handling outside of catch block ErrorMessage = e.Message; return null; } } 

Actualización 2: La solución .. Tipo de

Gracias a @Fei Xue, descubrí el problema … más o menos. Esto soluciona mi problema cuando se ejecuta localmente, pero aún no consigo adquirir el token de forma silenciosa al publicar en mi aplicación de escenario. Cuando creé la aplicación por primera vez, incluí la autenticación de Trabajo / Escuela que era Azure AD. Esto creó un contexto de DB local que usó para el caché de token ADAL. Al desarrollar la aplicación, creé otro contexto de base de datos para la base de datos SQL de Azure que creé para la aplicación. Tuve que actualizar mi AdalTokenCache.cs para reflejar el contexto de base de datos de mi aplicación y el nuevo modelo. Actualicé la línea:

 private ApplicationDbContext db = new ApplicationDbContext(); 

con mi propio contexto y actualicé el modelo UserTokenCache al modelo UserTokenCache de mi nuevo contexto. En este caso he cambiado:

 private UserTokenCache Cache; 

a:

 private UserTokenCach Cache; 

Luego actualicé el rest de la CS para que coincida con el UserTokenCach del contexto de base de datos de la aplicación.

Luego simplemente utilicé el método AcquireToken que vino OOB en el controlador UserProfile para obtener un token. Así es como se veía (Nota: También actualicé las cadenas en mi startup.auth de privado a público para poder usarlas en mi modelo de vista):

  public string GetTokenForApplication(string graphResourceID) { string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value; string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value; string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value; string authority = "https://login.microsoftonline.com/" + tenantID; try { // get a token for the Graph without triggering any user interaction (from the cache, via multi-resource refresh token, etc) ClientCredential clientcred = new ClientCredential(Startup.clientId, Startup.appKey); // initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's database AuthenticationContext authenticationContext = new AuthenticationContext(Startup.Authority, new ADALTokenCache(signedInUserID)); var result = authenticationContext.AcquireTokenSilent(graphResourceID, clientcred, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId)); return result.AccessToken; } catch (Exception e) { // Capture error for handling outside of catch block ErrorMessage = e.Message; return null; } } 

Voy a actualizar a medida que juego un poco más.

Hay dos tipos de token de acceso emitidos por Azure Active Directory.

El primero es el token de delegado que se utiliza para delegar al usuario para operar los recursos del usuario.

Y el otro es el token de aplicación que generalmente se usa para realizar la operación para el recurso de toda la organización y no hay un contexto de usuario en este token. Así que no deberíamos usar este token para realizar el recurso como me que requería el contexto del usuario.

El código en la publicación es adquirir el token de acceso usando el flujo de credenciales del cliente que es el token de la aplicación . Por lo tanto, obtendrá un error cuando obtenga el usuario o la imagen utilizando este tipo de token en función del contexto del usuario.

En este escenario, debe adquirir el token de acceso utilizando el evento AuthorizationCodeReceived como usted publica. Este evento utiliza el flujo de concesión de código de autorización para adquirir el token de delegado para el usuario. Luego, en el controlador, puede obtener el token utilizando el método AcquireTokenSilentAsync que obtendrá el token de acceso de la captura.

El ejemplo de código a continuación es muy útil para el escenario que llama a Microsoft Graph en una aplicación web para delegar al usuario de inicio de sesión:

directorio-activo-dotnet-graphapi-web