简体   繁体   中英

Getting 401 from custom Api when using Microsoft.Identity.platform to protect api

I am following the tutorial from Microsfot.document for how to protect api using Azure AD (Microsoft Identity).

The steps I took are following: Sorry I tried to put information that might be helpful but too much to get to the issue most of the time contributors ask for screenshot or the code.

I followed several documents and video tutorials but here is the link for one of them: https://learn.microsoft.com/en-us/learn/modules/identity-secure-custom-api/2-secure-api-microsoft-identity

WebApi.

  1. Created a webapi using core 5. Register it in Azure AD.
  2. Created single scope 'check' and allowed permission to user and admin.

Webapp Created webapp using .net(classic) Note that webapi is core 5.

  1. Created a webapp and register it in Azure AD.
  2. Setup the authentication and created a OnAuthorizationCodeReceived to get the access token to call the api.

Configuration: 1.From Azure AD->Registration for Webapi-> selected application(web app created above) and give permission to the scope.

在此处输入图像描述 2. For Azure AD->Registration for webapp-> Access permission->delegate->selected the scope. 在此处输入图像描述

Test: 1.Run the test. At this point; I do not have [Authorization] on the api method which I am calling. 2. Webapp successfully able to get the string which is returned by the api. Somewhat I get the idea that plumbing was right.

  1. Added [Authorize] filter on the app method.
  2. Result 401 unauthorized.

I have checked multiple times and looked at multiple tutorial and rewrote my code, watched several videos and updated my code but I am always getting 401 error.

Below is the code; Api controller:

namespace Utility.Controllers
 {
   [Authorize]
    [Route("api/[controller]")]
   [ApiController]
   public class AzAdUtility : ControllerBase
     {
    // GET: api/<AzAdUtility>
    [HttpGet]
    public string Get()
    {
        //HttpContext.VerifyUserHasAnyAcceptedScope(new string[] {"check"});
        var name = "Vaqas";
        return name;
    }        
 }
}

Api startup: 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"));   
        services.AddControllers();
        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new OpenApiInfo { Title = "GlobalNetApiUtility", Version = "v1" });
        });
    }

    // 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.UseSwagger();
            app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Utility v1"));
        }

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

Api Appsettings:

"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "myorg.onmicrosoft.com",
"ClientId": "abcd.............................",
"TenantId": "dabcd.............................."

},

Webapp startup: Only adding startup page because at first all I am doing getting some data for testing purpose in the OnAuthorizationCodeReceived.

public class Startup
    {
        // The Client ID is used by the application to uniquely identify itself to Azure AD.
       static string clientId = System.Configuration.ConfigurationManager.AppSettings["ClientId"];

        // RedirectUri is the URL where the user will be redirected to after they sign in.
        string redirectUri = System.Configuration.ConfigurationManager.AppSettings["RedirectUri"];

        // Tenant is the tenant ID (e.g. contoso.onmicrosoft.com, or 'common' for multi-tenant)
       static string tenant = System.Configuration.ConfigurationManager.AppSettings["Tenant"];

    // Authority is the URL for authority, composed by Microsoft identity platform endpoint and the tenant name (e.g. https://login.microsoftonline.com/contoso.onmicrosoft.com/v2.0)
     string authority = String.Format(System.Globalization.CultureInfo.InvariantCulture, System.Configuration.ConfigurationManager.AppSettings["Authority"], tenant);
    //string authority = "https://login.microsoftonline.com/" + tenant + "/adminconsent?client_id=" + clientId;
    string clientSecret = System.Configuration.ConfigurationManager.AppSettings["ClientSecret"];
    /// <summary>
    /// Configure OWIN to use OpenIdConnect 
    /// </summary>
    /// <param name="app"></param>
    public void Configuration(IAppBuilder app)
        {
            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

            app.UseCookieAuthentication(new CookieAuthenticationOptions());
            app.UseOpenIdConnectAuthentication(
            new OpenIdConnectAuthenticationOptions
            {
            // Sets the ClientId, authority, RedirectUri as obtained from web.config
            ClientId = clientId,
                Authority = authority,
                RedirectUri = redirectUri,
            // PostLogoutRedirectUri is the page that users will be redirected to after sign-out. In this case, it is using the home page
            PostLogoutRedirectUri = redirectUri,
                Scope = OpenIdConnectScope.OpenIdProfile,
                // ResponseType is set to request the code id_token - which contains basic information about the signed-in user
                //ResponseType = OpenIdConnectResponseType.CodeIdToken,
                ResponseType = OpenIdConnectResponseType.CodeIdToken,


                // OpenIdConnectAuthenticationNotifications configures OWIN to send notification of failed authentications to OnAuthenticationFailed method
                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                   AuthorizationCodeReceived = OnAuthorizationCodeReceived,
                    AuthenticationFailed = OnAuthenticationFailed
                  
                   
                }
            }
        );
        }

    private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
    {
        notification.HandleCodeRedemption();

        var idClient = ConfidentialClientApplicationBuilder.Create(clientId)
            .WithRedirectUri(redirectUri)
            .WithClientSecret(clientSecret)
            .WithAuthority(authority)               
            .Build();

        try
        {

            var apiScope = "api://28......................../check2 api://28................/check api://28...........................1d/AzAdUtility.Get";
            string[] scopes = apiScope.Split(' ');

            //gettig the token no issues.
            var result = await idClient.AcquireTokenByAuthorizationCode(
                scopes, notification.Code).ExecuteAsync();

            var myurl = "https://localhost:99356/api/AzAdUtility";

            var client = new HttpClient();
            // var accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(Constants.ProductCatalogAPI.SCOPES);
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken); //accessToken

            var json = await client.GetStringAsync(myurl);

            var serializerOptions = new JsonSerializerOptions
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase
            };

           
            //getting 401 response
            var checkResponse = JsonSerializer.Deserialize(json, typeof(string), serializerOptions) as string;

        }


        catch (Exception ex)
        {
            string message = "AcquireTokenByAuthorizationCodeAsync threw an exception";
            notification.HandleResponse();
            notification.Response.Redirect($"/Home/Error?message={message}&debug={ex.Message}");
        }

    }

    /// <summary>
    /// Handle failed authentication requests by redirecting the user to the home page with an error in the query string
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
        {
            context.HandleResponse();
            context.Response.Redirect("Error/AccessDenied/?errormessage=" + context.Exception.Message);
            return Task.FromResult(0);
        }
    }

In Api startup class I was missing app.UseAuthentication() .
I never really thought that would be an issue. Once I added this code. I got the expected response rather than 401 Unauthorized error

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM