Skip to content

Commit

Permalink
Refactor JWT handling and introduce token creation classes
Browse files Browse the repository at this point in the history
Updated ServiceCollectionExtensions.cs to use a new GetJwtSettings method for retrieving JWT settings, improving code readability and maintainability. Registered a new ITokenProvider service and updated JWT bearer authentication setup.

Added ClaimBuilder.cs with a fluent API for building claims.

Introduced ConfigurationExtensions.cs with GetJwtSettings method for retrieving and validating JWT settings.

Added JwtSettings.cs to represent JWT settings.

Introduced ITokenProvider.cs interface for creating tokens with customizable claims.

Added JwtTokenProvider.cs implementing ITokenProvider, using JwtSettings and ClaimBuilder for token creation.
  • Loading branch information
bvdcode committed Aug 1, 2024
1 parent 8e6b98a commit 5c37455
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 13 deletions.
53 changes: 53 additions & 0 deletions Sources/EasyExtensions.Authorization/Builders/ClaimBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Security.Claims;
using System.Collections.Generic;

namespace EasyExtensions.Authorization.Builders
{
/// <summary>
/// Claim builder.
/// </summary>
public class ClaimBuilder
{
private readonly List<Claim> _claims = [];

/// <summary>
/// Adds a claim to the builder.
/// </summary>
/// <param name="type"> Claim type. </param>
/// <param name="value"> Claim value. </param>
/// <returns> Current <see cref="ClaimBuilder"/> instance. </returns>
/// <exception cref="ArgumentException"> When <paramref name="type"/> or <paramref name="value"/> is null or empty. </exception>
public ClaimBuilder Add(string type, string value)
{
ArgumentException.ThrowIfNullOrWhiteSpace(type, nameof(type));
ArgumentException.ThrowIfNullOrWhiteSpace(value, nameof(value));
_claims.Add(new Claim(type, value));
return this;
}

/// <summary>
/// Adds a claim to the builder.
/// </summary>
/// <param name="claim"> Claim to add. </param>
/// <returns> Current <see cref="ClaimBuilder"/> instance. </returns>
/// <exception cref="ArgumentNullException"> When <paramref name="claim"/> is null or <see cref="Claim.Type"/> or <see cref="Claim.Value"/> is empty. </exception>
public ClaimBuilder Add(Claim claim)
{
ArgumentNullException.ThrowIfNull(claim, nameof(claim));
ArgumentNullException.ThrowIfNull(claim.Type, nameof(claim.Type));
ArgumentNullException.ThrowIfNull(claim.Value, nameof(claim.Value));
_claims.Add(claim);
return this;
}

/// <summary>
/// Builds the claims.
/// </summary>
/// <returns> Array of claims. </returns>
public Claim[] Build()
{
return [.. _claims];
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using EasyExtensions.Authorization.Models;

namespace EasyExtensions.Authorization.Services
{
internal static class ConfigurationExtensions
{
internal static JwtSettings GetJwtSettings(this IConfiguration configuration)
{
var jwtSettings = configuration.GetSection("JwtSettings");
int lifetimeMinutes = jwtSettings.Exists() ? int.Parse(jwtSettings["LifetimeMinutes"]!) : int.Parse(configuration["JwtLifetimeMinutes"]!);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(lifetimeMinutes, nameof(lifetimeMinutes));

return new JwtSettings
{
Key = (!jwtSettings.Exists() ? configuration["JwtKey"] : jwtSettings["Key"])
?? throw new KeyNotFoundException("JwtSettings.Key or JwtKey is not set"),
Issuer = (!jwtSettings.Exists() ? configuration["JwtIssuer"] : jwtSettings["Issuer"])
?? throw new KeyNotFoundException("JwtSettings.Issuer or JwtIssuer is not set"),
Audience = (!jwtSettings.Exists() ? configuration["JwtAudience"] : jwtSettings["Audience"])
?? throw new KeyNotFoundException("JwtSettings.Audience or JwtAudience is not set"),
LifetimeMinutes = lifetimeMinutes
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Authorization;
using EasyExtensions.Authorization.Handlers;
using EasyExtensions.Authorization.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Authentication.JwtBearer;

Expand Down Expand Up @@ -48,29 +49,21 @@ public static IServiceCollection AddCorsWithOrigins(this IServiceCollection serv
/// <exception cref="KeyNotFoundException"> When JwtSettings section is not set. </exception>
public static IServiceCollection AddJwt(this IServiceCollection services, IConfiguration configuration)
{
var jwtSettings = configuration.GetSection("JwtSettings");

string secret = (!jwtSettings.Exists() ? configuration["JwtKey"] : jwtSettings["Key"])
?? throw new KeyNotFoundException("JwtSettings.Key or JwtKey is not set");

string issuer = (!jwtSettings.Exists() ? configuration["JwtIssuer"] : jwtSettings["Issuer"])
?? throw new KeyNotFoundException("JwtSettings.Issuer or JwtIssuer is not set");

string audience = (!jwtSettings.Exists() ? configuration["JwtAudience"] : jwtSettings["Audience"])
?? throw new KeyNotFoundException("JwtSettings.Audience or JwtAudience is not set");
var jwtSettings = configuration.GetJwtSettings();

services.AddScoped<ITokenProvider, JwtTokenProvider>();
services.AddAuthentication("Bearer")
.AddJwtBearer(options =>
{
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(secret);
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(jwtSettings.Key);
var key = new SymmetricSecurityKey(bytes);
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = issuer,
ValidIssuer = jwtSettings.Issuer,
ValidateAudience = true,
ValidAudience = audience,
ValidAudience = jwtSettings.Audience,
ValidateLifetime = true,
IssuerSigningKey = key,
ValidateIssuerSigningKey = true,
Expand Down
10 changes: 10 additions & 0 deletions Sources/EasyExtensions.Authorization/Models/JwtSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace EasyExtensions.Authorization.Models
{
internal class JwtSettings
{
internal int? LifetimeMinutes { get; set; }
internal string Key { get; set; } = string.Empty;
internal string Issuer { get; set; } = string.Empty;
internal string Audience { get; set; } = string.Empty;
}
}
10 changes: 10 additions & 0 deletions Sources/EasyExtensions.Authorization/Services/ITokenProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;
using EasyExtensions.Authorization.Builders;

namespace EasyExtensions.Authorization.Services
{
internal interface ITokenProvider
{
string CreateToken(Func<ClaimBuilder, ClaimBuilder>? claimBuilder = null);
}
}
42 changes: 42 additions & 0 deletions Sources/EasyExtensions.Authorization/Services/JwtTokenProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.Extensions.Configuration;
using EasyExtensions.Authorization.Models;
using EasyExtensions.Authorization.Builders;

namespace EasyExtensions.Authorization.Services
{
internal class JwtTokenProvider(IConfiguration _configuration) : ITokenProvider
{
private readonly JwtSettings _jwtSettings = _configuration.GetJwtSettings();

public string CreateToken(Func<ClaimBuilder, ClaimBuilder>? claimBuilder = null)
{
var claims = claimBuilder == null ? [] : claimBuilder(new ClaimBuilder()).Build();
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.Key));
#pragma warning disable CA2208 // Instantiate argument exceptions correctly
string algorithm = _jwtSettings.Key.Length switch
{
128 / 8 => SecurityAlgorithms.HmacSha256,
192 / 8 => SecurityAlgorithms.HmacSha384,
256 / 8 => SecurityAlgorithms.HmacSha512,
_ => throw new ArgumentOutOfRangeException(nameof(_jwtSettings), "Key length must be 128, 192 or 256 bits"),
};
#pragma warning restore CA2208 // Instantiate argument exceptions correctly
var credentials = new SigningCredentials(securityKey, algorithm);
var expirationDate = _jwtSettings.LifetimeMinutes.HasValue
? DateTime.UtcNow.AddMinutes(_jwtSettings.LifetimeMinutes.Value)
: DateTime.UtcNow.AddMinutes(30);
var tokenDescriptor = new JwtSecurityToken(
issuer: _jwtSettings.Issuer,
audience: _jwtSettings.Audience,
claims: claims,
expires: expirationDate,
signingCredentials: credentials);
return new JwtSecurityTokenHandler()
.WriteToken(tokenDescriptor);
}
}
}

0 comments on commit 5c37455

Please sign in to comment.