Should fail

This commit is contained in:
Karsten Jeppesen
2026-08-28 08:35:19 +02:00
parent 7d75f20ee3
commit a8ae6f7b18
14 changed files with 450 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace OpenID_API.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries =
[
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
];
// Any authenticated user can access this endpoint, regardless of their role.
// This is the most basic level of authorization, which only checks if the user is authenticated.
[Authorize]
// The following lines are examples of how to restrict access to specific roles. Uncomment the desired line(s) to apply the restriction.
// This is the way you can specify multiple roles for an endpoint. You can use a comma-separated list to allow access to users with any of the specified roles.
// [Authorize(Roles = "user")] //Only users with the "user" role can access this endpoint
// [Authorize(Roles = "owner")] //Only users with the "owner" role can access this endpoint
// [Authorize(Roles = "admin")] //Only users with the "admin" role can access this endpoint
// [Authorize(Roles = "user,owner")] //Only users with either the "user" or "owner" role can access this endpoint
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var xxx = User;
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}

View File

@@ -0,0 +1,30 @@
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
# This stage is used when running from VS in fast mode (Default for Debug configuration)
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
# This stage is used to build the service project
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["OpenID_API/OpenID_API.csproj", "OpenID_API/"]
RUN dotnet restore "./OpenID_API/OpenID_API.csproj"
COPY . .
WORKDIR "/src/OpenID_API"
RUN dotnet build "./OpenID_API.csproj" -c $BUILD_CONFIGURATION -o /app/build
# This stage is used to publish the service project to be copied to the final stage
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./OpenID_API.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "OpenID_API.dll"]

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>2d959f13-fec1-48ad-a7f1-455a8d4830d3</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.11" />
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.22.0" />
<PackageReference Include="Microsoft.OpenApi" Version="2.12.2" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.2.3" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,6 @@
@OpenID_API_HostAddress = http://localhost:5007
GET {{OpenID_API_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@@ -0,0 +1,225 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi;
using System.IdentityModel.Tokens.Jwt;
using System.Text;
// Required NuGet Packages
// Microsoft.AspNet.WebApi.Core
// Microsoft.AspNetCore.Authentication.JwtBearer
// Microsoft.AspNetCore.Authentication.OpenIdConnect
// Microsoft.IdentityModel.Protocols.OpenIdConnect
namespace OpenID_API.OpenIDConnect
{
public class OpenIDConnectUtils
{
// Configuration priority from highest to lowest
// Highest: Command line arguments
// : Non-prefixed environment variables
// : User secrets from the .NET User Secrets Manager
// : Any appsettings.{ ENVIRONMENT_NAME }.json files
// : The appsettings.json file
// Lowest : Fallback to the host configuration
/// <summary>
/// Setting up OpenIDConnect authentication (Program.cs)
/// </summary>
/// <param name="builder">WebApplicationBuilder</param>
public void ConfigureBuilder(WebApplicationBuilder builder)
{
// Add services to the container.
MyConfiguration.Set(builder.Configuration);
// >>> This adds the authentication service
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie()
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Authority = builder.Configuration["OpenIDRealmURI"];
options.Audience = builder.Configuration["OpenIDClient"];
//options.TokenValidationParameters.RoleClaimType = "roles"; // adjust if your roles claim is named differently
options.TokenValidationParameters = new TokenValidationParameters
{
RoleClaimType = "roles", // 👈 IMPORTANT
NameClaimType = "name",
ValidateIssuer = true,
ValidateAudience = true
};
options.MapInboundClaims = false; // prevent automatic claim type mapping
options.RequireHttpsMetadata = true; // set to false only for development
options.IncludeErrorDetails = true;
// Map claim types if needed
//options.TokenValidationParameters = new TokenValidationParameters
//{
// NameClaimType = "name",
// RoleClaimType = "roles"
//};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
// Optional: log the token or handle custom token retrieval
var token = context.Request.Headers["Authorization"].ToString();
string path = context.Request.Path;
if (!string.IsNullOrEmpty(token))
{
Console.WriteLine("Access token");
Console.WriteLine($"URL: {path}");
Console.WriteLine($"Token: {token}\r\n");
}
else
{
Console.WriteLine("Access token");
Console.WriteLine("URL: " + path);
Console.WriteLine("Token: No access token provided\r\n");
}
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
var loggerFactory = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger("Api.Authorization");
var claims = context?.Principal?.Claims;
if (claims is null || !claims.Any())
{
logger.LogWarning("Claims null or empty");
return Task.CompletedTask;
}
var claimsBuilder = new StringBuilder();
claimsBuilder.AppendLine("User Claims:");
foreach (var claim in claims)
{
claimsBuilder.AppendLine($"[{claim.Type}] - [{claim.Value}]");
}
logger.LogTrace("{Claims}", claimsBuilder.ToString());
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
// Optional: log authentication failures
Console.WriteLine("Authentication failed: " + context.Exception.Message);
return Task.CompletedTask;
}
};
});
// <<< End of authentication service setup
// >>> This adds the authorization policies
builder.Services
.AddAuthorizationBuilder()
.AddPolicy("read_access", builder =>
{
// claim, list of acceptable values
builder.RequireClaim("myClaim", "MyClaimValueRO1", "MyClaimValueRO2");
})
.AddPolicy("write_access", builder =>
{
builder.RequireClaim("myClaim", "MyClaimValueRW1", "MyClaimValueRW2");
})
.AddPolicy("Admin", policy => policy.RequireClaim("roles", "admin", "warlock"));
// <<< End of authorization policies setup
ConfigureBuilderOpenAPI(builder);
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
options.KnownIPNetworks.Clear();
options.KnownProxies.Clear();
});
}
private void ConfigureBuilderOpenAPI(WebApplicationBuilder builder)
{
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
// Add Swagger with Authorization option
// https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/HEAD/docs/configure-and-customize-swaggergen.md#add-security-definitions-and-requirements
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "My API",
Version = "v1"
});
options.AddSecurityDefinition("bearer", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT",
Description = "JWT Authorization header using the Bearer scheme."
});
options.AddSecurityRequirement(document => new OpenApiSecurityRequirement
{
[new OpenApiSecuritySchemeReference("bearer", document)] = []
});
});
// Add Swagger with Authorization option
}
private void ConfigureAppOpenAPI(WebApplication app)
{
app.MapSwagger();
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("v1/swagger.json", "Your Name Here");
});
}
/// <summary>
/// </summary>
/// <param name="app">WebApplication</param>
public void ConfigureApp(WebApplication app)
{
if (app.Environment.IsDevelopment()) ConfigureAppOpenAPI(app);
}
public JwtSecurityToken GetJwtPayload(HttpContext myContext)
{
var handler = new JwtSecurityTokenHandler();
return handler.ReadJwtToken(myContext.GetTokenAsync("access_token").Result);
}
public string GetJwtClaim(HttpContext myContext, string theClaim)
{
JwtSecurityToken jwtPayload = GetJwtPayload(myContext);
return jwtPayload.Claims.FirstOrDefault(claim => claim.Type == theClaim).Value;
}
}
static public class MyConfiguration
{
static ConfigurationManager _config;
static public void Set(ConfigurationManager config)
{
_config = config;
}
static public ConfigurationManager Get()
{
return _config;
}
}
}

View File

@@ -0,0 +1,25 @@
using OpenID_API.OpenIDConnect;
var builder = WebApplication.CreateBuilder(args);
// Configure for OpenID
OpenIDConnectUtils oidcConfig = new();
oidcConfig.ConfigureBuilder(builder);
// Add services to the container.
builder.Services.AddControllers();
var app = builder.Build();
// Configure for OpenID
oidcConfig.ConfigureApp(app);
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();

View File

@@ -0,0 +1,35 @@
{
"profiles": {
"http": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:8887"
},
"https": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:8888"
},
"Container (Dockerfile)": {
"commandName": "Docker",
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
"environmentVariables": {
"ASPNETCORE_HTTPS_PORTS": "8081",
"ASPNETCORE_HTTP_PORTS": "8080"
},
"publishAllPorts": true,
"useSSL": true
}
},
"$schema": "https://json.schemastore.org/launchsettings.json"
}

View File

@@ -0,0 +1,13 @@
namespace OpenID_API
{
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,16 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.AspNetCore.Authentication": "Information",
"Microsoft.AspNetCore.Authorization": "Information",
"Api.Authorization": "Trace"
}
},
"AllowedHosts": "*",
"Comment1": "Configuration for OpenID Connect authentication",
"OpenIDRealmURI": "https://auth.a.ucnit.eu/realms/xOIDCx",
"OpenIDClient": "Alice",
"OpenIDSecret": "JvDnso8O773pE9ENJdRhsrJd5pVD5Q86"
}