Search code examples
asp.net-coreazure-active-directorymicrosoft-graph-apiasp.net-core-webapi

How to call Microsoft Graph API from ASP.NET Core Web API that is called by SPA


I have an Angular SPA and an ASP.NET Core Web API backend. I have an Azure application registration which the SPA is configured to use, and I can make Graph API calls from the SPA directly using @azure/msal-angular and @microsoft/microsoft-graph-client.

What I want to do now is have the SPA call the Web API, and for the Web API to make the Graph API calls on behalf of the logged in user.

I've waded through reams of documentation but I've not been able to find a complete tutorial for this scenario or find a definitive answer to a key question.

Which is, do I need a separate app registration for the SPA and the ASP.NET Core Web API, or do I need a single application registration with two platforms configured?

UPDATE

With the help of the responses in this thread I have this working.

One difference from the configuration proposed by @Rukmini is that in the configuration of the SPA app registration I do not have the web api added to API Permissions (it is not listed under My APIs). Instead in the configuration of the web API app registration, under Expose an API -> Authorized client applications I have the SPA app registration selected there. (I don't know what the difference is between these two approaches but it is working).

enter image description here

In the ASP web api my appsettings.json is trivial

    "AzureAd": {
      "Instance": "https://login.microsoftonline.com/",
      "ClientId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
      "TenantId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
      "ClientSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "Scopes": "Api.Access"
    }

No section is required to configure the Graph API, the defaults work.

Program.cs is also straightforward

    // Using Microsoft.Identity.Web.MicrosoftGraph 3.5.0
    builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
       .AddMicrosoftIdentityWebApi(builder.Configuration)
       .EnableTokenAcquisitionToCallDownstreamApi()
       .AddMicrosoftGraph()
       .AddDistributedTokenCaches();

Default configuration all works.

Controller is as follows.

    [Route("api/[controller]")]
    [ApiController]
    [Authorize]
    public class GraphTestController : ControllerBase
    {
       public readonly GraphServiceClient _graphClient;
 
       public GraphTestController(GraphServiceClient graphClient)
       {
          _graphClient = graphClient;
       }
 
       [HttpGet("invite-user")]
       public async Task<ActionResult> InviteUser(string email, string displayName)
       {
          var invite = new Invitation()
          {
             InvitedUserDisplayName = displayName,
             InvitedUserEmailAddress = email,
             InviteRedirectUrl = "https://www.google.com",
             SendInvitationMessage = true
          };
          Invitation response = await _graphClient.Invitations.PostAsync(invite);
          return Ok(response);
       }
    }

To send invitations the User.Invite.All delegated scope is added to both SPA and Web API app registrations, and the user must also have sufficient permissions.

Thanks everyone for the help!


Solution

  • I agree with @Jack A, You need two separate app registrations in Microsoft Entra ID, one for the Angular SPA (client) and one for the ASP.NET Core Web API (backend).

    • SPA App Registration: This is for the Angular application, which handles user authentication and retrieves an access token (via MSAL) to interact with the Web API. The SPA uses this access token to authenticate its requests to the Web API.
    • Web API App Registration: This is for the backend (ASP.NET Core Web API). It verifies the incoming requests from the SPA, and once the user is authenticated, the Web API can use the granted permissions to call the Microsoft Graph API on behalf of the user.

    User Authentication in SPA -> SPA Calls the Web API -> Web API Validates the Token -> Web API Calls Graph API

    Create a ASP.NET Core Web API application and expose an API add scope:

    enter image description here

    And add Microsoft Graph API permissions in ASP.NET Core Web API application:

    enter image description here

    In Angular SPA Microsoft Entra ID application, grant API permission of the ASP.NET Core Web API:

    enter image description here

    For sample, I generated access token using Angular SPA:

    https://login.microsoftonline.com/tenantId/oauth2/v2.0/token
    
    grant_type:authorization_code
    client_id:ClientSPAappId
    scope: api://xxxxxxxx/webapi.access
    code: xxx
    code_verifier:S256
    redirect_uri: https://jwt.ms
    

    enter image description here

    The above generated access token can be used to call web API.

    Now generate on-behalf-of flow access token to call Microsoft Graph:

    https://login.microsoftonline.com/tenantID/oauth2/v2.0/token
    
    client_id:WebAPIappID
    client_secret:WebAPIappSecret
    scope: https://graph.microsoft.com/.default
    grant_type: urn:ietf:params:oauth:grant-type:jwt-bearer
    assertion:<paste_token_from_above_step>
    requested_token_use:on_behalf_of
    

    enter image description here

    You can use this access token to call Microsoft Graph API:

    https://graph.microsoft.com/v1.0/users
    

    enter image description here

    For sample, I generated tokens via Rest API this can be done via code too.

    Reference:

    Microsoft identity platform and OAuth2.0 On-Behalf-Of flow - | Microsoft