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.
Webapp Created webapp using .net(classic) Note that webapi is core 5.
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.
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