{"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 When you use the [Authorize] attribute to protect your web API controller as followed <\/p>\n\n\n or to protect an action in the controller<\/p>\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 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 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 above implementation should result in 401 Error message with some output as followed<\/p>\n\n\n\nLet\u2019s dig in\u2026<\/h2>\n\n\n\n
\n[Authorize]\npublic class MyController : ControllerBase\n{\n ...\n}\n<\/pre>\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
Implement the following code:<\/h2>\n\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
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
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
The result…<\/h2>\n\n\n\n