且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

Azure 身份验证受众验证失败

更新时间:2022-10-24 20:46:33

恐怕问题出在启动时的 auth 配置上.请允许我展示我的代码片段以很好地解释它.

在我看来,您可以改用 services.AddMicrosoftIdentityWebApiAuthentication(Configuration);.并且你应该正确地暴露 api.

公开api的步骤,可以按照文档进行.我想在这里重复的是,当您生成访问令牌时,它应该具有像 api://clientid_of_the_app_exposed_api/tiny/User.Read 这样的范围,可以匹配 appsettings.json代码>

我的 react 代码,参考

I've built an ASP.net core Single tenant Web API that requires a token from Azure, I have also built a single tenant SPA via react that uses Azure to login Via MSAL-Brower. I want to use the token provided from azure when I log in to authenticate my client SPA to call my API. The token request comes back successfully but when I go to fetch I receive an error on my api stating that

Did not match: validationParameters.ValidAudience: 'System.String' or validationParameters.ValidAudiences: 'System.String'.

I requested a token via MSAL Client method acquireTokenSilent with the scope of permissions established on Azure. Ive tried it all, Ive changed the ClientId and ResourceId in both the Client and the Web API.

const PostToDataBase = () => {
    const authenticationModule: AzureAuthenticationContext = new AzureAuthenticationContext();
    const account = authenticationModule.myMSALObj.getAllAccounts()[0]
    const endpoint = {
        endpoint:"https://localhost:44309/api/values",
        scopes:[], // redacted for SO
        resourceId : "" // redacted for SO
    }

    async function postValues(value:string){
        if(value.length < 1){
            console.log("Value can not be null!")
            return;
        }
        console.log(account)
        if(account ){
            console.log("acquiring token")
            authenticationModule.myMSALObj.acquireTokenSilent({
                scopes: endpoint.scopes,
                account: account
            }).then(response => {
                console.log("token acquired posting to server")
                const headers = new Headers();
                const bearer = `Bearer ${response.accessToken}`;
                headers.append("Authorization", bearer);
                headers.append("Content-Type", "'application/json'")
                const options = {
                    method: "POST",
                    headers: headers,
                    bodyInit: JSON.stringify(value)
                };
                return fetch(endpoint.endpoint, options)
                .then(response => console.log(response))
                .catch(error => console.log(error));
            }).catch(err => {
                console.error(err)
                if(err instanceof InteractionRequiredAuthError){
                    if(account ){
                        authenticationModule.myMSALObj.acquireTokenPopup({
                            scopes: endpoint.scopes
                        }).then(response => {
                            const headers = new Headers();
                            const bearer = `Bearer ${response.accessToken}`;
                            headers.append("Authorization", bearer);

                            const options = {
                                method: "POST",
                                headers: headers,
                                body: value
                            };
                            return fetch(endpoint.endpoint, options)
                            .then(response => response.json())
                            .catch(error => console.log(error));
                        }).catch(err => console.error(err))
                    }
                }
            })
        }
        
    }
    async function getValues(){
        
        console.log(account)
        if(account ){
            console.log("acquiring token")
            authenticationModule.myMSALObj.acquireTokenSilent({
                scopes: endpoint.scopes,
                account: account
            }).then(response => {
                console.log("token acquired posting to server")
                const headers = new Headers();
                const bearer = `Bearer ${response.accessToken}`;
                headers.append("Authorization", bearer);
                headers.append("Content-Type", "'application/json'")
                const options = {
                    method: "GET",
                    headers: headers
                };
                return fetch(endpoint.endpoint, options)
                .then(response => response.json())
                .then(res => setValues(res))
                .catch(error => console.log(error));
            }).catch(err => {
                console.error(err)
                if(err instanceof InteractionRequiredAuthError){
                    if(account ){
                        authenticationModule.myMSALObj.acquireTokenPopup({
                            scopes: endpoint.scopes
                        }).then(response => {
                            const headers = new Headers();
                            const bearer = `Bearer ${response.accessToken}`;
                            headers.append("Authorization", bearer);

                            const options = {
                                method: "GET",
                                headers: headers,
                            };
                            return fetch(endpoint.endpoint, options)
                            .then(response => response.json())
                            .then(res => setValues(res))
                            .catch(error => console.log(error));
                        }).catch(err => console.error(err))
                    }
                }
            })
        }
        
    }

    const [values, setValues] = useState([]);
    const [inputValue, setInput] = useState("");
    useEffect(() => {
        // async function getinit(){
        //     const values = await fetch("https://localhost:44309/api/values")
        //     .then(res => res.json())
        //     .catch(e =>
        //         console.error(e))
        //     setValues(values)
        //     console.log(values)
        // } 

        getValues()
    }, [ getValues])
    return (
        <div>
            {values === undefined ? <p>no values to show</p> :
            values.map((n,i)=>( <p key={i}>{n}</p>))}
            <form>
                <input name="inputValues" value={inputValue} onChange={(e)=> setInput(e.target.value)} required></input>
            </form>
            <button onClick={() => postValues(inputValue)}>Post to Server</button>
        </div>
    )
}

export default PostToDataBase

This is functional component that makes a call to the api, this pages is only accessible after the user logs in.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Authentication.JwtBearer;

namespace McQuillingWebAPI
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            //change to client url in production 
            services.AddCors(o => o.AddPolicy("MyPolicy", builder =>
            {
                builder.AllowAnyOrigin()
                       .AllowAnyMethod()
                       .AllowAnyHeader();
            }));
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(opt =>
                {
                    opt.Audience = Configuration["AAD:ResourceId"];
                    opt.Authority = $"{Configuration["AAD:Instance"]}{Configuration["AAD:TenantId"]}";
                });

            services.AddControllers();
        }


        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseCors("MyPolicy");
            app.UseHttpsRedirection();

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

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

This is my startup class where I configure middleware for Authentication

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;

using Microsoft.Identity.Web.Resource;

namespace McQuillingWebAPI.Controllers
{
    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        // GET api/values
 
        
        [HttpGet]
        [RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]
        public ActionResult<IEnumerable<string>> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/values/5
        [HttpGet("{id}")]
        public ActionResult<string> Get(int id)
        {
            return "value";
        }

        // POST api/values
        [Authorize]
        [HttpPost]
        public IActionResult Post([FromBody] string value)
        {
            return Ok("Posted");
        }

        // PUT api/values/5
        [HttpPut("{id}")]
        public void Put(int id, [FromBody] string value)
        {
        }

        // DELETE api/values/5
        [HttpDelete("{id}")]
        public void Delete(int id)
        {
        }
    }
}

this is one of the generated controllers that I'm testing authentication with

I'm afraid the issue comes from the auth configuration in startup. Pls allow me show my code snippet to explain it well.

In my opinion, you could use services.AddMicrosoftIdentityWebApiAuthentication(Configuration); instead. And you should exposed the api correctly.

The steps of exposing api, you can follow the documents. What I wanna repeat here is when you generate an access token, it should have the scope like api://clientid_of_the_app_exposed_api/tiny/User.Read which can match the configuration in appsettings.json

My react code, it is referred to this sample:

import { AuthenticatedTemplate, UnauthenticatedTemplate, useMsal } from "@azure/msal-react";  
const callApi = (accessToken) => {
            const headers = new Headers();
            const bearer = `Bearer ${accessToken}`;
    
            headers.append("Authorization", bearer);
    
            const options = {
                method: "GET",
                headers: headers
            };
    
            fetch("https://localhost:44341/api/home", options)
                .then(response => {
                    var a = response.json();
                    console.log(a);
                })
                .catch(error => console.log(error));
        };
    
        const ProfileContent = () => {
            const { instance , accounts} = useMsal();
            const [graphData, setGraphData] = useState(null);
            const loginRequest = {"scopes": ["api://clientid_of_the_app_exposed_api/tiny/User.Read"]};
        
            function RequestProfileData() {
                instance.acquireTokenSilent({
                    ...loginRequest,
                    account: accounts[0]
                }).then((response) => {
                    callApi(response.accessToken);
                });
            }
        

My ConfigureServices in startup file, these are referred to this document:

public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors(o => o.AddPolicy("MyPolicy", builder =>
            {
                builder.AllowAnyOrigin()
                       .AllowAnyMethod()
                       .AllowAnyHeader();
            }));
            services.AddMicrosoftIdentityWebApiAuthentication(Configuration);
            services.AddControllers();
        }

My appsettings :

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "ClientId": "clientid_which_have_api_permission",
    "Domain": "tenantname.onmicrosoft.com",
    "TenantId": "common",
    "Audience": "clientid_of_the_app_exposed_api"
  }
}

My controller:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web.Resource;
using System.Collections.Generic;

namespace WebApplication1.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    [Authorize]
    public class HomeController : ControllerBase
    {
        [HttpGet]
        [RequiredScope("User.Read")]
        public ActionResult<IEnumerable<string>> Get()
        {
            return new string[] { "value1", "value2" };
        }
    }
}

相关阅读

技术问答最新文章