I was trying to update my ASP.NET Core (.NET 6) Web API to use Steeltoe 3.x (it currently uses 2.1.1), but I've run into a breaking problem with JWT authorization. After some checking, the same problem occurs when updating to 2.5.5.
In my project I have this code:
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseCloudFoundryHosting().AddCloudFoundry();
//other code
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddCloudFoundryJwtBearer(builder.Configuration);
It works fine
I tried to change it to this code, when updating to Steeltoe 3.2.2:
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseCloudHosting().AddCloudFoundryConfiguration();
//other code
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddCloudFoundryJwtBearer(builder.Configuration);
But it didn't work, there were no claims in the AuthorizationContext
Then I tried to use the first code, but using Steeltoe 2.5.5, but encountered the same problem.
Is there some additional configuration required in the newer versions of Steeltoe?
UPDATE
I apologize, now I have updated the code.
In Steeltoe 3.x, you could try the code below.
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseCloudHosting().AddCloudFoundryConfiguration();
//other code
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.Authority = "https://your-authorization-server-url";;
options.Audience = "your-api-or-resource-name";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false
};
});
Related
How to configure NodaTime serialization for System.Text.Json in Blazor WASM .Net 6? In a WebApi you would do
builder.Services.AddControllers().AddJsonOptions(settings => settings.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb));
but there are no controllers in Blazor WASM.
This does not work either:
builder.Services.Configure<JsonSerializerOptions>(options => options.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb));
var options = new JsonSerializerOptions().ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
and providing to HttpClient does not work either.
This works.
JsonSerializerOptions default = new JsonSerializerOptions(JsonSerializerDefaults.Web).ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
This is a hack and a workaround, but I ended up using Newtonsoft for this
//Should work but doesn't
var options = new JsonSerializerOptions().ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
var customClassList = await Http1.GetFromJsonAsync<List<customClass>>($"api/CustomClass", options);
//First: Get the raw json string
var response = await Http1.GetAsync($"api/CustomClass");
var content = await response.Content.ReadAsStringAsync();
//Second: Deserialize with Newtonsoft
var settings = new JsonSerializerSettings();
settings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
var customClassList = JsonConvert.DeserializeObject<List<CustomClass>>(content, settings);
//Doesn't work (System.Text.Json)
var customClassList = JsonSerializer.Deserialize<List<CustomClass>>(content, options);
I suspect this has something to do with Blazor/WASM since the above code is basically the default example for json and nodatime with System.Text.Json. However, I don't get any weird errors or exceptions with in my Blazor app. It just doesn't deserialize properly.
We are starting a new web application that will be hosted in our customers infrastructure. Since this is a solution that will be here for a while and serve as a base for a lot of future products, we wanted to have a future proof security, that would be future poff (SSO / MFA) but this is something for like in 3 years. It's important for our customer that we rely on some standards, so I thought about using OpenId.
The solution will be based on ASP.NET Core + Angular. So I found out there was ASP.NET Core Identity, already compatible with OpenID Connect, but then I saw here that Microsoft recommends Duende Identity Server (IdentityServer4).
The problem is that we are a small team, building a small application, but in a big enterprise, so will have to go for the licensed version. The other problem is that since its our customers that deploy themself the application, we do not control how many servers will be deployed, therefore we would have to opt for an "enterprise" subscription, which is totally out of our budget.
Despite this, we were hoping that we could still use ASP.NET Core Identity to connect to different sources of users, manage permissions for our app, use the attributes on our controllers.
So, how to use ASP.NET Core Identity, without using IdentityServer?
According to the MSFT docs
ASP.NET Core Identity adds user interface (UI) login functionality to
ASP.NET Core web apps.To secure web APIs and SPAs, use one of the
following:
Azure Active Directory Azure
Active Directory B2C (Azure AD B2C)
IdentityServer4
So they first offer their cloud solutions.Identityserver4 free version is still supported though till the .Net Core 3.1 EOL.
As a free-free option without any predefined EOL, you can try this OpenIddict sample as a start point for your solution, however it has a bit more gaps to be filled in yourself.
And here is an explanation why MSFT don't offer it in their docs (spoiler: see above)
You can use pure ASP.NET Core without IdentityServer.
It's quite easy if you're using the same backend for authentication and API.
Example (copied from source):
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey
(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = false,
ValidateIssuerSigningKey = true
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseHttpsRedirection();
app.MapGet("/security/getMessage", () => "Hello World!").RequireAuthorization();
app.MapPost("/security/createToken",
[AllowAnonymous] (User user) =>
{
if (user.UserName == "joydip" && user.Password == "joydip123")
{
var issuer = builder.Configuration["Jwt:Issuer"];
var audience = builder.Configuration["Jwt:Audience"];
var key = Encoding.ASCII.GetBytes
(builder.Configuration["Jwt:Key"]);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim("Id", Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Email, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti,
Guid.NewGuid().ToString())
}),
Expires = DateTime.UtcNow.AddMinutes(5),
Issuer = issuer,
Audience = audience,
SigningCredentials = new SigningCredentials
(new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha512Signature)
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
var jwtToken = tokenHandler.WriteToken(token);
var stringToken = tokenHandler.WriteToken(token);
return Results.Ok(stringToken);
}
return Results.Unauthorized();
});
app.UseAuthentication();
app.UseAuthorization();
app.Run();
See also:
Similar thread
Thanks to this answer: Integration test and hosting ASP.NET Core 6.0 without Startup class
I have been able to perform integration tests with API.
WebApplicationFactory<Program>? app = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
});
});
HttpClient? client = app.CreateClient();
This has worked using the appsettings.json from the API project. Am now trying to use integrationtestsettings.json instead using:
IConfiguration configuration = new ConfigurationBuilder()
.SetBasePath(ProjectDirectoryLocator.GetProjectDirectory())
.AddJsonFile("integrationtestsettings.json")
.Build();
WebApplicationFactory<Program>? app = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder =>
{
builder.ConfigureAppConfiguration(cfg => cfg.AddConfiguration(configuration));
builder.ConfigureServices(services =>
{
});
});
_httpClient = app.CreateClient();
I have inspected the configuration variable and can see the properties loaded from my integrartiontestsettings.json file. However, the host is still running using the appsettings.json from the server project.
Previously, in .Net5, I was using WebHostBuilder and the settings were overridden by test settings.
WebHostBuilder webHostBuilder = new();
webHostBuilder.UseStartup<Startup>();
webHostBuilder.ConfigureAppConfiguration(cfg => cfg.AddConfiguration(_configuration));
But cannot get the test settings to apply using the WebApplicationFactory.
It seems the method has changed.
Changing:
builder.ConfigureAppConfiguration(cfg => cfg.AddConfiguration(configuration));
To:
builder.UseConfiguraton(configuration);
has done the trick.
builder.ConfigureAppConfiguration, now it's configuring the app (after your WebApplicationBuilder.Build() is called) and your WebApplication is created.
You need to "inject" your configurations before the .Build() is done. This is why you need to call UseConfiguraton instead of ConfigureAppConfiguration.
I am trying to use Microsoft.Extensions.DependencyInjection's method AddAuthentification on a ASP .NET Core 2.1 project though it seems it does not exist in the package when trying to resolve it.Instead i have an extension AddAuthentificationCore which does not have the same signature.
According to the documentation it should be present for .NET Core 2.1.
P.S I want to use the AddAuthentification like :
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options => {
options.LoginPath = "/login";
options.AccessDeniedPath = "/AccessDenied";
options.Events.OnRedirectToLogin = (context) => {
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
});
That's not part of Microsoft.Extensions.DependencyInjection. It simply uses that namespace so no explicit additional using statement is required to have the extensions show up. It will actually live in one of the Microsoft.AspNetCore.Authentication NuGet packages.
I have a relative complex project which is .net core 1.1 (.net framework).
The reason why choose .Net Framework was the database is Oracle and the Core Framework didn't support Oracle Entity Data Model. Anyway, the application has been working well in production about a year now.
I am trying to upgrade this project to .net core 2 now. According to the documentation here I modified the Authentication register in Startup, the project compiles.
old code:
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationScheme = "Administration",
LoginPath = new PathString("/Login/"),
AccessDeniedPath = new PathString("/Login/"),
AutomaticAuthenticate = true,
CookieSecure = CookieSecurePolicy.SameAsRequest,
AutomaticChallenge = true
});
updated code:
app.UseAuthentication();
services.AddAuthentication("Administration").AddCookie(options =>
{
options.LoginPath = new PathString("/Login/");
options.AccessDeniedPath = new PathString("/Login/");
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
});
I am getting an error page 500:
Can anyone please help?
After look at the stdout log, the problem was ConfigureServices can only take IServiceCollection. I was trying to inject another parameter in, but the project compiled.
I hope this can be found by intelligent sense or IDE run time