adding call api to web app - mattchenderson/microsoft-identity-web GitHub Wiki

You have a web app that signs-in users and now you want to call a web API...

Initial state

Your startup.cs has the following content:

public void ConfigureServices(IServiceCollection services)
{
 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
   .AddMicrosoftIdentityWebApi(Configuration, "AzureAd");
 /// 
}

The appsettings.json is like the following:

    {
     "AzureAd": {
        "Instance": "https://login.microsoftonline.com/",
        "Domain": "msidentitysamplestesting.onmicrosoft.com",
        "TenantId": "7f58f645-c190-4ce5-9de4-e2b7acd2a6ab",
        "ClientId": "86699d80-dd21-476a-bcd1-7c1a3d471f75",
        "CallbackPath": "/signin-oidc",
        "SignedOutCallbackPath ": "/signout-callback-oidc",

        // To call an API
        // "ClientSecret": "[Copy the client secret added to the app from the Azure portal]",
        //"ClientCertificates": [
        //    {
        //        "SourceType": "",
        //        "Container": "",
        //        "ReferenceOrValue": ""
        //    }
        //]
    }
   }

And you now want call a web API. Here is how to proceed.

Update the startup.cs to add support for calling a Web API

Add the following methods after AddMicrosoftIdentityWebApi:

   .EnableTokenAcquisitionToCallDownstreamApi()
       .AddInMemoryTokenCaches();

producing:

public void ConfigureServices(IServiceCollection services)
{
 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
   .AddMicrosoftIdentityWebApi(Configuration, "AzureAd")
       .EnableTokenAcquisitionToCallDownstreamApi()
           .AddInMemoryTokenCaches();
}

Update the appsettings.json to add a client secret or certificate

In the AzureAd section add:

        "ClientSecret": "xxx-ttt-zzzz"

Optionally update appsettings.json to add configuration about the called API

In the AzureAd section add:

    "TodoList": {
        /*
      TodoListScope is the scope of the Web API you want to call. This can be: "api://a4c2469b-cf84-4145-8f5f-cb7bacf814bc/access_as_user",
      - a scope for a V2 application (for instance api://b3682cc7-8b30-4bd2-aaba-080c6bf0fd31/access_as_user)
      - a scope corresponding to a V1 application (for instance <GUID>/user_impersonation, where  <GUID> is the
        clientId of a V1 application, created in the https://portal.azure.com portal.
    */
        //"TodoListScope": "api://a4c2469b-cf84-4145-8f5f-cb7bacf814bc/.default",
        "TodoListScope": "api://a4c2469b-cf84-4145-8f5f-cb7bacf814bc/access_as_user",
        "TodoListBaseAddress": "https://localhost:44351"

    },

In the controller acquire a token (and call the API)

For the moment your controller is like the following:

[Authorize]
[Route("api/[controller]")]
public class TodoListController : Controller
{
 // The web API will only accept tokens 1) for users, and 2) having the access_as_user scope for this API
 static readonly string[] scopeRequiredByApi = new string[] { "access_as_user" };

 public TodoListController()
 {
 }

 // GET: api/values
 [HttpGet]
 public async Task<IEnumerable<Todo>> Get()
 {
  HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
 }

If you want to call graph (scopes "user.read"), you need to:

  • Inject a ITokenAcquisition service in the constructor of the controller (or the page)
  • In the controller action, use the ITokenAcquisition to acquire a token for the specified scope.
  • Add [AuthorizeForScopes(Scopes = new string[]{"user.read" })] on the controller action, or the Razor page. This is to automatically handle conditional access and incremental consent. For details see Managing incremental consent and conditional access
[Authorize]
[Route("api/[controller]")]
[AuthorizeForScopes(Scopes = new string[]{"user.read" })]
public class TodoListController : Controller
{
 // The web API will only accept tokens 1) for users, and 2) having the access_as_user scope for this API
 static readonly string[] scopeRequiredByApi = new string[] { "access_as_user" };

 private readonly ITokenAcquisition _tokenAcquisition;

 public TodoListController(ITokenAcquisition tokenAcquisition)
 {
  _tokenAcquisition = tokenAcquisition;
 }

 // GET: api/values
 [HttpGet]
 public async Task<IEnumerable<Todo>> Get()
 {
  HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);

  var token2 = await _tokenAcquisition.GetAccessTokenForUserAsync(new string[] { "user.read" });
 }

Optionally, provide a cancellation token

 // GET: api/values
 [HttpGet]
 public async Task<IEnumerable<Todo>> Get()
 {
  HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);

  cancellationToken = GetTheCancellationToken(); // for instance from some context.

  TokenAcquisitionOptions options = new TokenAcquisitionOptions {CancellationToken = cancellationToken}
  var token2 = await _tokenAcquisition.GetAccessTokenForUserAsync(new string[] { "user.read" }, tokenAcquisitionOptions: options );
 }
⚠️ **GitHub.com Fallback** ⚠️