1

I have a Blazor server application that uses Azure AD login. This works fine, but when setting [Authorize] on my API controller, the controller returns 401 unauthorized event though the Bearer token is passed.

I can see the token being passed in the API, so I am kind of stuck. Problem with config or some code error? Any help would be appreciated.

Azure config for Blazor project:

Azure setup for Blazor (server side project):

Azure setup for Blazor (server side project)

Auth setup for Blazor: Auth setup for Blazor

Secret added for Blazor: Secret added for Blazor

Api permissions for Blazor: Api permissions for Blazor

Azure config for API project:

Azure setup for API: Azure setup for API

Auth setup for API: Auth setup for API

API permissions for API (probably too many, found a guide that suggested adding more): API permissions for API (probably too many, found a guide that suggested adding more)

Exposed an API for API (type-o in admin.accsess): Exposed an API for API (type-o in admin.accsess)

Code for blazor project:

appsettings.json (new secret will be made and moved to keyvault as soon as this works): `

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "ClientId": "3c                           8c",
    "TenantId": "eb                            a0",
    "Scopes": "api://3c                        8c/admin.accsess" 
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

`

Startup.cs `

public void ConfigureServices(IServiceCollection services)
{
    services.AddMicrosoftIdentityWebAppAuthentication(Configuration)
        .EnableTokenAcquisitionToCallDownstreamApi(new string[] { Configuration["AzureAd.Scopes"] })
        .AddInMemoryTokenCaches();
    
    services.AddHttpContextAccessor();

    services.AddControllersWithViews(options =>
    {
        var policy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
        options.Filters.Add(new AuthorizeFilter(policy));
    }).AddMicrosoftIdentityUI();

    services.AddAuthorization(options =>
    {
        // By default, all incoming requests will be authorized according to the default policy
        options.FallbackPolicy = options.DefaultPolicy;
    });
    services.AddRazorPages();
    services.AddServerSideBlazor()
        .AddMicrosoftIdentityConsentHandler();
    
    
    [...]
}

`

`

 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                endpoints.MapBlazorHub();
                endpoints.MapFallbackToPage("/_Host");
            });
        }

`

Test controller for attempting the api call (always get 401 from api): `

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Diagnostics;
using Microsoft.Identity.Web;
using Newtonsoft.Json.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace S********.Controllers
{
    [Authorize]
    public class TenantController : Controller
    {
        private readonly IHttpClientFactory _clientFactory;
        private readonly ITokenAcquisition _tokenAcquisitionService;
        private HttpClient _client;

        public TenantController(IHttpClientFactory clientFactory, ITokenAcquisition tokenAcquisitionService)
        {
            _clientFactory = clientFactory;
            _tokenAcquisitionService = tokenAcquisitionService;
        }
        public IActionResult Index()
        {
            return View();
        }

        public async Task<IActionResult> GetStuff()
        {
            string[] scopes = { "api://3c*******************c8c/admin.accsess" };
            string accessToken = await _tokenAcquisitionService.GetAccessTokenForUserAsync(scopes);
            
            _client = _clientFactory.CreateClient();
            _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
            var response = await _client.GetAsync("https://localhost:44394/api/SystemConfig/GetByName/System.Tenants");
            return Ok(response);
        }
    }
}

`

Code for API project:

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "custodisas.onmicrosoft.com",
    "TenantId": "eb                      a0",
    "ClientId": "7d                       de",
    "ClientSecret": "G                     5",
    "CallbackPath": "/auth-response"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

Startup.cs:

`

public void ConfigureServices(IServiceCollection services)
{
    var NlogSetup = new SetupLogger();
    NLogConfig = NlogSetup.SetUpLogger("C://Logtest//Skynet//SiloApi.txt").Result;
    LogManager.Configuration = NLogConfig;
    Logger = LogManager.GetLogger("SkynetAPI");
    Logger.Info("Logger up and running");

    services.AddAuthentication(AzureADDefaults.JwtBearerAuthenticationScheme)
        .AddAzureADBearer(options => Configuration.Bind("AzureAd", options));

    // Have tried both with and without scope as required claim
    var scopePolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        //.RequireClaim("scope", "api://3c7*****************c8c/admin.accsess")
        .Build();

    // Have tried both with and without adding scopepolicy as filter
    services.AddMvc(options =>
    {
        //options.Filters.Add(new AuthorizeFilter(scopePolicy));
        options.EnableEndpointRouting = false;
    });

    [...other services...]

    services.AddControllers();
    
    [...other services...]
}

`

`

   public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });

            app.UseMvc(routes =>
            {
                routes.MapRoute(name: "default", template: "api/{controller}/{action}/{id?}");
            });

        }

`

Controller beind called (returns correct info when removing [Authorize]): `

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web.Resource;
using Newtonsoft.Json;
using NLog;
using Orleans;
using *****.Grains.Interfaces;
using *****.Grains.Interfaces.Core.ApplicationScheduler;
using *****.Shared.Core;
using *****.Shared.Core.ApplicationScheduler;
using *****.Shared.Core.GrainStates;
using *****.Shared.Core.RunApplication;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Identity.Web;
using Microsoft.AspNetCore.Cors;
using System.IdentityModel.Tokens.Jwt;

namespace *****.API.Controllers
{

    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class SystemConfigController : MasterController
    {
        public SystemConfigController(Logger logger, IClusterClient clusterClient) : base(logger, clusterClient)
        {
        }

        [HttpGet]
        [Route("GetByName/{configGrainName}")]
        public async Task<ActionResult<string>> GetSystemConfigByName(string configGrainName)
        {
            // Tried adding this code to get the token, it is passed and I can see it when removing [Authorize]
            var re = Request;
            var headers = re.Headers;
            var tokenString = headers["Authorization"];
            Logger.Info("Token: \n\n" + tokenString + "\n\n");

            var tmpGrain = clusterClient.GetGrain<I*****>(0);
            return JsonConvert.SerializeObject(await tmpGrain.GetSystemGrainByName(configGrainName));
        }
    }
}

`

MasterController: `

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using NLog;
using Orleans;

namespace *****.API.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class MasterController : ControllerBase
    {
        // TODO: Get from config.
        public Guid SystemID { get; set; } = Guid.Parse("a9****************71c");
        public Logger Logger;
        public IClusterClient clusterClient;

        public MasterController(Logger logger, IClusterClient client)
        {
            Logger = logger;
            clusterClient = client;
        }
    }
}

`

Hope this is enough info. I am completely stuck, so any help would be appreciated. Thank you :)

Dag Eian
  • 11
  • 3

1 Answers1

0

I tried to reproduce in my environment .

Given API permissions, to the API for you application and granting admin consent to your application.

enter image description here

  • As you have given two scopes api://43xxxxxxx6b3edf/admin.access , api://438xxxxx-a788-xxxx/user.access in the portal expose an API section, both of them must be given in code.

enter image description here

  • I tried giving only one scope using postman and got error as invalid scope. This is causing error 401 in your api Access.

enter image description here

***For client_credentials the scope to access your api must be api://<client_id>/.default

I could get the token successfully after giving the above scope.***

enter image description here

kavyaS
  • 8,026
  • 1
  • 7
  • 19