Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
<PackageId>Duende.AspNetCore.Authentication.OAuth2Introspection</PackageId>
<Description>ASP.NET Core authentication handler for validating tokens using OAuth 2.0 introspection</Description>
<PackageTags>OAuth2;OAuth 2.0;Introspection;Security;Identity;IdentityServer</PackageTags>
<Nullable>disable</Nullable>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,14 @@ namespace Duende.AspNetCore.Authentication.OAuth2Introspection.Context;
/// <summary>
/// Context for the AuthenticationFailed event
/// </summary>
public class AuthenticationFailedContext : ResultContext<OAuth2IntrospectionOptions>
public class AuthenticationFailedContext(HttpContext context,
AuthenticationScheme scheme,
OAuth2IntrospectionOptions options,
string error)
: ResultContext<OAuth2IntrospectionOptions>(context, scheme, options)
{
/// <summary>
/// ctor
/// </summary>
public AuthenticationFailedContext(
HttpContext context,
AuthenticationScheme scheme,
OAuth2IntrospectionOptions options)
: base(context, scheme, options) { }

/// <summary>
/// The error
/// </summary>
public string Error { get; set; }
public string Error => error;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,15 @@ namespace Duende.AspNetCore.Authentication.OAuth2Introspection.Context;
/// <summary>
/// Context for the SendingRequest event
/// </summary>
public class SendingRequestContext : BaseContext<OAuth2IntrospectionOptions>
public class SendingRequestContext(
HttpContext context,
AuthenticationScheme scheme,
OAuth2IntrospectionOptions options,
TokenIntrospectionRequest tokenIntrospectionRequest)
: BaseContext<OAuth2IntrospectionOptions>(context, scheme, options)
{
/// <summary>
/// ctor
/// </summary>
public SendingRequestContext(
HttpContext context,
AuthenticationScheme scheme,
OAuth2IntrospectionOptions options)
: base(context, scheme, options) { }

/// <summary>
/// The <see cref="TokenIntrospectionRequest"/> request
/// </summary>
public TokenIntrospectionRequest TokenIntrospectionRequest { get; set; }
public TokenIntrospectionRequest TokenIntrospectionRequest => tokenIntrospectionRequest;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,15 @@ namespace Duende.AspNetCore.Authentication.OAuth2Introspection.Context;
/// <summary>
/// Context for the TokenValidated event
/// </summary>
public class TokenValidatedContext : ResultContext<OAuth2IntrospectionOptions>
public class TokenValidatedContext(
HttpContext context,
AuthenticationScheme scheme,
OAuth2IntrospectionOptions options,
string securityToken)
: ResultContext<OAuth2IntrospectionOptions>(context, scheme, options)
{
/// <summary>
/// ctor
/// </summary>
public TokenValidatedContext(
HttpContext context,
AuthenticationScheme scheme,
OAuth2IntrospectionOptions options)
: base(context, scheme, options) { }

/// <summary>
/// The security token
/// </summary>
public string SecurityToken { get; set; }
public string SecurityToken => securityToken;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,20 @@ namespace Duende.AspNetCore.Authentication.OAuth2Introspection.Context;
/// <summary>
/// Context for the UpdateClientAssertion event
/// </summary>
public class UpdateClientAssertionContext : ResultContext<OAuth2IntrospectionOptions>
public class UpdateClientAssertionContext(
HttpContext context,
AuthenticationScheme scheme,
OAuth2IntrospectionOptions options,
ClientAssertion clientAssertion)
: ResultContext<OAuth2IntrospectionOptions>(context, scheme, options)
{
/// <summary>
/// ctor
/// </summary>
public UpdateClientAssertionContext(
HttpContext context,
AuthenticationScheme scheme,
OAuth2IntrospectionOptions options)
: base(context, scheme, options) { }

/// <summary>
/// The client assertion
/// </summary>
public ClientAssertion ClientAssertion { get; set; }
public ClientAssertion ClientAssertion { get; set; } = clientAssertion;

/// <summary>
/// The client assertion expiration time
/// </summary>
public DateTime ClientAssertionExpirationTime { get; set; }
public DateTime ClientAssertionExpirationTime { get; set; } = options.ClientAssertionExpirationTime;
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,29 @@ static CacheExtensions()

[RequiresUnreferencedCode(
"Calls System.Text.Json.JsonSerializer.Deserialize<TValue>(ReadOnlySpan<Byte>, JsonSerializerOptions)")]
public static async Task<IEnumerable<Claim>> GetClaimsAsync(this IDistributedCache cache,
OAuth2IntrospectionOptions options, string token)
public static async Task<IEnumerable<Claim>?> GetClaimsAsync(
this IDistributedCache cache,
OAuth2IntrospectionOptions options,
string token)
{
var cacheKey = options.CacheKeyGenerator(options, token);
var bytes = await cache.GetAsync(cacheKey).ConfigureAwait(false);

if (bytes == null)
{
return null;
}
var claims = bytes == null
? null
: JsonSerializer.Deserialize<IEnumerable<Claim>>(bytes, Options);

return JsonSerializer.Deserialize<IEnumerable<Claim>>(bytes, Options);
return claims;
}

[RequiresUnreferencedCode(
"Calls System.Text.Json.JsonSerializer.SerializeToUtf8Bytes<TValue>(TValue, JsonSerializerOptions)")]
public static async Task SetClaimsAsync(this IDistributedCache cache, OAuth2IntrospectionOptions options,
string token, IEnumerable<Claim> claims, TimeSpan duration, ILogger logger)
[RequiresUnreferencedCode("Calls System.Text.Json.JsonSerializer.SerializeToUtf8Bytes<TValue>(TValue, JsonSerializerOptions)")]
public static async Task SetClaimsAsync(
this IDistributedCache cache,
OAuth2IntrospectionOptions options,
string token,
IEnumerable<Claim> claims,
TimeSpan duration,
ILogger logger)
{
var expClaim = claims.FirstOrDefault(c => c.Type == JwtClaimTypes.Expiration);
var now = DateTimeOffset.UtcNow;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class ClaimConverter : JsonConverter<Claim>
public override Claim Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
#pragma warning restore IL2046
{
var source = JsonSerializer.Deserialize<ClaimLite>(ref reader, options);
var source = JsonSerializer.Deserialize<ClaimLite>(ref reader, options)!;
var target = new Claim(source.Type, source.Value);

return target;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace Duende.AspNetCore.Authentication.OAuth2Introspection.Infrastructure;

public class ClaimLite
{
public string Type { get; set; }
public string Value { get; set; }
public required string Type { get; init; }

public required string Value { get; init; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using System.Text;

Expand All @@ -11,20 +12,17 @@ internal static class StringExtensions
{
[DebuggerStepThrough]
public static string EnsureTrailingSlash(this string input)
{
if (!input.EndsWith("/"))
{
return input + "/";
}

return input;
}
=> input.EndsWith("/")
? input
: input + "/";

[DebuggerStepThrough]
public static bool IsMissing(this string value) => string.IsNullOrWhiteSpace(value);
public static bool IsMissing([NotNullWhen(false)] this string? value)
=> string.IsNullOrWhiteSpace(value);

[DebuggerStepThrough]
public static bool IsPresent(this string value) => !string.IsNullOrWhiteSpace(value);
public static bool IsPresent([NotNullWhen(true)] this string? value)
=> !string.IsNullOrWhiteSpace(value);

internal static string Sha256(this string input)
{
Expand All @@ -33,12 +31,10 @@ internal static string Sha256(this string input)
return string.Empty;
}

using (var sha = SHA256.Create())
{
var bytes = Encoding.UTF8.GetBytes(input);
var hash = sha.ComputeHash(bytes);
using var sha = SHA256.Create();
var bytes = Encoding.UTF8.GetBytes(input);
var hash = sha.ComputeHash(bytes);

return Convert.ToBase64String(hash);
}
return Convert.ToBase64String(hash);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static class TokenRetrieval
/// Reads the token from the authorization header.
/// </summary>
/// <param name="scheme">The scheme (defaults to Bearer).</param>
public static Func<HttpRequest, string> FromAuthorizationHeader(
public static Func<HttpRequest, string?> FromAuthorizationHeader(
string scheme = OAuth2IntrospectionDefaults.AuthenticationScheme)
{
var schemePrefix = scheme + " ";
Expand All @@ -42,7 +42,7 @@ public static Func<HttpRequest, string> FromAuthorizationHeader(
/// Reads the token from a query string parameter.
/// </summary>
/// <param name="name">The name (defaults to access_token).</param>
public static Func<HttpRequest, string> FromQueryString(string name = "access_token") => request =>
public static Func<HttpRequest, string?> FromQueryString(string name = "access_token") => request =>
{
if (request.Query.TryGetValue(name, out var value) && value.Count > 0)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,37 @@ namespace Duende.AspNetCore.Authentication.OAuth2Introspection;

internal static class Log
{
public static readonly Action<ILogger, Exception> NoExpClaimFound
public static readonly Action<ILogger, Exception?> NoExpClaimFound
= LoggerMessage.Define(
LogLevel.Warning,
1,
"No exp claim found on introspection response, can't cache");

public static readonly Action<ILogger, DateTimeOffset, Exception> TokenExpiresOn
public static readonly Action<ILogger, DateTimeOffset, Exception?> TokenExpiresOn
= LoggerMessage.Define<DateTimeOffset>(
LogLevel.Debug,
2,
"Token will expire on {Expiration}");

public static readonly Action<ILogger, DateTimeOffset, Exception> SettingToCache
public static readonly Action<ILogger, DateTimeOffset, Exception?> SettingToCache
= LoggerMessage.Define<DateTimeOffset>(
LogLevel.Debug,
3,
"Setting cache item expiration to {Expiration}");

public static readonly Action<ILogger, Exception> SkippingDotToken
public static readonly Action<ILogger, Exception?> SkippingDotToken
= LoggerMessage.Define(
LogLevel.Trace,
4,
"Token contains a dot - skipped because SkipTokensWithDots is set");

public static readonly Action<ILogger, Exception> TokenNotCached
public static readonly Action<ILogger, Exception?> TokenNotCached
= LoggerMessage.Define(
LogLevel.Trace,
5,
"Token is not cached");

public static readonly Action<ILogger, string, Exception> IntrospectionError
public static readonly Action<ILogger, string, Exception?> IntrospectionError
= LoggerMessage.Define<string>(
LogLevel.Error,
6,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,28 @@ public static AuthenticationBuilder AddOAuth2Introspection(this AuthenticationBu
/// <param name="services">The services.</param>
/// <param name="configureOptions">The configure options.</param>
/// <returns></returns>
public static AuthenticationBuilder AddOAuth2Introspection(this AuthenticationBuilder services, Action<OAuth2IntrospectionOptions> configureOptions)
public static AuthenticationBuilder AddOAuth2Introspection(
this AuthenticationBuilder services,
Action<OAuth2IntrospectionOptions>? configureOptions)
=> services.AddOAuth2Introspection(OAuth2IntrospectionDefaults.AuthenticationScheme, configureOptions: configureOptions);


/// <summary>
/// Adds the OAuth 2.0 introspection handler.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="authenticationScheme">The authentication scheme.</param>
/// <param name="configureOptions">The configure options.</param>
/// <returns></returns>
public static AuthenticationBuilder AddOAuth2Introspection(this AuthenticationBuilder builder, string authenticationScheme, Action<OAuth2IntrospectionOptions> configureOptions)
public static AuthenticationBuilder AddOAuth2Introspection(
this AuthenticationBuilder builder,
string authenticationScheme,
Action<OAuth2IntrospectionOptions>? configureOptions)
{
builder.Services.AddHttpClient(OAuth2IntrospectionDefaults.BackChannelHttpClientName);

builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<OAuth2IntrospectionOptions>, PostConfigureOAuth2IntrospectionOptions>());
return builder.AddScheme<OAuth2IntrospectionOptions, OAuth2IntrospectionHandler>(authenticationScheme, configureOptions);
var serviceDescriptor = ServiceDescriptor
.Singleton<IPostConfigureOptions<OAuth2IntrospectionOptions>, PostConfigureOAuth2IntrospectionOptions>();
builder.Services.TryAddEnumerable(serviceDescriptor);
builder.AddScheme<OAuth2IntrospectionOptions, OAuth2IntrospectionHandler>(authenticationScheme, configureOptions);
return builder;
}
}
Loading