Get the AccessToken inside the ProfileService in identity server 4 - asp.net-core

i have implemented the profileService for identity server 4 to add custom claims that i want. The idea is, before the claims are added to the token, i'm calling another .net api inside the public async Task GetProfileDataAsync(ProfileDataRequestContext context) method. I'm making a httpclient.getAsync("url/getInfo") to get some info that i want to add later in the claims. GetInfo api endpoint have [Authorize] so i need the Bearer {TOKEN} in the header when i'm making the call. How can i get the Token inside ProfileService or how can i secure my request to the /getInfo endpoint?
Thanks.

I secure the route with CORS. I'm allowing to have calls only from the identity server:
In API's startup file:
services.AddCors(o => o.AddPolicy("AllowIdentityServer", builder =>
{
builder.WithOrigins(Configuration["IdentityServer"])
.AllowAnyMethod()
.AllowAnyHeader();
}));
And at the API's endpoint :
[EnableCors("AllowIdentityServer")]

Related

Can't Authenticate Power Query to ASP.NET Core MVC Web App

Bottom line up front... when I try to connect Power Query from Excel to my app, I get the error described here - We were unable to connect because this credential type isn’t supported for this resource. Please choose another credential type.
Details...
My app was created from the ASP.NET Core Web App MVC template.
It uses the Microsoft Identity Platform for authentication.
// Sign-in users with the Microsoft identity platform
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options =>
{
builder.Configuration.Bind("AzureAd", options);
options.Events = new OpenIdConnectEvents();
options.Events.OnTokenValidated = async context =>
{
//Calls method to process groups overage claim.
var overageGroupClaims = await GraphHelper.GetSignedInUsersGroups(context);
};
})
.EnableTokenAcquisitionToCallDownstreamApi(options => builder.Configuration.Bind("AzureAd", options), initialScopes)
.AddInMemoryTokenCaches();
It uses Azure AD group claims for authorization.
// Adding authorization policies that enforce authorization using group values.
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("Administrators", policy => policy.Requirements.Add(new GroupPolicyRequirement(builder.Configuration["Groups:Administrators"])));
options.AddPolicy("SuperUsers", policy => policy.Requirements.Add(new GroupPolicyRequirement(builder.Configuration["Groups:SuperUsers"])));
options.AddPolicy("Deliverables", policy => policy.Requirements.Add(new GroupPolicyRequirement(builder.Configuration["Groups:Deliverables"])));
options.AddPolicy("Orders", policy => policy.Requirements.Add(new GroupPolicyRequirement(builder.Configuration["Groups:Orders"])));
options.AddPolicy("Users", policy => policy.Requirements.Add(new GroupPolicyRequirement(builder.Configuration["Groups:Users"])));
});
builder.Services.AddSingleton<IAuthorizationHandler, GroupPolicyHandler>();
In addition to the standard MVC UI, it also has an OData controller for API access.
[ApiController]
[Route("[controller]")]
//[Authorize(Policy = "Users")]
[AllowAnonymous]
public class OdataController : Controller Base
From a browser, everything works perfectly. Access is properly controlled by [Authorize(Policy = "Users")].
From Excel, however, it only works if I set the controller to [AllowAnonymous] and use the Get Data > From Web option.
If I try Get Data > From OData Feed, I get an error The given URL neither points to an OData service or a feed. Which is OK as long as Get Data > From Web works.
If I add the authorization policy, I get an error We were unable to connect because this credential type isn’t supported for this resource. Please choose another credential type.
Following the supported workflow here, Power Query is expecting a 401 response with a WWW_Authentication header containing the Azure AD login URL. Instead, it's being sent directly to the Azure login, and therefore the authentication fails. I did try adding the Power Query client IDs to my Azure AD app, but that had no effect.
I have done every search I can think of and am out of ideas. Can anyone help? Thanks!

Dynamic Authority value for multi-tenant application in .NET 5

I'm working with a multi-tenant application where we have an API project and another WEB project (razor pages). In this context, each customer (tenant) has a specific database and there is a third project, called Manager which is responsible for storing the information of each tenant. Thus, whenever a client accesses one of the projects (WEB or API) we identify the database and the parameters needed by the tenant based on their domain. For example:
client1-api.mydomain.com
client1-web.mydomain.com
client2-api.mydomain.com
client2-web.mydomain.com
Because the WEB and API projects are unique and have multiple databases (one for each tenant), Identity Server 4 was configured in the WEB project, so that whenever a client needs to connect, it logs in via the WEB project or generate a JWT token via the address {clientname}-web.mydomain.com/connect/token (for example: client1-web.mydomain.com/connect/token).
Thus, the API has an Identity Server 4 authentication and authorization configuration, so that the token generated in the WEB project can be used to access its resources.
However, we are not able to make the Authority parameter dynamically update on a per-customer basis (the tenant). The initial idea was to build a middlaware that checks the connected tenant and loads, through the Manager, the Authority parameter and set it whenever a request is made. In general, middleware works and the Authority parameter is set. However, whenever a client tries to access an API resource, the token is invalid and therefore the request is not authorized. Analyzing this problem, I verified that whenever the API receives a request, it tries to validate the token in the WEB project, but when analyzing the address of the Tenant that is making the request to the WEB project, I noticed that it is the same that is set by default in the method ConfigureServices and with that, authentication and authorization only works for this default client.
So is there any way to dynamically set the Identity Server 4 Authority in an application with this scenario?
API Startup:
public void ConfigureServices(IServiceCollection services)
{
//other settings
services.AddAuthentication(option =>
{
option.DefaultAuthenticateScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddIdentityServerAuthentication(options =>
{
options.Authority = "https://client1-web.mydomain.com";
});
services.AddAuthorization(options =>
{
options.AddPolicy("ApiScope", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("scope", "any1", "any2");
});
});
//other settings
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//other settings
app.UseAuthentication();
app.UseAuthorization();
app.UseTenantConfig();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers()
.RequireAuthorization("ApiScope");
});
//other settings
}
TENANT PARAMETER CONFIGURATION MIDDLAWARE
public async Task Invoke(HttpContext context,
ITenantParameterService tenantParameterService,
IOptions<IdentityServerAuthenticationOptions> identityServerAuthenticationOptions)
{
var tenantInfo = context.RequestServices.GetRequiredService<TenantInfo>(); //Get tenant domain
var connectionString = tenantParameterService.GetConnectionStringByTenant(tenantInfo.Address);
var authorityParameter = tenantParameterService.GetParameterByTenant(tenantInfo.Address, "Authority");
var myContext = context.RequestServices.GetService<MyDbContext>();
myContext?.Database.SetConnectionString(connectionString);
identityServerAuthenticationOptions.Value.Authority = authorityParameter;
await _next(context);
}
By executing the code, whenever a request is made in the application, middlaware will update the necessary parameters, including the connection string. However the problem is: if the token is generated in client1-web.mydomain.com to be used in client1-api.mydomain.com the authentication works perfectly due to the default configuration at startup (we use this configuration due to the boostrap of the application when it starts and even leaving null for it to be set dynamically, the authentication problem persists). Now, if the token is generated in client2-web.mydomain.com to be used in client2-api.mydomain.com I get the message saying that the token is invalid: Bearer error="invalid_token", error_description="The issuer 'https://client2-web.mydomain.com' is invalid". When decoding the token generated in this case, it was possible to notice that it is correct, because the iss is like https://client2-web.mydomain.com and therefore, the error is in the token and yes in its validation by the API.

Identity Server 4 Client Credentials for custom endpoint on token Server

I implemented a token server using Identity Server 4.
I added a custom API endpoint to the token server and struggle with the authentication. The custom endpoint is inherited from ControllerBase and has 3 methods (GET, POST, DELETE).
I intend to call the custom endpoint from within another API using a dedicated client with credentials (server to server) implemented as HttpClient in .NET Core. There is no user involved into this.
For getting the access token I use the IdentityModel DiscoveryClient and TokenEndpoint.
So in sum I did the following so far:
setup "regular" identity server and validate it works -> it works
implement custom endpoint and test it without authorizatio -> it works
add another api resource ("api.auth") with a custom scope "api.auth.endpoint1"
setup a client with client credentials allowing access to scope "api.auth.endpoint1".
implement the HttpClient and test setup -> I get an access token via the Identity Model Token Endpoint.
Now, when I call the endpoint using the HttpClient with the access token I received I get response code 200 (OK) but the content is the login page of the identity server.
The documentation of Identity Server 4 state the use of
services.AddAuthentication()
.AddIdentityServerAuthentication("token", isAuth =>
{
isAuth.Authority = "base_address_of_identityserver";
isAuth.ApiName = "name_of_api";
});
as well as the use of
[Authorize(AuthenticationSchemes = "token")]
Unfortunatly the compiler state that .AddIdentityServerAuthentication can't be found. Do I miss a special nuget?
The nugets I use on the token server so far are:
IdentityServer4 (v2.2.0)
IdentityServer4.AspNetIdentity (v2.1.0)
IdentityServer4.EntityFramework (v2.1.1)
Figured out that part. The missing nuget for AddIdentityServerAuthentication is:
IdentityServer4.AccessTokenValidation
Struggling with the authorization based on the custom scope.
Does anyone know how the security has to be configured?
Configure a client with ClientGrantTypes = client_credentials and your api like this:
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5000";
options.ApiName = "api.auth";
});
Where ApiName is the name of the resource. Please note that resource != scope. In most samples the resource name is equal to the scope name. But not in your case, where resource name is api.auth and scope name is api.auth.endpoint1.
Configure the client to request the scope.
var tokenClient = new TokenClient(disco.TokenEndpoint, clientId, secret);
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api.auth.endpoint1");
IdentityServer will lookup the Resource name and add that to the token as audience (aud) while the scope is added as claim with type scope.
This should be enough to make it work. Also check the sample project.
Custom authentication scheme and scope based policies for different access rights bundled together looks like that:
// Startup.ConfigureServices
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication("CustomAuthEndpointsAuthenticationScheme", options =>
{
options.Authority = "http://localhost:5000";
options.ApiName = "api.auth"; //IdentityServer4.Models.ApiResource.Name aka Audience
});
services.AddAuthorization(options =>
{
options.AddPolicy("Endpoint1Policy", policy => {
policy.AddAuthenticationSchemes(new string[] { "CustomAuthEndpointsAuthenticationScheme" });
policy.RequireScope("api.auth.endpoint1"); } ); //IdentityServer4.Models.Scope.Name
options.AddPolicy("Endpoint2Policy", policy => {
policy.AddAuthenticationSchemes(new string[] { "CustomAuthEndpointsAuthenticationScheme" });
policy.RequireScope("api.auth.endpoint2"); } ); //IdentityServer4.Models.Scope.Name
} );
// securing the custom endpoint controllers with different access rights
[Authorize(AuthenticationSchemes = "CustomAuthEndpointsAuthenticationScheme", Policy = "Endpoint1Policy")]
It seems not to interfere with the IdentityServer4 default endpoints nor with the ASP.NET Core Identity part.

How can I use Asp.Net Core 2.0's in-memory TestServer class for Integration Tests when my API requires an Authorization Token?

I am working on an ASP.NET Core 2.0 Web API and I want to do some integration tests using ASP.NET Core's TestServer class. I am using xUnit as my testing framework so I have created a TestServerFixture class that creates the in-memory TestServer instance and then use the TestServer's .CreateClient() to create the HTTPClient instance.
My Web API requires an OAuth2.0 Access Token from my Azure AD. I set this up using this code in my Startup.cs, ConfigureServices method:
// Add Azure AD OAUTH2.0 Authentication Services
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddAzureAdBearer(options => Configuration.Bind("AzureAd", options));
and in my controllers, I have the [Authorize] attribute on the class.
So for my Integration Tests setup, I have a method in my TestServerFixture that obtains a valid token from Azure AD and I add it to my client request header as follows;
Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", await _testServerFixture.GetAccessToken());
When I debug my integration test, I can see that the request does contain a valid access token but I am still getting a 401 Unauthorized from the API when I run my Integration Test.
After doing some digging I found several resources that talk about a similar issue with TestServer, but related to Authentication rather than Authorization, as I am experiencing. Here are links to these resources;
https://medium.com/#zbartl/authentication-and-asp-net-core-integration-testing-using-testserver-15d47b03045a
How do I integration test a ASP 5/Core Web API with [Authorize] Attributes
http://geeklearning.io/how-to-deal-with-identity-when-testing-an-asp-net-core-application/
These all talk about assigning a ClaimsPrincipal to the context.user using custom middleware. Since this is based upon Authentication rather than Authorization, I am not sure if I can do something similar for my Access Token.
I do know that in my API, I can access the HTTPContext.User and pull out the AppId value, which is part of the Access Token so it would seem that Authentication and Authorization both use the Context.User.
So, before I burn time building up my own custom middleware for this purpose, I wanted to see if anyone has already addressed this issue or perhaps are aware of a NuGet that does what I need.
EDIT - SOLUTION
I am showing this in case anyone else runs into this issue.
I ended up building the middleware that Zach Bartlett presented in his blog , but making the following changes.
public class AuthenticatedTestRequestMiddleware
{
#region Class Variables
private const string TestingAccessTokenAuthentication = "TestingAccessTokenAuthentication";
private readonly RequestDelegate _next;
#endregion Class Variables
#region Constructor(s)
public AuthenticatedTestRequestMiddleware(RequestDelegate next)
{
_next = next;
}
#endregion Constructor(s)
public async Task Invoke(HttpContext context)
{
if (context.Request.Headers.Keys.Contains("X-Integration-Testing"))
{
if (context.Request.Headers.Keys.Contains("Authorization"))
{
var token = context.Request.Headers["Authorization"].First();
var claimsIdentity = new ClaimsIdentity(new List<Claim>
{
new Claim(ClaimTypes.Authentication, token)
}, TestingAccessTokenAuthentication);
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
context.User = claimsPrincipal;
}
}
await _next(context);
}
}
There were one interesting "Gotcha".
In Zach's blog he had the code;
public const string TestingHeader = "X-Integration-Testing";
at the top of his middleware and then references the TestingHeader in the test for the key in the header collection like this;
if (context.Request.Headers.Keys.Contains(TestingHeader)
Doing it this way was failing for me until I put the string literal instead of the variable into the .Contains() clause.
Now, my integration test is passing with a 200 OK response. :)
I was able to find a solution following Zach Bartlett's blog post, and making some small changes to make it pertain to the Authentication header. The code is shown as an edit in my original post above.

write single API accessible through asp.net identity user and bearer token both

I have created asp.net mvc 6 application and configured asp.net identity users using entity framework 7 working fine. Then I added AspNet.Security.OpenIdConnect.Server token provider server that is also working fine.
Then I created an api controller as follows:
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET: api/values
[Authorize(Policy = "SomePolicy")]
[HttpGet]
public IEnumerable Get()
{
return new string[] { "value1", "value2" };
}
}
Question:
I want to configure authorization in such a way so that either bearer token or asp.net identity user is valid (and belong to some role), I want to allow the user to access API.
Here is what I tried in startup.cs:
services.AddAuthorization(options => {
// Add a new policy requiring a "scope" claim
// containing the "api-resource-controller" value.
options.AddPolicy("API", policy => {
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
policy.RequireClaim(OpenIdConnectConstants.Claims.Scope, "offline_access");
});
});
then if I add [Authorize(Policy="API")] to my api controller, that is ONLY respecting bearer tokens, not identity users.
Any help is appreciated!
policy.AddAuthenticationSchemes supports multiple schemes, so you could - in theory - do something like that:
services.AddAuthorization(options => {
options.AddPolicy("API", policy => {
policy.AddAuthenticationSchemes(
/* Scheme 1: */ JwtBearerDefaults.AuthenticationScheme,
/* Scheme 2: */ typeof(IdentityCookieOptions).Namespace + ".Application");
});
});
Note: typeof(IdentityCookieOptions).Namespace + ".Application" is the
default authentication scheme used by ASP.NET Identity 3:
https://github.com/aspnet/Identity/blob/3.0.0-rc1/src/Microsoft.AspNet.Identity/IdentityCookieOptions.cs#L61
Alternatively, you could also remove the policy.AddAuthenticationSchemes call and configure the bearer and cookies middleware to use automatic authentication (AutomaticAuthenticate = true, which is the default value for the cookies middleware, but not for the JWT middleware).
In practice, it's absolutely not recommended as it defeats the whole purpose of using bearer-only authentication: mitigating XSRF attacks. If you really want to support cookies + bearer authentication, you should strongly consider implementing XSRF countermeasures.