feat(core): add jwt validation to Authorization middleware

This commit is contained in:
2026-06-17 11:58:22 +02:00
parent 8addf1ceeb
commit 0a64890474
4 changed files with 80 additions and 6 deletions
+1
View File
@@ -12,6 +12,7 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.19.1"/>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.2"/> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.2"/>
</ItemGroup> </ItemGroup>
+64 -5
View File
@@ -1,17 +1,23 @@
using System.Security.Cryptography;
using Core.Models; using Core.Models;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
namespace Core.Middlewares; namespace Core.Middlewares;
public class AuthorizationMiddleware(RequestDelegate next) public class AuthorizationMiddleware(RequestDelegate next, IServiceProvider serviceProvider)
{ {
public Task Invoke(HttpContext httpContext, CookiePlayerData playerData) public async Task Invoke(HttpContext httpContext, CookiePlayerData playerData)
{ {
var cookieHeader = httpContext.Request.Headers.Cookie.ToString(); var cookieHeader = httpContext.Request.Headers.Cookie.ToString();
if (cookieHeader.Trim() == "") if (cookieHeader.Trim() == "")
{ {
httpContext.Response.StatusCode = 401; httpContext.Response.StatusCode = 401;
return Task.CompletedTask; await Task.CompletedTask;
return;
} }
var hasSid = httpContext.Request.Cookies.ContainsKey("sid"); var hasSid = httpContext.Request.Cookies.ContainsKey("sid");
@@ -21,13 +27,66 @@ public class AuthorizationMiddleware(RequestDelegate next)
if (!(hasSid && hasUser && hasVersion)) if (!(hasSid && hasUser && hasVersion))
{ {
httpContext.Response.StatusCode = 401; httpContext.Response.StatusCode = 401;
return Task.CompletedTask; await Task.CompletedTask;
return;
}
var token = new JsonWebToken(httpContext.Request.Cookies["sid"]!.Split(":")[1]);
var memoryCache = serviceProvider.GetRequiredService<IMemoryCache>();
var validationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateLifetime = true,
ValidateActor = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "authentication",
IssuerSigningKeyResolver = (_, _, _, _) =>
{
return memoryCache.GetOrCreate("minecraftPublicKeys", entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1);
var minecraftPublicKeys = new HttpClient()
.GetFromJsonAsync<MinecraftPublicKeys>("https://api.minecraftservices.com/publickeys")
.GetAwaiter()
.GetResult();
var signingKeys = new List<SecurityKey>();
if (minecraftPublicKeys == null)
{
Console.WriteLine("Couldn't fetch Minecraft Public Keys");
return signingKeys;
}
var publicKeyBytes = Convert.FromBase64String(minecraftPublicKeys.AuthenticationKeys[0].PublicKey);
var rsa = RSA.Create();
rsa.ImportSubjectPublicKeyInfo(publicKeyBytes, out _);
signingKeys.Add(new RsaSecurityKey(rsa.ExportParameters(false)));
return signingKeys;
});
}
};
var validationResult = await new JsonWebTokenHandler().ValidateTokenAsync(token, validationParameters);
if (!validationResult.IsValid)
{
httpContext.Response.StatusCode = 401;
await Task.CompletedTask;
return;
} }
playerData.Uuid = httpContext.Request.Cookies["sid"]!.Split(":")[2]; playerData.Uuid = httpContext.Request.Cookies["sid"]!.Split(":")[2];
playerData.Name = httpContext.Request.Cookies["user"]!; playerData.Name = httpContext.Request.Cookies["user"]!;
playerData.Version = httpContext.Request.Cookies["version"]!; playerData.Version = httpContext.Request.Cookies["version"]!;
return next(httpContext); await next(httpContext);
} }
} }
+13
View File
@@ -0,0 +1,13 @@
namespace Core.Models;
public class MinecraftPublicKeys
{
public required List<PublicKeyWrapper> ProfilePropertyKeys { get; set; }
public required List<PublicKeyWrapper> PlayerCertificateKeys { get; set; }
public required List<PublicKeyWrapper> AuthenticationKeys { get; set; }
}
public class PublicKeyWrapper
{
public required string PublicKey { get; set; }
}
+2 -1
View File
@@ -8,6 +8,7 @@ var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<CookiePlayerData>(); builder.Services.AddScoped<CookiePlayerData>();
builder.Services.AddMemoryCache();
builder.Services.AddControllers(); builder.Services.AddControllers();
var connectionString = builder.Configuration.GetValue<string>("Database"); var connectionString = builder.Configuration.GetValue<string>("Database");
@@ -32,7 +33,7 @@ if (!app.Environment.IsDevelopment())
app.UseRouting(); app.UseRouting();
app.UseMiddleware<AuthorizationMiddleware>(); app.UseMiddleware<AuthorizationMiddleware>(app.Services);
app.UseMiddleware<CheckRealmAccessMiddleware>(); app.UseMiddleware<CheckRealmAccessMiddleware>();
app.MapControllers(); app.MapControllers();