0

I created asp.net core with React template in VS 2019, i need to authorize a controller method so I first registered my app on Azure AD and than i used this Startup.cs configurations:

    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)
        {

            services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
         .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"))
         .EnableTokenAcquisitionToCallDownstreamApi()
         .AddInMemoryTokenCaches();

            services.AddControllersWithViews().AddMicrosoftIdentityUI();

            // In production, the React files will be served from this directory
            services.AddSpaStaticFiles(configuration =>
            {
                configuration.RootPath = "ClientApp/build";
            });
        }

        // 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();
            }
            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.UseSpaStaticFiles();

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

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller}/{action=Index}/{id?}");
            });

            app.UseSpa(spa =>
            {
                spa.Options.SourcePath = "ClientApp";

                if (env.IsDevelopment())
                {
                    spa.UseReactDevelopmentServer(npmScript: "start");
                }
            });
        }
    }

In the controller I used AuthorizeForScopes and ITokenAcquisition as follows

    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {  
        private readonly ITokenAcquisition tokenAcquisition;
        public WeatherForecastController(ITokenAcquisition tokenAcquisition)
        {
            this.tokenAcquisition = tokenAcquisition;
        }

        [AuthorizeForScopes(Scopes = new[] { "https://tenantname.sharepoint.com/AllSites.FullControl" })]
        [HttpGet]
        public async Task<IEnumerable<WeatherForecast>> Get()
        {
            string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(new[] { "https://tenantname.sharepoint.com/AllSites.FullControl" });
           
            ......
            ......
        }
    }

but when i try to fetch the data i have a CORS error

enter image description here

Can you help me

1 Answers1

0

Regarding the issue, please refer to the following steps

  1. Register client application and server application

  2. Use React project template with ASP.NET Core

  3. Client application

a. Install msal

npm install msal

b. Define MsalAuthProvider.

import React, { Component } from 'react';
import { UserAgentApplication } from 'msal';

const msalConfig = {
    authority: 'https://login.microsoftonline.com/common',
    clientId: '232a1406-b27b-4667-b8c2-3a865c42b79c',
    redirectUri: document.getElementById('root').baseURI
};
export const msalAuth = new UserAgentApplication({
    auth: msalConfig
});

export function withAuth(HocComponent) {
    return class extends Component {
        constructor(props) {
            super(props);

            this.state = {
                isAuthenticated: false,
                user: {},
                renewIframe: false,
                hasError: false,
                errorMessage: null
            };
        }

        async componentWillMount() {
            msalAuth.handleRedirectCallback(() => {
                let userAccount = msalAuth.getAccount();

                this.setState({
                    isAuthenticated: true,
                    user: userAccount
                });
            }, (authErr, accountState) => {  // on fail
                console.log(authErr);

                this.setState({
                    hasError: true,
                    errorMessage: authErr.errorMessage
                });
            });

            if (msalAuth.isCallback(window.location.hash)) {
                this.setState({
                    auth: {
                        renewIframe: true
                    }
                });
                return;
            }

            let userAccount = msalAuth.getAccount();
            if (!userAccount) {
                msalAuth.loginRedirect({});
                return;
            } else {
                this.setState({
                    isAuthenticated: true,
                    user: userAccount
                });
            }
        }

        onSignIn() {
            msalAuth.loginRedirect({});
        }

        onSignOut() {
            msalAuth.logout();
        }

        render() {
            if (this.state.renewIframe) {
                return <div>hidden renew iframe - not visible</div>;
            }

            if (this.state.isAuthenticated) {
                return <HocComponent auth={this.state} onSignIn={() => this.onSignIn()} onSignOut={() => this.onSignOut()} {...this.props} />;
            }

            if (this.state.hasError) {
                return <div>{this.state.errorMessage}</div>;
            }

            return <div>Login in progress...</div>;
        }
    };
}

c. Update App.js


import { withAuth } from './msal/MsalAuthProvider';
import './custom.css'

class RootApp extends Component {
  static displayName ="Azure AD application";

  render () {
    return (
      <Layout>
        ...
      </Layout>
    );
  }
}


//enable auth when we access the page
const App = withAuth(RootApp)
export default App;
  1. call the API
import { msalAuth } from '../msal/MsalAuthProvider'

 async componentDidMount() {
     // get token and call the api
      try {
          const accessTokenRequest = {
              scopes: ["api://872ebcec-c24a-4399-835a-201cdaf7d68b/access_as_user"]
          }
          var authRes
          var accessToken = null;
          try {
              authRes = await msalAuth.acquireTokenSilent(accessTokenRequest);
              accessToken=authRes.accessToken
          }
          catch (error) {
              console.log("AquireTokenSilent failure");
              authRes = await msalAuth.acquireTokenPopup(accessTokenRequest);
              accessToken = authRes.accessToken
          }

          
          console.log(accessToken)
          this.populateWeatherData(accessToken);
      }
      catch (err) {
          var error = {};
          if (typeof (err) === 'string') {
              var errParts = err.split('|');
              error = errParts.length > 1 ?
                  { message: errParts[1], debug: errParts[0] } :
                  { message: err };
          } else {
              error = {
                  message: err.message,
                  debug: JSON.stringify(err)
              };
          }

          this.setState({
              user: {},
              isLoading: false,
              error: error
          });
      }
  }

async populateWeatherData(token) {
      const response = await fetch('weatherforecast', {
          method: 'get',
          headers: new Headers({
              'Authorization': 'Bearer ' + token
          })

      });
    const data = await response.json();
    this.setState({ forecasts: data, loading: false });
  }
  1. Server code

a. Startup.cs

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)
        {
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd"))
                    .EnableTokenAcquisitionToCallDownstreamApi()
                     .AddInMemoryTokenCaches();

            services.AddControllersWithViews();
           
                // In production, the React files will be served from this directory
                services.AddSpaStaticFiles(configuration =>
            {
                configuration.RootPath = "ClientApp/build";
            });
        }

        // 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();
            }
            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.UseSpaStaticFiles();

            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller}/{action=Index}/{id?}");
            });

            app.UseSpa(spa =>
            {
                spa.Options.SourcePath = "ClientApp";

                if (env.IsDevelopment())
                {
                    spa.UseReactDevelopmentServer(npmScript: "start");
                }
            });
        }
    }

b. Controller

[ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {  
        private readonly ITokenAcquisition tokenAcquisition;
        public WeatherForecastController(ITokenAcquisition tokenAcquisition)
        {
            this.tokenAcquisition = tokenAcquisition;
        }

        [AuthorizeForScopes(Scopes = new[] { "<your scope>" })]
        [HttpGet]
        public async Task<IEnumerable<WeatherForecast>> Get()
        {
            string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(new[] { "<your scope>" });
           
            ......
            ......
        }
    }

For more details, please refer to here snd here.

Jim Xu
  • 21,610
  • 2
  • 19
  • 39
  • Please see my answer – Francesco Borraccino Mar 03 '21 at 09:46
  • @FrancescoBorraccino Could you please describe your issue in detail? You want to call your web api then the web api will call another api. Right? – Jim Xu Mar 03 '21 at 14:02
  • I followed all your steps and for the registrations I used the following configurations: 1. I registered two apps: - server app [1]: https://i.stack.imgur.com/VzpyQ.png [2]: https://i.stack.imgur.com/fHKWK.png [3]: https://i.stack.imgur.com/Pd1RX.png [4]: https://i.stack.imgur.com/zARsS.png - client app [5]: https://i.stack.imgur.com/vAaEh.png [6]: https://i.stack.imgur.com/P7DFV.png [7]: https://i.stack.imgur.com/FxzX6.png But when I try to fetch data from WeatherForecastController i have an exception [8]: https://i.stack.imgur.com/sZQNW.png – Francesco Borraccino Mar 03 '21 at 15:05
  • @FrancescoBorraccino Please check if you have configured the permissions for your server application. – Jim Xu Mar 04 '21 at 02:26
  • @FrancescoBorraccino Do you have any update? – Jim Xu Mar 05 '21 at 01:49