{"id":6526,"date":"2019-12-10T06:31:19","date_gmt":"2019-12-10T06:31:19","guid":{"rendered":"http:\/\/blogs.aaddevsup.xyz\/?p=6526"},"modified":"2020-08-17T01:41:41","modified_gmt":"2020-08-17T01:41:41","slug":"troubleshooting-asp-net-core-azure-ad-protected-web-api-authorization-issues","status":"publish","type":"post","link":"https:\/\/blogs.aaddevsup.xyz\/2019\/12\/troubleshooting-asp-net-core-azure-ad-protected-web-api-authorization-issues\/","title":{"rendered":"Troubleshooting ASP.NET Core Azure AD-protected Web API Authorization issues"},"content":{"rendered":"\n

There is plenty of content on the internet that shows you how to use ASP.NET Core Authentication pipeline to protect a Web API with Azure Active Directory.  This is all working great except when your Web API returns a 401 Unauthorized error without providing much information.  You can end up spending a lot of time, pulling your hair out trying to figure out what can go wrong even though you (or at least believe) have followed every step of the documentation, implemented all the best practices and guidelines.  Hopefully, this blog can help alleviate that pain for you.  I will build the content here on top of my previous<\/a> blog post which discusses how you can protect your Web API with Azure Active Directory.<\/p>\n\n\n\n

Let\u2019s dig in\u2026<\/h2>\n\n\n\n

When you use the [Authorize] attribute to protect your web API controller as followed <\/p>\n\n\n

    \n[Authorize]\npublic class MyController : ControllerBase\n{\n    ...\n}\n<\/pre>\n\n\n

or to protect an action in the controller<\/p>\n\n\n

public class MyController : ControllerBase\n{\n   [Authorize]\n   public ActionResult<string> Get(int id)\n   {\n       return \"value\";\n   }\n   ...\n}<\/pre>\n\n\n

It expects the request has a valid Bearer token presented in the \u201cAuthorization: Bearer ey\u2026\u201d HTTP Request header.  The controller returns a 401 Unauthorized response when the request either does not have an “Authorization Bearer token” header or the request contains an invalid Bearer token (the token is expired, the token is for a different resource, or the token’s claims do not satisfy at least one of the application\u2019s token validation criteria as defined in the JwtBearerOptions’s TokenValidationParameters<\/a> class).<\/p>\n\n\n\n

The JwtBearerEvents class has the following callback properties (invoked in the following order) that can help us debug these 401 Access Denied or UnAuthorization issues:<\/p>\n\n\n\n

  1. OnMessageRecieved<\/a> \u2013 gets called first for every request<\/li>
  2. OnAuthenticationFailed<\/a> \u2013 gets called when the token does not pass the application\u2019s token validation criteria. <\/li>
  3. OnChallenge<\/a> \u2013 this gets called last before a 401 response is returned<\/li><\/ol>\n\n\n\n

    Note:  In some scenarios, the OnAuthenticationFailed<\/strong> method may not be invoked, for example:  the request does not have any bearer token or it contains a malformed token (invalid jwt token format)<\/p>\n\n\n\n

    We will use the above callbacks to build our own custom error message to return to the client.<\/p>\n\n\n\n

    Note:  the sample code in this blog is only meant for Proof of Concept and can review PII information only for debugging local development effort so you should be cautious not to use in production environment.<\/p>\n\n\n\n

    Implement the following code:<\/h2>\n\n\n\n
    1. Enable PII logging (it\u2019s off by default) in the Configure<\/strong> method in Startup.cs file:<\/li><\/ol>\n\n\n
      public void Configure(IApplicationBuilder app, IHostingEnvironment env)\n{\n    if (env.IsDevelopment())\n    {\n        app.UseDeveloperExceptionPage();\n    }\n    else\n    {\n        \/\/ The default HSTS value is 30 days. You may want to change this for production scenarios, see https:\/\/aka.ms\/aspnetcore-hsts.\n        app.UseHsts();\n    }\n    \/\/ turn on PII logging\n    Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;\n\n    app.UseHttpsRedirection();\n    app.UseAuthentication();\n    app.UseMvc();\n}        <\/pre>\n\n\n
      1. Create a method called FlattenException<\/strong> to format our Exception Message<\/li><\/ol>\n\n\n
        public static string FlattenException(Exception exception)\n{\n    var stringBuilder = new StringBuilder();\n    while (exception != null)\n    {\n        stringBuilder.AppendLine(exception.Message);\n        stringBuilder.AppendLine(exception.StackTrace);\n        exception = exception.InnerException;\n    }\n    return stringBuilder.ToString();\n}<\/pre>\n\n\n
        1. Implement the above callbacks in the ConfigureServices<\/strong> method in Startup.cs file<\/li><\/ol>\n\n\n
          public void ConfigureServices(IServiceCollection services)\n{\n....\n    .AddJwtBearer(options =>\n    {\n        options.Authority = \"https:\/\/login.microsoftonline.com\/<Tenant>.onmicrosoft.com\";\n        \/\/ if you intend to validate only one audience for the access token, you can use options.Audience instead of\n        \/\/ using options.TokenValidationParameters which allow for more customization.\n        \/\/ options.Audience = \"10e569bc5-4c43-419e-971b-7c37112adf691\";\n\n        options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters\n        {\n            ValidAudiences = new List<string> { \"<Application ID URI>\", \"10e569bc5-4c43-419e-971b-7c37112adf691\" },\n            ValidIssuers = new List<string> { \"https:\/\/sts.windows.net\/<Directory ID>\/\", \"https:\/\/sts.windows.net\/<Directory ID>\/v2.0\" }\n        };\n                \n        options.Events = new JwtBearerEvents\n        {\n            OnAuthenticationFailed = ctx =>\n            {\n                ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;\n                message += \"From OnAuthenticationFailed:\\n\";\n                message += FlattenException(ctx.Exception);\n                return Task.CompletedTask;\n            },\n\n            OnChallenge = ctx =>\n            {\n                message += \"From OnChallenge:\\n\";\n                ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;\n                ctx.Response.ContentType = \"text\/plain\";\n                return ctx.Response.WriteAsync(message);\n            },\n\n            OnMessageReceived = ctx =>\n            {\n                message = \"From OnMessageReceived:\\n\";\n                ctx.Request.Headers.TryGetValue(\"Authorization\", out var BearerToken);\n                if (BearerToken.Count == 0)\n                    BearerToken = \"no Bearer token sent\\n\";\n                message += \"Authorization Header sent: \" + BearerToken + \"\\n\";\n                return Task.CompletedTask;\n            },\n\n            OnTokenValidated = ctx =>\n            {\n                Debug.WriteLine(\"token: \" + ctx.SecurityToken.ToString());\n                return Task.CompletedTask;\n            }\n        };\n    });\n...\n}<\/pre>\n\n\n

          For completeness, I have also implemented the OnTokenValidated property to print out the token claims. This method is invoked when the authentication is successful.<\/p>\n\n\n

          Here is my entire Startup.cs file<\/p>\n

          [gist id=”90b646e2fedb0a446522d5e0076dddf7″ file=”Startup.cs”]<\/p>\n\n\n

          The result…<\/h2>\n\n\n\n

          The above implementation should result in 401 Error message with some output as followed<\/p>\n\n\n\n

          \"\"<\/figure>\n\n\n\n

          or the following<\/p>\n\n\n\n

          \"\"<\/div>\n\n\n\n

          <\/p>\n","protected":false},"excerpt":{"rendered":"

          There is plenty of content on the internet that shows you how to use ASP.NET Core Authentication pipeline to protect a Web API with Azure Active Directory.  This is all working great except when your Web API returns a 401 Unauthorized error without providing much information.  You can end up spending a lot of time, pulling your hair out trying to figure out what can go wrong even though you…<\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[172,37,144,137],"class_list":["post-6526","post","type-post","status-publish","format-standard","hentry","category-azure-ad","tag-aspnetcore","tag-authorization","tag-jwt","tag-web-appapi"],"_links":{"self":[{"href":"https:\/\/blogs.aaddevsup.xyz\/wp-json\/wp\/v2\/posts\/6526"}],"collection":[{"href":"https:\/\/blogs.aaddevsup.xyz\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.aaddevsup.xyz\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.aaddevsup.xyz\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.aaddevsup.xyz\/wp-json\/wp\/v2\/comments?post=6526"}],"version-history":[{"count":55,"href":"https:\/\/blogs.aaddevsup.xyz\/wp-json\/wp\/v2\/posts\/6526\/revisions"}],"predecessor-version":[{"id":7719,"href":"https:\/\/blogs.aaddevsup.xyz\/wp-json\/wp\/v2\/posts\/6526\/revisions\/7719"}],"wp:attachment":[{"href":"https:\/\/blogs.aaddevsup.xyz\/wp-json\/wp\/v2\/media?parent=6526"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.aaddevsup.xyz\/wp-json\/wp\/v2\/categories?post=6526"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.aaddevsup.xyz\/wp-json\/wp\/v2\/tags?post=6526"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}