Postman cannot access AAD protected Asp.net core 3.1 restapi. 401 unauthorized - asp.net-core

I have setup an AAD-protected asp.net core 3.1 restapi web service by the following steps.
Register a server app (HelloWorld) and then add a scope.
create the server app (HelloWorld)
Add a scope
Register a client app(domino-client) and create a secret. Then add the server app permission.
create the client app(domino-client)
add server app(HelloWorld) permission
Add AAD auth to asp.net core. I create a rest api project and do the following changes. (Config auth related service and middleware. Config controller.)
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddAuthentication(o =>
{
o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.Authority = "https://login.microsoftonline.com/{tenant_id}";
o.Audience = "a1faffea-24c6-42ff-9586-ee86ec7b8e80"; // server app client id
});
}
// 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.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication(); // Add aad auth.
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
[Authorize] // Enable auth.
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
.....
}
}
Then try to use postman to access the api.
postman
access token
Some parmas when accessing token.
Access token url: Got from Endpoint
Client ID: client app client id
Client Secret: client app secret
scope: server app scope
Howerer, I get 401 unauthoried error. Is something wrong with the process?

According to the details you provided, you want to use OAuth 2.0 client credentials flow to access the API protected by Azure AD. If so you need to define app role instead of scope in your server app.
The detailed steps are as below
Create server app
Define app role
a. Select the application you want to define app roles in. Then select Manifest.
b. Edit the app manifest by locating the appRoles setting and adding all your Application Roles. It should be like as below
"appRoles": [
{
"allowedMemberTypes": [
"Application"
],
"displayName": "access the web api",
"id": "47fbb575-859a-4941-89c9-0f7a6c30beac",
"isEnabled": true,
"description": "Consumer apps have access to web api.",
"value": "Consumer"
}
],
Register a client app and create a secret.
Add the app role for your client application
Configure web API
a. Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.Authority = "https://login.microsoftonline.com/<tenant id>/v2.0";
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidAudiences = new[] {"<app id>","<app id url>" }
};
});
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
b. add [Authorize] in your API Controller to enable auth
Test in postman
a. Get Access token
b. Call API

You are using client credential flow to accessing your protected web api , the audience of access token is api://fxxxb30-xxx-xxx-xxxx-bcaae52203cf , so try to modify your AddJwtBearer options to(also notice you are using Azure AD V2.0 endpoint) :
.AddJwtBearer(o =>
{
o.Authority = "https://login.microsoftonline.com/{tenant_id}/v2.0"; <--AAD V2.0
o.Audience = "api://a1faffea-24c6-42ff-9586-ee86ec7b8e80"; <-- add api//
});
Another problem is you are adding the delegate permission , so access token issued by client credential flow won't include the delegate permission , instead you should use delegate flows like authorization code flow .

Related

Fetch data return Untheorized 401 access to asp.net core API protected in Azure AD

Im new to `webassembly blazor, Im spend too much time trying to figure out what's wrong here but I couldnt manage.
I have the following scenario:
Asp.net API registered and protected in Azure AD
Expose API with Scope AcessApi with status enabled
A Client application is added to authorized client applications
Token configuration both are checked Access Token and ID Token
And a client app that will call the API, developed in webassembly blazor
client app is registered in Azure AD
Client API permissions has delegated permission to use my client API
with correct scope AccessApi.
I tested the API using swagger interface, it forces user to authenticate first before accessing the API.
I tested using curl and grabbed the token from swagger interface and works perfectly fine.
curl -X GET "http://localhost:9400/api/getdata" -H "accept: text/plain" -H "Authorization: Bearer XX"
However, when my client application trying to access the API, a sign-in page pop-up for credentials, I could see the Token ID at browser bar being retrieved and while calling the API the app logs error not authorized
program class of the client application:
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
//builder.Logging.SetMinimumLevel(LogLevel.Debug);
////builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped<CustomAuthorizationMessageHandler>();
builder.Services.AddHttpClient("AccessApi",
client => client.BaseAddress = new Uri("http://localhost:9400"))
.AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("AccessApi"));
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add(scope);
});
await builder.Build().RunAsync();
}
in CustomAuthorizationMessageHandler class I have defined:
private static string scope = #"api://xxx-35fc2470889f/AccessApi";
public CustomAuthorizationMessageHandler(IAccessTokenProvider provider,
NavigationManager navigationManager)
: base(provider, navigationManager)
{
ConfigureHandler(
authorizedUrls: new[] { "http://localhost:9400" },
}
In appsettings.json a defined the client id of the API and tenant id without scopes since they are been defined in the CustomAuthorizationMessageHandlerclass:
{
"AzureAd": {
"Authority": "https://login.microsoftonline.com/<tenant_id>",
"ClientId": "<clientid>",
"CallbackPath": "/signin-oidc",
"ValidateAuthority": "true"
}
}
After a successful login via Azure AD, I call to fetch data from the API here
protected override async Task OnInitializedAsync()
{
...
try
{
responseBody = await Http.GetStringAsync("/api/getdata"); # use base URL of the API
}
catch (AccessTokenNotAvailableException ex)
{
ex.Redirect();
}
}
the console logs
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[1]
Authorization was successful.
info: System.Net.Http.HttpClient.AccessApi.ClientHandler[100]
Sending HTTP request GET http://localhost:9400/api/getdata
:9400/customer-manager/api/getdata:1 Failed to load resource: the server responded with a status of 401 (Unauthorized)
What could be wrong here?
Is there a way how to print the return token?
Update
I tested the API using Postman where auth Grant type is Implicit, after successful login, I store token on variable and passed in the header as Bearer the API return 401 Unauthroized. I decoded the token it contains the right scope AccessApi , with the correct clientId. what could be wrong here ?
If you want to call Microsoft graph and your custom API in one blazor webassembly project, we can implement it by creating different HTTP client to call different API
For example
Register a server API app
Register an AAD app for the Server API app
Expose an API
Register a client app
Register a client app
Enable Implicit grant flow
Add API permissions. ( API app permissions)
Configure API app
Please add the following code in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
services.AddCors(options =>
{
options.AddDefaultPolicy(
builder => builder.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod());
});
services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
.AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options =>
{
options.Authority += "/v2.0";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuers = new[] {
$"https://sts.windows.net/{Configuration["AzureAD:TenantId"]}/",
$"https://login.microsoftonline.com/{Configuration["AzureAD:TenantId"]}/v2.0"
},
RoleClaimType = "roles",
// The web API accepts as audiences both the Client ID (options.Audience) and api://{ClientID}.
ValidAudiences = new[]
{
options.Audience,
$"api://{options.Audience}"
}
};
});
....
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.OAuthClientId(Configuration["Swagger:ClientId"]);
c.OAuthScopeSeparator(" ");
c.OAuthAppName("Protected Api");
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Configure Client APP
Create custom AuthorizationMessageHandler for Graph API and custom API
// custom API
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
{
public CustomAuthorizationMessageHandler(IAccessTokenProvider provider,
NavigationManager navigationManager)
: base(provider, navigationManager)
{
ConfigureHandler(
authorizedUrls: new[] { "<your web API url>" },
scopes: new[] { "the API app scope" });
}
}
Add the following code to the program.cs
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
builder.Services.AddScoped<CustomAuthorizationMessageHandler>();
// register HTTP client to call our own api
builder.Services.AddHttpClient("MyAPI", client => client.BaseAddress = new Uri("<your web API url>"))
.AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("<the API app scope>");
});
await builder.Build().RunAsync();
}
}
Call the api
#inject IHttpClientFactory _clientFactory
var httpClient = _clientFactory.CreateClient("<the client name you register>");
await apiClient.GetStringAsync("path");
Finally I found the issue was on the server side ASP.net core where I was validating the token in ConfigureServices at startup class:
// For token parameters validation
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(o =>
{
o.Audience = "<xx>"; // Application id
o.Authority = "https://login.microsoftonline.com/<xx>"; // Tenant ID
//Token validation
o.TokenValidationParameters = new TokenValidationParameters {ValidateIssuerSigningKey = false, ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = true};
});
I had to disable Issuer since the token is coming from a different application.

Authenticate API in .net core using ping identity OAuth2.0

Problem Statement : I want to secure APIs using ping identity OAuth 2.0. I am following this blog but I get 401.
I have configured in postman tool with OAuth2.0 with details provided by ping identity team and I'm able to generate the token but the same token when I copy paste and send it as Bearer, I get 401 in the API.
I doubt if I'm giving the wrong callback URL. If my API URL is say http://web.abc.com/_api/home/userinfo then what should be my callback URL?
NOTE : I am not using this solution in the browser and directly trying to secure the APIs. May be my approach itself is not correct. Let me know if any better solution.
EDIT :
Startup.cs looks like this :
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)
{
string x509PublicCert = #"XXXXXXXXXXX";
var byteCert = Convert.FromBase64String(x509PublicCert);
var x509Cert = new X509Certificate2(byteCert);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Audience = "http://localhost:65180/";//Configuration["Audience"]; //"http://localhost:9000/";
options.Authority = "https://myloginqa.xyz.com:8080/"; //Configuration["Authority"]; // "https://idp.yourcompany.com:5000/";
options.TokenValidationParameters = new TokenValidationParameters
{
// Validate the JWT Audience
ValidateIssuerSigningKey = true,
IssuerSigningKey = new X509SecurityKey(x509Cert),
ValidateIssuer = true,
ValidIssuer = "myloginqa.xyz.com",//Configuration["Issuer"], //idp.yourcompany.com
ValidateAudience = false,
ValidateLifetime = true,
// If you want to allow a certain amount of clock drift, set that here:
ClockSkew = TimeSpan.Zero
};
});
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");
}
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseRouting();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
app.UseCors("CorsApi");
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");
}
});
}
}
Controller looks like this :
[EnableCors("CorsApi")]
//[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase

No default authentication/challenge schemes found, but they're set in Startup

In an effort to port a .NET Framework web API to .NET Core 2.2, I ran into some issues when configuring authentication using AD. The old app would have something like this:
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = "contoso.onmicrosoft.com",
TokenValidationParameters = new TokenValidationParameters { ValidAudience = "1111..."}
});
The way we use the API is as follows: a web app handles authentication (gets a JWT) and uses said token with our API for certain actions. Searching around the web I found this sample which uses Microsoft.AspNetCore.Authentication.AzureAD.UI. I now ended up with the following:
appsettings.json:
{
...
"AzureAd": {
"Domain": "contoso.onmicrosoft.com",
"Instance": "https://login.microsoftonline.com",
"TenantId": "2222...",
"ClientId": "1111..."
}
}
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = AzureADDefaults.JwtBearerAuthenticationScheme;
options.DefaultChallengeScheme = AzureADDefaults.JwtBearerAuthenticationScheme;
});
}
public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
app.UseMvc();
}
The problem is that when I use the generated token (that works with the .NET Framework app) I get the following error: InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found.
It looks to me you haven't added the Azure AD authentication scheme. That usually looks something like services.AddAuthentication().AddAzureADBearer().

Embedded IdentityServer 4 with Aspnet Identity and resource owner

I am trying to use IdentityServer4 with resource owner flow + aspnet identity and embed the api in the same project.
I tested the Sample here on github and it's working fine. I am able to retrieve a token for a registered user in the database and use this token to get protected resources from the api.
The sample the api is separated from the identity server, once both are merged into one project, im still able to get a token, BUT I get 401 Unauthorized while trying to access the protected resource. somehow the embedded api is no longer validating the token.
here's the Startup.cs code :
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
//(1)
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
services
.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryPersistedGrants()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
//(2)
.AddAspNetIdentity<ApplicationUser>();
//.AddTestUsers(Config.GetUsers());
var corsBuilder = new CorsPolicyBuilder();
corsBuilder.AllowAnyHeader();
corsBuilder.AllowAnyMethod();
corsBuilder.AllowAnyOrigin();
corsBuilder.AllowCredentials();
corsBuilder.WithExposedHeaders("Location");
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", corsBuilder.Build());
});
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:51318";
options.RequireHttpsMetadata = false;
options.ApiName = "api";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseCors("CorsPolicy");
app.UseIdentityServer();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
Note that if we swith to in memory TestUser instead of persisted ApplicationUser by commenting the code in (1) and changing the code in (2) to :
//(2)
//.AddAspNetIdentity<ApplicationUser>();
.AddTestUsers(Config.GetUsers());
the whole system works and the embedded api is authenticating the user normally.
Is there something missing in this code ? In real life scenarios the api will almost always be embedded with the identity server because of cost efficiency, is there any example I can use to make it work ?
Thank you.
After digging into AspNet Identity source code, I realized that the AddIdentity extension was doing some extra work that prevents from validating the token, but without it and the AddEntityFrameworkStores method the identity managers were not set by dependency injection.
So we need to replace :
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
by a piece of code that does only dependency injection like that :
services.TryAddScoped<IUserValidator<ApplicationUser>, UserValidator<ApplicationUser>>();
services.TryAddScoped<IPasswordValidator<ApplicationUser>, PasswordValidator<ApplicationUser>>();
services.TryAddScoped<IPasswordHasher<ApplicationUser>, PasswordHasher<ApplicationUser>>();
services.TryAddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>();
services.TryAddScoped<IRoleValidator<IdentityRole>, RoleValidator<IdentityRole>>();
services.TryAddScoped<IdentityErrorDescriber>();
services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<ApplicationUser>>();
services.TryAddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>>();
services.TryAddScoped<UserManager<ApplicationUser>, AspNetUserManager<ApplicationUser>>();
services.TryAddScoped<SignInManager<ApplicationUser>, SignInManager<ApplicationUser>>();
services.TryAddScoped<RoleManager<IdentityRole>, AspNetRoleManager<IdentityRole>>();
services.TryAddScoped<IRoleStore<IdentityRole>, RoleStore<IdentityRole>>();
services.TryAddScoped<DbContext, ApplicationDbContext>();
services.TryAddScoped<IUserStore<ApplicationUser>, UserStore<ApplicationUser>>();
by doing this, the final result is a working identity server embedded in the api with AspNet Identity.

Setting Up Social Authentication in ASP.NET Core 2.0

I'm setting up social login in an ASP.NET Core 2.0 application without using Identity.
I simply want to authenticate the user through Facebook, Google and LinkedIn and receive their info. I handle storing user info myself.
Here's what I've done so far which is giving me the following error:
No authentication handler is configured to handle the scheme: facebook
Here's the Startup.cs file changes:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Added these lines for cookie and Facebook authentication
services.AddAuthentication("MyCookieAuthenticationScheme")
.AddCookie(options => {
options.AccessDeniedPath = "/Account/Forbidden/";
options.LoginPath = "/Account/Login/";
})
.AddFacebook(facebookOptions =>
{
facebookOptions.AppId = "1234567890";
facebookOptions.AppSecret = "1234567890";
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
// Added this line
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
I then have this action method where I send the user to determine the provider we're using for authenticaiton e.g. Facebook, Google, etc. This code came from my ASP.NET Core 1.1 app which is working fine.
[AllowAnonymous]
public async Task ExternalLogin(string provider, string returnUrl)
{
var properties = new AuthenticationProperties
{
RedirectUri = "Login/Callback"
};
// Add returnUrl to properties -- if applicable
if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))
properties.Items.Add("returnUrl", returnUrl);
// The ASP.NET Core 1.1 version of this line was
// await HttpContext.Authentication.ChallengeAsync(provider, properties);
await HttpContext.ChallengeAsync(provider, properties);
return;
}
I'm getting the error message when I hit the ChallangeAsync line.
What am I doing wrong?
No authentication handler is configured to handle the scheme: facebook
Scheme names are case-sensitive. Use provider=Facebook instead of provider=facebook and it should work.