-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Net8JwtBearerSample project and update OpenAPI config
Updated SimpleAuthentication.sln to include Net8JwtBearerSample. Replaced Swashbuckle with OpenAPI in JwtBearerSample.csproj. Refactored Program.cs for OpenAPI configuration. Simplified OpenApiSecurityRequirement and OpenApiResponse in Helpers.cs. Renamed SwaggerExtensions to OpenApiExtensions in OpenApiExtensions.cs. Added appsettings.Development.json and appsettings.json for config. Added Net8JwtBearerSample.csproj with necessary dependencies. Added Program.cs for Net8JwtBearerSample setup. Added ApplicationAuthenticationSchemeProvider.cs for custom auth logic. Added ClaimsTransformer.cs for custom claims transformation. Added launchSettings.json for JwtBearerSample project.
- Loading branch information
1 parent
9ba1a77
commit ff576f1
Showing
13 changed files
with
383 additions
and
61 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
64 changes: 64 additions & 0 deletions
64
...MinimalApis/Net8JwtBearerSample/Authentication/ApplicationAuthenticationSchemeProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
using Microsoft.AspNetCore.Authentication; | ||
using Microsoft.Extensions.Options; | ||
using SimpleAuthentication.ApiKey; | ||
using SimpleAuthentication.JwtBearer; | ||
|
||
namespace JwtBearerSample.Authentication; | ||
|
||
public class ApplicationAuthenticationSchemeProvider(IHttpContextAccessor httpContextAccessor, IOptions<AuthenticationOptions> options, | ||
IOptions<JwtBearerSettings> jwtBearerSettingsOptions, IOptions<ApiKeySettings> apiKeySettingsOptions) : AuthenticationSchemeProvider(options) | ||
{ | ||
private readonly JwtBearerSettings jwtBearerSettings = jwtBearerSettingsOptions.Value; | ||
private readonly ApiKeySettings apiKeySettings = apiKeySettingsOptions.Value; | ||
|
||
private async Task<AuthenticationScheme?> GetRequestSchemeAsync() | ||
{ | ||
var request = (httpContextAccessor.HttpContext?.Request) ?? throw new ArgumentNullException("The HTTP request cannot be retrieved."); | ||
|
||
// For API requests, use Jwt Bearer Authentication. | ||
if (request.IsApiRequest()) | ||
{ | ||
return await GetSchemeAsync(jwtBearerSettings.SchemeName); | ||
} | ||
|
||
// For Services requests, use Api Key Authentication. | ||
if (request.IsServiceRequest()) | ||
{ | ||
return await GetSchemeAsync(apiKeySettings.SchemeName); | ||
} | ||
|
||
// For the other requests, return null to let the base methods | ||
// decide what's the best scheme based on the default schemes | ||
// configured in the global authentication options. | ||
return null; | ||
} | ||
|
||
public override async Task<AuthenticationScheme?> GetDefaultAuthenticateSchemeAsync() => | ||
await GetRequestSchemeAsync() ?? | ||
await base.GetDefaultAuthenticateSchemeAsync(); | ||
|
||
public override async Task<AuthenticationScheme?> GetDefaultChallengeSchemeAsync() => | ||
await GetRequestSchemeAsync() ?? | ||
await base.GetDefaultChallengeSchemeAsync(); | ||
|
||
public override async Task<AuthenticationScheme?> GetDefaultForbidSchemeAsync() => | ||
await GetRequestSchemeAsync() ?? | ||
await base.GetDefaultForbidSchemeAsync(); | ||
|
||
public override async Task<AuthenticationScheme?> GetDefaultSignInSchemeAsync() => | ||
await GetRequestSchemeAsync() ?? | ||
await base.GetDefaultSignInSchemeAsync(); | ||
|
||
public override async Task<AuthenticationScheme?> GetDefaultSignOutSchemeAsync() => | ||
await GetRequestSchemeAsync() ?? | ||
await base.GetDefaultSignOutSchemeAsync(); | ||
} | ||
|
||
public static class HttpRequestExtensions | ||
{ | ||
public static bool IsApiRequest(this HttpRequest request) | ||
=> request.Path.StartsWithSegments("/api"); | ||
|
||
public static bool IsServiceRequest(this HttpRequest request) | ||
=> request.Path.StartsWithSegments("/service"); | ||
} |
16 changes: 16 additions & 0 deletions
16
samples/MinimalApis/Net8JwtBearerSample/Authentication/ClaimsTransformer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
using System.Security.Claims; | ||
using Microsoft.AspNetCore.Authentication; | ||
|
||
namespace JwtBearerSample.Authentication; | ||
|
||
public class ClaimsTransformer : IClaimsTransformation | ||
{ | ||
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) | ||
{ | ||
var identity = principal.Identity as ClaimsIdentity; | ||
var newClaim = new Claim(ClaimTypes.Version, "v1"); | ||
identity!.AddClaim(newClaim); | ||
|
||
return Task.FromResult(principal); | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
samples/MinimalApis/Net8JwtBearerSample/Net8JwtBearerSample.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<Project Sdk="Microsoft.NET.Sdk.Web"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<Nullable>enable</Nullable> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.11" /> | ||
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\..\..\src\SimpleAuthentication.Swashbuckle\SimpleAuthentication.Swashbuckle.csproj" /> | ||
<ProjectReference Include="..\..\..\src\SimpleAuthentication\SimpleAuthentication.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
using System.Security.Claims; | ||
using JwtBearerSample.Authentication; | ||
using Microsoft.AspNetCore.Authentication; | ||
using Microsoft.AspNetCore.Http.HttpResults; | ||
using SimpleAuthentication; | ||
using SimpleAuthentication.JwtBearer; | ||
using SimpleAuthentication.Permissions; | ||
|
||
var builder = WebApplication.CreateBuilder(args); | ||
|
||
// Add services to the container. | ||
builder.Services.AddHttpContextAccessor(); | ||
builder.Services.AddProblemDetails(); | ||
|
||
// Add authentication services. | ||
builder.Services.AddSimpleAuthentication(builder.Configuration); | ||
|
||
// Enable permission-based authorization. | ||
builder.Services.AddScopePermissions(); // This is equivalent to builder.Services.AddPermissions<ScopeClaimPermissionHandler>(); | ||
|
||
// Define a custom handler for permission handling. | ||
//builder.Services.AddPermissions<CustomPermissionHandler>(); | ||
|
||
builder.Services.AddAuthorizationBuilder() | ||
// Define permissions using a policy. | ||
.AddPolicy("PeopleRead", builder => builder.RequirePermission(Permissions.PeopleRead, Permissions.PeopleAdmin)) | ||
//.AddPolicy("Bearer", builder => builder.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme).RequireAuthenticatedUser()) | ||
//.SetDefaultPolicy(new AuthorizationPolicyBuilder() | ||
// .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme) | ||
// .RequireAuthenticatedUser() | ||
// .Build()) | ||
//.SetFallbackPolicy(new AuthorizationPolicyBuilder() | ||
// .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme) | ||
// .RequireAuthenticatedUser() | ||
// .Build()) | ||
; | ||
|
||
// Uncomment the following line if you have multiple authentication schemes and | ||
// you need to determine the authentication scheme at runtime (for example, you don't want to use the default authentication scheme). | ||
//builder.Services.AddSingleton<IAuthenticationSchemeProvider, ApplicationAuthenticationSchemeProvider>(); | ||
|
||
builder.Services.AddTransient<IClaimsTransformation, ClaimsTransformer>(); | ||
|
||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle | ||
builder.Services.AddEndpointsApiExplorer(); | ||
|
||
builder.Services.AddSwaggerGen(options => | ||
{ | ||
options.AddSimpleAuthentication(builder.Configuration); | ||
}); | ||
|
||
var app = builder.Build(); | ||
|
||
// Configure the HTTP request pipeline. | ||
app.UseHttpsRedirection(); | ||
|
||
if (!app.Environment.IsDevelopment()) | ||
{ | ||
app.UseExceptionHandler(); | ||
} | ||
|
||
app.UseStatusCodePages(); | ||
|
||
if (app.Environment.IsDevelopment()) | ||
{ | ||
app.UseSwagger(); | ||
app.UseSwaggerUI(); | ||
} | ||
|
||
app.UseAuthentication(); | ||
app.UseAuthorization(); | ||
|
||
var authApiGroup = app.MapGroup("api/auth"); | ||
|
||
authApiGroup.MapPost("login", async (LoginRequest loginRequest, DateTime? expiration, IJwtBearerService jwtBearerService) => | ||
{ | ||
// Check for login rights... | ||
|
||
// Add custom claims (optional). | ||
var claims = new List<Claim>(); | ||
if (!string.IsNullOrWhiteSpace(loginRequest.Scopes)) | ||
{ | ||
claims.Add(new("scp", loginRequest.Scopes)); | ||
} | ||
|
||
var token = await jwtBearerService.CreateTokenAsync(loginRequest.UserName, claims, absoluteExpiration: expiration); | ||
return TypedResults.Ok(new LoginResponse(token)); | ||
}) | ||
.WithOpenApi(operation => | ||
{ | ||
operation.Description = "Insert permissions in the scope property (for example: 'profile people:admin')"; | ||
return operation; | ||
}); | ||
|
||
authApiGroup.MapPost("validate", async Task<Results<Ok<User>, BadRequest>> (string token, bool validateLifetime, IJwtBearerService jwtBearerService) => | ||
{ | ||
var result = await jwtBearerService.TryValidateTokenAsync(token, validateLifetime); | ||
|
||
if (!result.IsValid) | ||
{ | ||
return TypedResults.BadRequest(); | ||
} | ||
|
||
return TypedResults.Ok(new User(result.Principal.Identity!.Name)); | ||
}) | ||
.WithOpenApi(); | ||
|
||
authApiGroup.MapPost("refresh", async (string token, bool validateLifetime, DateTime? expiration, IJwtBearerService jwtBearerService) => | ||
{ | ||
var newToken = await jwtBearerService.RefreshTokenAsync(token, validateLifetime, expiration); | ||
return TypedResults.Ok(new LoginResponse(newToken)); | ||
}) | ||
.WithOpenApi(); | ||
|
||
app.MapGet("api/me", (ClaimsPrincipal user) => | ||
{ | ||
return TypedResults.Ok(new User(user.Identity!.Name)); | ||
}) | ||
.RequireAuthorization() | ||
.RequirePermission("profile") | ||
.WithOpenApi(operation => | ||
{ | ||
operation.Description = "This endpoint requires the 'profile' permission"; | ||
return operation; | ||
}); | ||
|
||
app.MapGet("api/people", () => | ||
{ | ||
return TypedResults.NoContent(); | ||
}) | ||
.RequireAuthorization(policyNames: "PeopleRead") | ||
.WithOpenApi(operation => | ||
{ | ||
operation.Description = $"This endpoint requires the '{Permissions.PeopleRead}' or '{Permissions.PeopleAdmin}' permissions"; | ||
return operation; | ||
}); | ||
|
||
app.Run(); | ||
|
||
public record class User(string? UserName); | ||
|
||
public record class LoginRequest(string UserName, string Password, string? Scopes); | ||
|
||
public record class LoginResponse(string Token); | ||
|
||
public class CustomPermissionHandler : IPermissionHandler | ||
{ | ||
public Task<bool> IsGrantedAsync(ClaimsPrincipal user, IEnumerable<string> permissions) | ||
{ | ||
bool isGranted; | ||
|
||
if (!permissions?.Any() ?? true) | ||
{ | ||
isGranted = true; | ||
} | ||
else | ||
{ | ||
var permissionClaim = user.FindFirstValue("permissions"); | ||
var userPermissions = permissionClaim?.Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) ?? Enumerable.Empty<string>(); | ||
|
||
isGranted = userPermissions.Intersect(permissions!).Any(); | ||
} | ||
|
||
return Task.FromResult(isGranted); | ||
} | ||
} | ||
|
||
public static class Permissions | ||
{ | ||
public const string PeopleRead = "people:read"; | ||
public const string PeopleWrite = "people:write"; | ||
public const string PeopleAdmin = "people:admin"; | ||
} |
Oops, something went wrong.