IdentityServer and CertificateValidation ASP.NET Core - c#

I'm building an ASP.NET Core 3.1 Web API. For Authentication I'm using right now IdentyServer4. Now I got the additional requirement to apply Mutual TLS. When applying this this results in the following code in my Startup.cs (using: IdentityServer4.AccessTokenValidation (3.0.1) and Microsoft.AspNetCore.Authentication.Certificate (3.1.3)):
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "<baseaddress>";
options.ApiName = "<API>";
});
services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(options =>
{
...
}
Now I'm facing the issue that my ClaimsPrincipal is overwritten by the Microsoft.AspNetCore.Authentication.Certificate. This is not desired because we use the claims from IdentityServer4 for allowing/denying functionality.
What's recommended in this situation?

These two lines are the problem in your code
services.AddAuthentication("Bearer")
services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
You are overriding the default authentication scheme to certificate.
You should just add your certificate authentication
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "<baseaddress>";
options.ApiName = "<API>";
})
.AddCertificate(options =>
{
...
}
And use this code to authenticate with certificate
httpContext.AuthenticateAsync(CertificateAuthenticationDefaults.AuthenticationScheme);

Related

Authenticating tokens from multiple sources (e.g Cognito and Azure)

We're working on an API that allows users authenticating through a number of different providers. The individual providers are not an issue, but using them together is proving to be a challenge.
It seems that adding more than 1 provider throws a InvalidOperationException with "Scheme already exists: Bearer" when the application starts up.
Below is the ConfigureServices function from Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "Value";
options.Audience = "Value";
})
.AddMicrosoftIdentityWebApi(options =>
{
Configuration.Bind("AzureAdB2C", options);
options.TokenValidationParameters.NameClaimType = "name";
},
options => { Configuration.Bind("AzureAdB2C", options); });
services.AddControllers();
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder(
JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
});
}
I'm using the Microsoft example for authenticating with Azure AD as a starting point. Removing either the AddJwtBearer or AddMicrosoftIdentityWebApi calls works fine, but I need to configure both providers for our use-case.
Is there a way to do this with .NET Core 3.1 or up?
We can't register 2 authentications under same scheme name. So we need to register the 2 authentication schemes with different name(or one with default and another with a scheme name)
In my case I am registering 2 authentication schemes:
My own JWT scheme with our app name "MyAppName",
Azure AD authentication with JWT default scheme JwtBearerDefaults.AuthenticationScheme, as I was not able to add it with custom scheme name.
I was able to make it work with the following configuration:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer("MyAppName",options =>
{
options.Authority = "Value";
options.Audience = "Value";
})
.AddMicrosoftIdentityWebApi(Configuration, "AzureAd");
and Authorization configuration:
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder(
"MyAppName",
JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
});

How to use dynamic ApiSecret in securing an api with IdentityServer middleware

I'm securing an API with the IdentityServer middle-ware talking to an identity server.
In the API I have code similar to:
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(
options =>
{
options.Authority = identityServerSettings.AuthorityUri;
options.ApiName = identityServerSettings.ApiName;
options.ApiSecret = secret;
}
);
I want to setup the middle-ware with an ApiSecret, but I want to get this secret from hashicorp's vault.
I want to configure the middle-ware so that it retrieves a values and sets the ApiSecret on each request.
Something like:
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(
options =>
{
options.Authority = identityServerSettings.AuthorityUri;
options.ApiName = identityServerSettings.ApiName;
options.ApiSecret = () => GetSecret();
}
);
The intention for this would be to enable service restart-less key rotation by hashicorp's vault.
Both this API and the IdentityServer would request the secret from vault and this would allow the rotation of secrets without the restarting of the API or IdentityServer services.
Is this possible? How would one do it?

Can't protect internal api using IdentityServer 4 (500 error)

A bit of background, I have an IdenityServer 4 project that I use to protect access to an mvc project that I have (Using ASP.NET Identity).
Now what I also wanted was an api that is protected via client credentials that returns some information.
What I did was make a new core api project and this was working fine with the client protection, however, I wanted to move the api so it was within IdenityServer.
e.g. localhost:5000/api/info/getinfo
Now I have moved the code over I get a 500 error when I use the attribute [Authorize(AuthenticationSchemes = "Bearer")]
I can use the DiscoveryClient to get a successful token using the credentials but can't with any request unless they are not authorized.
So in ID I set up my start up like this:
services.AddMvc();
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
// Configure identity server with in-memory stores, keys, clients and scopes
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryPersistedGrants()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryClients(Config.GetClients(Configuration))
.AddInMemoryApiResources(Config.GetApiResources())
.AddAspNetIdentity<ApplicationUser>();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = Configuration.GetSection("Authority").Value;
options.RequireHttpsMetadata = false;
options.ApiName = "IdentityInfoApi";
});
And then for my api call that is protected I tag it with: [Authorize(AuthenticationSchemes = "Bearer")]
but this returns me a 500 error, now when I use the tag: [Authorize] it works but that's because the user is logged into the mvc app and the response is an html page and not the json object i want.
At the moment I'm using a unit test to hit the api and the code looks like this:
var client = new HttpClient();
var disco = DiscoveryClient.GetAsync("https://localhost:5000").Result;
var tokenClient = new TokenClient(disco.TokenEndpoint, "client", "secret");
var tokenResponse = tokenClient.RequestClientCredentialsAsync("IdentityInfoApi").Result;
client.SetBearerToken(tokenResponse.AccessToken);
var response = client.GetAsync("https://localhost:5000/api/info/getinfo").Result;
if (!response.IsSuccessStatusCode)
{
var userResult = response.Content.ReadAsStringAsync().Result;
var result = JsonConvert.DeserializeObject<PagedUserList>(userResult);
Assert.NotNull(result);
}
Is there something wrong with my setup of ID, the client code or can you not use ID in this way?
Thank's for your help
After a lot of playing around I believe I found the fix.
You must define AddAuthentication() before AddIdentity() or in other words you must configure the api before Identity Server
It's fine to do it any way round if your api is external but not if it is within the Identity Server app it'self.
My new code looks like this:
//Configure api
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "https://localhost:5000";
options.RequireHttpsMetadata = false;
options.ApiName = "IdentityInfoApi";
});
//end
services.AddIdentity<ApplicationUser, IdentityRole>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.Configure<AuthMessageSenderOptions>(Configuration.GetSection("SMTP"));
services.AddMvc();
// Configure identity server with in-memory stores, keys, clients and scopes
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryPersistedGrants()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryClients(Config.GetClients(Configuration))
.AddInMemoryApiResources(Config.GetApiResources())
.AddAspNetIdentity<ApplicationUser>();
Hope this helps anyone else

IdentityServer4 with ASP.NET Core 2.1 Identity - Error loading external login information

I'm trying to get IdentityServer4 get to work with ASP.NET Core Identity using my own UserStore for SSO. While the guides seem rather straightforward, and the authentication process itself seems to work, in the application (another ASP.NET Core MVC application) I get the following error:
Error loading external login information
My setup is as follows:
For the ASP.NET MVC application (the client):
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies", options =>
{
options.ExpireTimeSpan =TimeSpan.FromMinutes(30);
})
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "https://localhost:5001/";
options.ClientId = "clientId";
options.ClientSecret = "secret";
options.SaveTokens = true;
options.ResponseType = "code id_token";
options.Scope.Add(IdentityServerConstants.StandardScopes.Profile);
options.Scope.Add(IdentityServerConstants.StandardScopes.Email);
options.Scope.Add(IdentityServerConstants.StandardScopes.OfflineAccess);
options.GetClaimsFromUserInfoEndpoint = true;
});
For the IdentityServer4 application:
services.AddScoped<UserManager<User>, MyUserManager>();
services.AddIdentity<User, UserGroup>()
.AddRoleStore<MyRoleStore>()
.AddUserStore<MyUserStore>()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserIdClaimType = JwtClaimTypes.Subject;
options.ClaimsIdentity.UserNameClaimType = JwtClaimTypes.Name;
options.ClaimsIdentity.RoleClaimType = JwtClaimTypes.Role;
});
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(OpenIDConfig.GetApiResources())
.AddInMemoryIdentityResources(OpenIDConfig.GetIdentityResources())
.AddInMemoryClients(OpenIDConfig.GetClients())
.AddResourceOwnerValidator<ResourceOwnerPasswordValidator<User>>()
.AddProfileService<ProfileService<User>>();
The main issue is that I don't know where to even start looking for why there is a problem with this after a successful authentication flow.
Since it does help me - but I've almost missed anserw which was in comment I'll put it here. Comment is made by #Thomas Levesque and you might thank him for that ansertw ;-)
in fact the problem was that I was changing the default SignInScheme
in the Google options. It has to be IdentityConstants.ExternalScheme,
because that's what SignInManager.GetExternalLoginInfoAsync uses.

How to support multiple authentication scheme in Web API Core 2?

I'm developing a web api core 2.0 project.
I need support two authorization types: jwt and basic.
Inside my ConfigureServices method I've added this code:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer((options) =>
{
options.Authority = $"...";
options.Audience = "...";
});
services.AddAuthentication(BasicAuthenticationDefaults.AuthenticationScheme)
.AddBasicAuthentication(credentials =>
Task.FromResult(
credentials.username == "username"
&& credentials.password == "password"));
Inside my Configure method I've added this code:
app.UseAuthentication();
app.UseMvc();
And finally I've added AuthorizeAttribute on my controller:
[Authorize]
public class MioController : Controller
{ ... }
Actually work only the last authentication specified on ConfigureServices.
How can I support both authentication types?
Thanks
Note: I'm using this NuGet package for basic authentication Bazinga.AspNetCore.Authentication.Basic.
try Adding your authentication service in one chain
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer((options) =>
{
options.Authority = $"...";
options.Audience = "...";
})
.AddBasicAuthentication(credentials =>
{
Task.FromResult(credentials.username == "username" && credentials.password == "password"));
}
and also on AuthorizeAttribute you can specify which Scheme you want to authenticate the request with
[Authorize(AuthenticationSchemes = BasicAuthenticationDefaults.AuthenticationScheme + ", " + JwtBearerDefaults.AuthenticationScheme)]

Categories