0

I have written this code - this API is supposed to return user's information list of my Azure AD app (has delegated permission - User.ReadBasic.All). I have done settings for

"TenantId": "","ClientId": "" & "ClientSecret": "" 

in appsettings.json file. But I am getting an "401 Unauthorized" error even if I sent the request with token from Postman.

namespace WebApplication4.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class UsersController : ControllerBase
    {
        private readonly ITokenAcquisition _tokenAcquisition;

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

        [HttpGet]
        public async Task<IActionResult> GetUsers()
        {
            try
            {
                // Acquire the access token for Microsoft Graph
                var token = await _tokenAcquisition.GetAccessTokenForUserAsync(new[] { "User.ReadBasic.All", "User.Read" });

                // Create the GraphServiceClient with an authentication provider
                var graphClient = new GraphServiceClient(new AuthProvider(token));

                var usersRequestBuilder = graphClient.Users;

                var users = await usersRequestBuilder
                    .GetAsync(requestConfiguration: request =>
                    {
                        request.QueryParameters.Select = new[] { "displayName", "userType", "mail" };
                    });

                return Ok(users);
            }
            catch (Exception ex)
            {
                return StatusCode(StatusCodes.Status500InternalServerError, "An error occurred while fetching users.");
            }
        }

        private class AuthProvider : IAuthenticationProvider
        {
            private readonly string _token;

            public AuthProvider(string token)
            {
                _token = token;
            }

            public async Task AuthenticateRequestAsync(RequestInformation request, Dictionary<string, object>? additionalAuthenticationContext = null, CancellationToken cancellationToken = default)
            {
                // Set the Authorization header
                request.Headers.Add("Authorization", $"Bearer {_token}");
                await Task.CompletedTask;
            }
        }
    }
}

I am attaching the image - This is how I've added token in request header. enter image description here

I am also getting the correct token. But when I am sending the get request I am getting 401 Unauthorized. Instead I want the user info.

What am I doing wrong?

2
  • using on behalf of flow into your web api project, you can follow this case stackoverflow.com/a/76457022/14574199 Commented Aug 20, 2024 at 8:01
  • 401 error should occur when unauthorized, so that I'm afraid that you don't have auth scheme like builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd")); Commented Aug 20, 2024 at 8:17

2 Answers 2

0

The error you are facing is due to not configuring the Scope in the appsettings.json.

I added the Scope as access_as_user in the Azuree AD app as shown below.

enter image description here

appsettings.json :

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "<tenantID>",
    "ClientId": "<clientID>",
    "Scopes": "access_as_user"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "MicrosoftGraph": {
    "BaseUrl": "https://graph.microsoft.com/v1.0"
  }
}

Below is the complete ASP.NET Web API code to authenticate and get a list of user information from an Azure AD app by calling the Microsoft Graph API.

UsersController.cs :

using Microsoft.AspNetCore.Mvc;
using Microsoft.Graph;
using Microsoft.Identity.Web;
using System.Net.Http.Headers;

namespace WebApplication5.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class UsersController : ControllerBase
    {
        private readonly ITokenAcquisition _tokenAcquisition;
        private readonly ILogger<UsersController> _logger;

        public UsersController(ITokenAcquisition tokenAcquisition, ILogger<UsersController> logger)
        {
            _tokenAcquisition = tokenAcquisition;
            _logger = logger;
        }

        [HttpGet]
        public async Task<IActionResult> GetUsers()
        {
            try
            {
                var token = await _tokenAcquisition.GetAccessTokenForUserAsync(new[] { "User.ReadBasic.All", "User.Read" });
                var graphClient = new GraphServiceClient(new AuthProvider(token));

                var users = await graphClient.Users
                    .Request()
                    .Select(u => new { u.DisplayName, u.UserType, u.Mail })
                    .GetAsync();

                return Ok(users);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "An error occurred while fetching users.");
                return StatusCode(StatusCodes.Status500InternalServerError, "An error occurred while fetching users.");
            }
        }

        private class AuthProvider : IAuthenticationProvider
        {
            private readonly string _token;
            public AuthProvider(string token)
            {
                _token = token;
            }
            public async Task AuthenticateRequestAsync(HttpRequestMessage request)
            {
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token);
                await Task.CompletedTask;
            }
        }
    }
}

Program.cs :

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
        .EnableTokenAcquisitionToCallDownstreamApi()
            .AddMicrosoftGraph(builder.Configuration.GetSection("MicrosoftGraph"))
            .AddInMemoryTokenCaches();

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();

I granted admin consent for User.ReadBasic.All and User.Read as shown below.

enter image description here

Postman Output :

In Postman, I configured the Authorization header with the following values.

enter image description here

Make sure to add the Scope as below in the Postman.

Scope : api://<clientID>/access_as_user

enter image description here

I added the below callback URL from Postman to the Azure AD app's Authentication settings as a Web URI.

https://oauth.pstmn.io/v1/callback

enter image description here

I successfully retrieved the user information list from the Azure AD app by calling the Microsoft Graph API in Postman, as shown below.

enter image description here

Sign up to request clarification or add additional context in comments.

Comments

0

We are now trying to create a web api which could return the Users' information list of your Azure AD app. Therefore, we need to call the API first and then the API itself should call Graph API to get the user information you required.

In the meantime, you mentioned that you've consent the Delegated API permissions, using Delegated API permission requires us to give authorized to our API(using Azure AD on-behalf-flow, requiring to secure the API with AAD), while Application permission doesn't require(using client credential flow).

If we hope to use Application API permission instead, we could use codes below in our API.

using Microsoft.Graph;
using Azure.Identity;

var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "tenantId";
var clientId = "clientId";
var clientSecret = "clientSecret";
var clientSecretCredential = new ClientSecretCredential(
                tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var res = await graphClient.Users.GetAsync();

If we want to secure the API through AAD and then using on-behalf-flow and Delegated permission, we can follow this tutorial first to integrate AAD into the API project. Then we need to generate an access token which containing the correct scope to call our API. The scope value should be the api value(api://client_id/api_permission_name) what we exposed in Azure just now. In my scenario, I used request below to get the auth code, and then post with this auth code to get the access token. Decoding the access token in https://jwt.io/, I can see it contains the scope I exposed. I used auth code flow, and the request below will give us the auth code in the callback request. enter image description here

https://login.microsoftonline.com/tenant_id/oauth2/v2.0/authorize?
client_id=client_id
&response_type=code
&redirect_uri=redirect_url_defined_id_AAD
&response_mode=query
&scope=user.read
&state=12345

enter image description here enter image description here

With this token, we shall be able to hit the Web api. We can create a new web api project, and choose authentication type as Micrsoft Identity platform for test purpose. Don't forget the modify the appsettings.json.

enter image description here

Here's my appsettings.json, since we need to use ITokenAcquisition to generate access token, we need to add client secret as well.

"AzureAd": {
  "Instance": "https://login.microsoftonline.com/",
  "Domain": "tenant_id",
  "TenantId": "tenant_id",
  "ClientId": "client_id",
  "ClientSecret": "client_id",

  "Scopes": "Tiny.Greet",//I exposed Tiny.Greet api permission
  "CallbackPath": "/signin-oidc"
},

Then in Program.cs, injecting ITokenAcquision service.

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi().AddInMemoryTokenCaches();

And in the Controller, just do what you had done. It worked for me. Adding the access token into Request header will help you call MS graph API.

enter image description here

1 Comment

About how to expose an API in Azure portal, refer to the tutorial I mentioned and you will get what @Dasari Kamali showed in his answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.