diff --git a/Auths.sln b/Auths.sln
index dd66bc1..02b112d 100644
--- a/Auths.sln
+++ b/Auths.sln
@@ -14,6 +14,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jwt.Server", "Jwt.Server\Jw
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RsaKeyLoader", "RsaKeyLoader\RsaKeyLoader.csproj", "{579C8783-7E95-40F3-96F4-BEBFB40F2D38}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jwt.Application", "Jwt.Application\Jwt.Application.csproj", "{E1DC6422-489C-424C-BC3A-3844599B43C9}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -40,11 +42,16 @@ Global
{579C8783-7E95-40F3-96F4-BEBFB40F2D38}.Debug|Any CPU.Build.0 = Debug|Any CPU
{579C8783-7E95-40F3-96F4-BEBFB40F2D38}.Release|Any CPU.ActiveCfg = Release|Any CPU
{579C8783-7E95-40F3-96F4-BEBFB40F2D38}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E1DC6422-489C-424C-BC3A-3844599B43C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E1DC6422-489C-424C-BC3A-3844599B43C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E1DC6422-489C-424C-BC3A-3844599B43C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E1DC6422-489C-424C-BC3A-3844599B43C9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{3684B6DC-9301-496C-9832-FFC26084C496} = {A7B682CD-CF61-4684-977B-82E48A4051D8}
{63CE5DC9-F976-430B-8B12-50FB3DA3F872} = {A7B682CD-CF61-4684-977B-82E48A4051D8}
{BAD4810F-B60A-42F1-8176-649855B93794} = {29AFF1AF-FE18-491A-AFE1-CE2786187166}
{579C8783-7E95-40F3-96F4-BEBFB40F2D38} = {29AFF1AF-FE18-491A-AFE1-CE2786187166}
+ {E1DC6422-489C-424C-BC3A-3844599B43C9} = {29AFF1AF-FE18-491A-AFE1-CE2786187166}
EndGlobalSection
EndGlobal
diff --git a/Jwt.Application/Jwt.Application.csproj b/Jwt.Application/Jwt.Application.csproj
new file mode 100644
index 0000000..b1f5538
--- /dev/null
+++ b/Jwt.Application/Jwt.Application.csproj
@@ -0,0 +1,20 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Jwt.Application/Jwt.Application.http b/Jwt.Application/Jwt.Application.http
new file mode 100644
index 0000000..1f54de8
--- /dev/null
+++ b/Jwt.Application/Jwt.Application.http
@@ -0,0 +1,6 @@
+@Jwt.Application_HostAddress = http://localhost:5064
+
+GET {{Jwt.Application_HostAddress}}/weatherforecast/
+Accept: application/json
+
+###
diff --git a/Jwt.Application/Program.cs b/Jwt.Application/Program.cs
new file mode 100644
index 0000000..f7d5897
--- /dev/null
+++ b/Jwt.Application/Program.cs
@@ -0,0 +1,76 @@
+
+using Microsoft.IdentityModel.Protocols.OpenIdConnect;
+using Microsoft.IdentityModel.Tokens;
+
+var builder = WebApplication.CreateBuilder(args);
+
+var jwKey = await new HttpClient().GetStringAsync("https://localhost:5000/jwk");
+
+// Add services to the container.
+// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddCors();
+builder.Services.AddSwaggerGen();
+builder.Services.AddAuthentication()
+ .AddJwtBearer(b =>
+ {
+ b.TokenValidationParameters = new TokenValidationParameters()
+ {
+ // для облегчения дебага
+ ValidateAudience = false,
+ ValidateIssuer = false,
+ };
+
+ // important
+ b.Configuration = new OpenIdConnectConfiguration()
+ {
+ SigningKeys =
+ {
+ JsonWebKey.Create(jwKey)
+ }
+ };
+
+ // b.Events = new JwtBearerEvents()
+ // {
+ // OnMessageReceived = (ctx) =>
+ // {
+ // if (ctx.Request.Query.ContainsKey("token"))
+ // {
+ // ctx.Token = ctx.Request.Query["token"];
+ // }
+ //
+ // return Task.CompletedTask;
+ // }
+ // };
+ });
+builder.Services.AddAuthorization();
+
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+if (app.Environment.IsDevelopment())
+{
+ app.UseSwagger();
+ app.UseSwaggerUI();
+}
+
+app.UseHttpsRedirection();
+app.UseCors(p =>
+{
+ p.AllowAnyOrigin();
+ p.AllowAnyMethod();
+ p.AllowAnyHeader();
+});
+app.UseAuthentication();
+app.UseAuthorization();
+
+app.MapGet("/me", (HttpContext ctx) =>
+ {
+ return ctx.User.FindFirst("name").Value;
+ })
+ .RequireAuthorization()
+ .WithName("GetWeatherForecast")
+ .WithOpenApi();
+
+app.Run();
\ No newline at end of file
diff --git a/Jwt.Application/Properties/launchSettings.json b/Jwt.Application/Properties/launchSettings.json
new file mode 100644
index 0000000..bb99045
--- /dev/null
+++ b/Jwt.Application/Properties/launchSettings.json
@@ -0,0 +1,41 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:2141",
+ "sslPort": 44353
+ }
+ },
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "http://localhost:7001",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "https://localhost:7000;http://localhost:7001",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/Jwt.Application/appsettings.Development.json b/Jwt.Application/appsettings.Development.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/Jwt.Application/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/Jwt.Application/appsettings.json b/Jwt.Application/appsettings.json
new file mode 100644
index 0000000..10f68b8
--- /dev/null
+++ b/Jwt.Application/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/Jwt.Server/Jwt.Server.csproj b/Jwt.Server/Jwt.Server.csproj
index 48e39a5..6ee9f30 100644
--- a/Jwt.Server/Jwt.Server.csproj
+++ b/Jwt.Server/Jwt.Server.csproj
@@ -7,8 +7,13 @@
+
+
+
+
+
diff --git a/Jwt.Server/Program.cs b/Jwt.Server/Program.cs
index 161f695..965eefc 100644
--- a/Jwt.Server/Program.cs
+++ b/Jwt.Server/Program.cs
@@ -1,3 +1,19 @@
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Security.Cryptography;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.IdentityModel.Protocols.OpenIdConnect;
+using Microsoft.IdentityModel.Tokens;
+using RsaKeyLoader;
+
+var _users = new List<(string Name, string Id)>()
+{
+ new ValueTuple("Danil", Guid.NewGuid().ToString())
+};
+
+var _rsaKey = KeyLoader.GetGeneratedKey();
+
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
@@ -5,6 +21,42 @@ var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
+// todo: package Microsoft.AspNetCore.Authentication.JwtBearer;
+builder.Services.AddAuthentication()
+ .AddJwtBearer(b =>
+ {
+ b.TokenValidationParameters = new TokenValidationParameters()
+ {
+ // для облегчения дебага
+ ValidateAudience = false,
+ ValidateIssuer = false,
+ };
+
+ // important
+ b.Configuration = new OpenIdConnectConfiguration()
+ {
+ SigningKeys =
+ {
+ new RsaSecurityKey(_rsaKey)
+ }
+ };
+
+ // b.Events = new JwtBearerEvents()
+ // {
+ // OnMessageReceived = (ctx) =>
+ // {
+ // if (ctx.Request.Query.ContainsKey("token"))
+ // {
+ // ctx.Token = ctx.Request.Query["token"];
+ // }
+ //
+ // return Task.CompletedTask;
+ // }
+ // };
+ });
+builder.Services.AddAuthorization();
+
+
var app = builder.Build();
// Configure the HTTP request pipeline.
@@ -15,6 +67,8 @@ if (app.Environment.IsDevelopment())
}
app.UseHttpsRedirection();
+app.UseAuthentication();
+app.UseAuthorization();
var summaries = new[]
{
@@ -33,12 +87,55 @@ app.MapGet("/weatherforecast", () =>
.ToArray();
return forecast;
})
+ .RequireAuthorization()
.WithName("GetWeatherForecast")
.WithOpenApi();
+app.MapGet("/login", (string userName) =>
+{
+ var user = _users.First(x => x.Name == userName);
+ var handler = new JwtSecurityTokenHandler();
+ var key = new RsaSecurityKey(_rsaKey);
+ var token = handler.CreateToken(new SecurityTokenDescriptor()
+ {
+ Issuer = "https://localhost:5000",
+ Subject = new ClaimsIdentity(new List()
+ {
+ new Claim("sub", user.Id),
+ new Claim("name", user.Name)
+ }),
+ // important
+ SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.RsaSha256)
+ });
+ return handler.WriteToken(token);
+});
+
+app.MapGet("/jwk", () =>
+{
+ var publicKey = RSA.Create();
+ publicKey.ImportRSAPublicKey(_rsaKey.ExportRSAPublicKey(), out _);
+ var key = new RsaSecurityKey(publicKey);
+ return JsonWebKeyConverter.ConvertFromRSASecurityKey(key);
+});
+
app.Run();
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
-}
\ No newline at end of file
+}
+
+
+/* script:
+
+const token = '';
+
+fetch("/weatherforecast", {
+ method: "GET",
+ headers: {
+ "Accept": "application/json",
+ "Authorization": "Bearer " + token
+ }
+ }).then(r => r.json()).then(r => console.log(r));
+
+*/
\ No newline at end of file
diff --git a/Jwt.Server/Properties/launchSettings.json b/Jwt.Server/Properties/launchSettings.json
index c9f4735..7fb8b23 100644
--- a/Jwt.Server/Properties/launchSettings.json
+++ b/Jwt.Server/Properties/launchSettings.json
@@ -14,7 +14,7 @@
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
- "applicationUrl": "http://localhost:5079",
+ "applicationUrl": "http://localhost:5001",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
@@ -24,7 +24,7 @@
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
- "applicationUrl": "https://localhost:7256;http://localhost:5079",
+ "applicationUrl": "https://localhost:5000;http://localhost:5001",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
diff --git a/RsaKeyLoader/KeyLoader.cs b/RsaKeyLoader/KeyLoader.cs
index 7d65518..716d2a6 100644
--- a/RsaKeyLoader/KeyLoader.cs
+++ b/RsaKeyLoader/KeyLoader.cs
@@ -4,10 +4,32 @@ namespace RsaKeyLoader;
public class KeyLoader
{
- public void Generate(string path)
+ public static string PathString =
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "auth-key");
+ public static RSA GetGeneratedKey()
+ {
+ if (File.Exists(PathString))
+ {
+ return LoadKey(PathString);
+ }
+ else
+ {
+ Generate(PathString);
+ return LoadKey(PathString);
+ }
+ }
+
+ public static void Generate(string path)
{
var rsaKey = RSA.Create();
var privateKey = rsaKey.ExportRSAPrivateKey();
File.WriteAllBytes(path, privateKey);
}
+
+ public static RSA LoadKey(string path)
+ {
+ var rsaKey = RSA.Create();
+ rsaKey.ImportRSAPrivateKey(File.ReadAllBytes(path), out _);
+ return rsaKey;
+ }
}
\ No newline at end of file