Skip to content

Commit f2fb759

Browse files
committed
Add JWT support for the frontend changes
1 parent d251923 commit f2fb759

7 files changed

Lines changed: 151 additions & 42 deletions

File tree

src/Fragment.NetSlum.Server/Api/Controllers/LobbiesController.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
using Fragment.NetSlum.Networking.Stores;
33
using Fragment.NetSlum.Server.Api.Models;
44
using Fragment.NetSlum.Server.Mappings;
5+
using Microsoft.AspNetCore.Authorization;
56
using Microsoft.AspNetCore.Mvc;
67

78
namespace Fragment.NetSlum.Server.Api.Controllers;
89

910
[ApiController]
1011
[Route("api/[controller]")]
12+
[Authorize]
1113
public class LobbiesController : ControllerBase
1214
{
1315
private readonly ChatLobbyStore _lobbyStore;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Fragment.NetSlum.Server.Authentication.Configuration;
2+
3+
public class DiscordAuthOptions
4+
{
5+
public string JwtSecret { get; set; } = default!;
6+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System;
2+
using System.IdentityModel.Tokens.Jwt;
3+
using System.Security.Claims;
4+
using System.Security.Cryptography;
5+
using System.Text;
6+
using Fragment.NetSlum.Server.Authentication.Configuration;
7+
using Microsoft.Extensions.Options;
8+
using Microsoft.IdentityModel.Tokens;
9+
10+
namespace Fragment.NetSlum.Server.Authentication;
11+
12+
public class DiscordJwtTokenHandler : JwtSecurityTokenHandler
13+
{
14+
private readonly IOptions<DiscordAuthOptions> _options;
15+
16+
public DiscordJwtTokenHandler(IOptions<DiscordAuthOptions> options)
17+
{
18+
_options = options;
19+
}
20+
21+
public override ClaimsPrincipal ValidateToken(string token, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
22+
{
23+
var iv = MD5.HashData(Encoding.UTF8.GetBytes(_options.Value.JwtSecret));
24+
25+
using var aes = Aes.Create();
26+
aes.KeySize = 256;
27+
aes.Key = Encoding.UTF8.GetBytes(_options.Value.JwtSecret);
28+
aes.IV = iv;
29+
aes.Mode = CipherMode.CBC;
30+
aes.Padding = PaddingMode.PKCS7;
31+
32+
return base.ValidateToken(token, validationParameters, out validatedToken);
33+
}
34+
}

src/Fragment.NetSlum.Server/Fragment.NetSlum.Server.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<PrivateAssets>all</PrivateAssets>
1717
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1818
</PackageReference>
19+
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.19" />
1920
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.19">
2021
<PrivateAssets>all</PrivateAssets>
2122
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -29,6 +30,7 @@
2930
<PackageReference Include="NSwag.AspNetCore" Version="14.5.0" />
3031
<PackageReference Include="Riok.Mapperly" Version="4.2.1" />
3132
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
33+
<PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.3.0" />
3234
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.0" />
3335
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
3436
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />

src/Fragment.NetSlum.Server/Startup.cs

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,33 @@
11
using System.Data;
2+
using System.Net.Http.Headers;
3+
using System.Security.Cryptography;
24
using System.Text;
35
using System.Text.Json;
46
using System.Text.Json.Serialization;
7+
using System.Threading.Tasks;
58
using Fragment.NetSlum.Core.CommandBus;
69
using Fragment.NetSlum.Networking.Extensions;
710
using Fragment.NetSlum.Networking.Stores;
811
using Fragment.NetSlum.Persistence;
912
using Fragment.NetSlum.Persistence.Extensions;
1013
using Fragment.NetSlum.Persistence.Interceptors;
1114
using Fragment.NetSlum.Persistence.Listeners;
15+
using Fragment.NetSlum.Server.Authentication;
16+
using Fragment.NetSlum.Server.Authentication.Configuration;
1217
using Fragment.NetSlum.Server.Converters;
1318
using Fragment.NetSlum.Server.Servers;
1419
using Fragment.NetSlum.Server.Services;
1520
using Fragment.NetSlum.TcpServer;
1621
using HealthChecks.UI.Client;
22+
using Microsoft.AspNetCore.Authentication.JwtBearer;
1723
using Microsoft.AspNetCore.Builder;
1824
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
1925
using Microsoft.EntityFrameworkCore;
2026
using Microsoft.Extensions.Configuration;
2127
using Microsoft.Extensions.DependencyInjection;
2228
using Microsoft.Extensions.Hosting;
29+
using Microsoft.Extensions.Options;
30+
using Microsoft.IdentityModel.Tokens;
2331
using Serilog;
2432

2533
namespace Fragment.NetSlum.Server;
@@ -40,7 +48,8 @@ public void ConfigureServices(IServiceCollection services)
4048
// Add failsafe to ensure the database is never executed on the original one
4149
if (connectionString!.Contains("Database=fragment;"))
4250
{
43-
throw new ConstraintException("Auto-migrations have been disabled. The connection string contains the old database information!");
51+
throw new ConstraintException(
52+
"Auto-migrations have been disabled. The connection string contains the old database information!");
4453
}
4554

4655
services
@@ -69,13 +78,49 @@ public void ConfigureServices(IServiceCollection services)
6978
.AddDbContextCheck<FragmentContext>()
7079
.AddPrivateMemoryHealthCheck(2147483648)
7180
.AddProcessAllocatedMemoryHealthCheck(2048)
72-
;
81+
;
82+
83+
services.Configure<DiscordAuthOptions>(Configuration.GetSection("Authentication"));
84+
85+
services.AddAuthentication()
86+
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme);
87+
88+
services.AddOptions<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme)
89+
.Configure<IOptions<DiscordAuthOptions>>((options, discordOptions) =>
90+
{
91+
options.TokenHandlers.Clear();
92+
options.TokenHandlers.Add(new DiscordJwtTokenHandler(discordOptions));
93+
94+
options.Events = new JwtBearerEvents
95+
{
96+
// Allow the JWT policy to accept the token from either the Authorization header, or the 'token' cookie
97+
// supplied in the request
98+
OnMessageReceived = context =>
99+
{
100+
context.Token = context.HttpContext.Request.Headers.TryGetValue("Authorization", out var bearerToken)
101+
? AuthenticationHeaderValue.Parse(bearerToken.ToString()).Parameter
102+
: context.HttpContext.Request.Cookies["netslum-token"];
103+
104+
return Task.CompletedTask;
105+
},
106+
};
107+
108+
options.MapInboundClaims = true;
109+
options.IncludeErrorDetails = true;
110+
options.TokenValidationParameters = new TokenValidationParameters
111+
{
112+
RequireSignedTokens = true,
113+
ValidateAudience = false,
114+
ValidateIssuer = false,
115+
ValidateIssuerSigningKey = true,
116+
IncludeTokenOnFailedValidation = true,
117+
ValidateLifetime = true,
118+
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(discordOptions.Value.JwtSecret)),
119+
};
120+
});
73121

74122
// Register command bus
75-
services.AddMediator(opt =>
76-
{
77-
opt.ServiceLifetime = ServiceLifetime.Transient;
78-
});
123+
services.AddMediator(opt => { opt.ServiceLifetime = ServiceLifetime.Transient; });
79124
services.AddScoped<ICommandBus, MediatorCommandBus>();
80125

81126
services.AddOpenApiDocument(doc =>
@@ -99,19 +144,30 @@ public void ConfigureServices(IServiceCollection services)
99144

100145
public void Configure(IApplicationBuilder app, IHostEnvironment env)
101146
{
147+
app.UseSerilogRequestLogging(o =>
148+
{
149+
o.MessageTemplate =
150+
"HTTP [{ClientIp}] {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms";
151+
o.IncludeQueryInRequestPath = true;
152+
});
153+
102154
app.UseRouting();
103155
app.UseCors(opt =>
104156
{
157+
opt.SetIsOriginAllowedToAllowWildcardSubdomains();
105158
opt.AllowAnyHeader();
106159
opt.AllowAnyMethod();
107-
opt.AllowAnyOrigin();
160+
opt.WithOrigins(Configuration.GetValue<string>("AllowedOrigins")?.Split(",") ?? ["*"])
161+
.AllowCredentials();
108162
});
109163

110164
app.UseHealthChecks("/api/health", new HealthCheckOptions
111165
{
112166
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse,
113167
});
114168

169+
app.UseAuthentication();
170+
app.UseAuthorization();
115171
app.UseStaticFiles();
116172
app.UseOpenApi();
117173
app.UseSwaggerUi(opt =>

src/Fragment.NetSlum.Server/serverConfig.Production.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"Serilog": {
33
"Using": [
4+
"Serilog.Enrichers.ClientInfo",
45
"Serilog.Sinks.Console"
56
],
67
"MinimumLevel": {
@@ -12,7 +13,8 @@
1213
},
1314
"Enrich": [
1415
"FromLogContext",
15-
"WithMachineName"
16+
"WithMachineName",
17+
"WithClientIp"
1618
],
1719
"WriteTo": [
1820
{
Lines changed: 41 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,44 @@
11
{
2-
"Serilog": {
3-
"Using": [
4-
"Serilog.Sinks.Console"
5-
],
6-
"MinimumLevel": {
7-
"Default": "Debug",
8-
"Override": {
9-
"Microsoft.AspNetCore": "Warning",
10-
"Microsoft.EntityFrameworkCore": "Warning"
11-
}
12-
},
13-
"Enrich": [
14-
"FromLogContext",
15-
"WithMachineName"
16-
],
17-
"WriteTo": [
18-
{
19-
"Name": "Console",
20-
"Args": {
21-
"outputTemplate": "[{Timestamp:HH:mm:ss.fff}] [{Application}] [{Level:u3}] [{RequestId}] [{SourceContext}]: {Message:lj}{NewLine}{Exception}"
2+
"Serilog": {
3+
"Using": [
4+
"Serilog.Enrichers.ClientInfo",
5+
"Serilog.Sinks.Console"
6+
],
7+
"MinimumLevel": {
8+
"Default": "Debug",
9+
"Override": {
10+
"Microsoft.AspNetCore": "Warning",
11+
"Microsoft.AspNetCore.Authentication": "Information",
12+
"Microsoft.EntityFrameworkCore": "Warning"
13+
}
14+
},
15+
"Enrich": [
16+
"FromLogContext",
17+
"WithMachineName",
18+
"WithClientIp"
19+
],
20+
"WriteTo": [
21+
{
22+
"Name": "Console",
23+
"Args": {
24+
"outputTemplate": "[{Timestamp:HH:mm:ss.fff}] [{Application}] [{Level:u3}] [{RequestId}] [{SourceContext}]: {Message:lj}{NewLine}{Exception}"
25+
}
26+
}
27+
],
28+
"Properties": {
29+
"Application": "Fragment.NetSlum.Server",
30+
"Environment": "local"
2231
}
23-
}
24-
],
25-
"Properties": {
26-
"Application": "Fragment.NetSlum.Server",
27-
"Environment": "local"
28-
}
29-
},
30-
"ConnectionStrings": {
31-
"Database": ""
32-
},
33-
"TcpServer": {
34-
"IpAddress": "0.0.0.0",
35-
"Port": 49000
36-
}
32+
},
33+
"ConnectionStrings": {
34+
"Database": ""
35+
},
36+
"TcpServer": {
37+
"IpAddress": "0.0.0.0",
38+
"Port": 49000
39+
},
40+
"Authentication": {
41+
"JwtSecret": ""
42+
},
43+
"AllowedOrigins": "http://localhost:3000,https://fragment.psrewired.com"
3744
}

0 commit comments

Comments
 (0)