I know, I know, .NET 9 is released but I’m still helping one of my customers to move all workloads to .NET 8 first. Today we got a compiler error after upgrading our authentication code to .NET 8:
'ISystemClock' is obsolete: 'Use TimeProvider instead.'
1>C:\projects\IAMCore\IAM.Loket\IAMApiKeyAuthenticationHandler.cs(21,13,21,52): warning CS0618: 'AuthenticationHandler<IAMApiKeyAuthenticationOptions>.AuthenticationHandler(IOptionsMonitor<IAMApiKeyAuthenticationOptions>, ILoggerFactory, UrlEncoder, ISystemClock)' is obsolete: 'ISystemClock is obsolete, use TimeProvider on AuthenticationSchemeOptions instead.'
Sidenote: I know that this is a warning, but we got ‘Treat warnings as errors enabled’, a good practice I would recommend everyone to activate on their projects.
Here is the original code:
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Security.Claims; | |
using System.Text; | |
using System.Text.Encodings.Web; | |
using System.Threading.Tasks; | |
using Microsoft.AspNetCore.Authentication; | |
using Microsoft.Extensions.Logging; | |
using Microsoft.Extensions.Options; | |
namespace IAM.Loket | |
{ | |
public class IAMApiKeyAuthenticationHandler : AuthenticationHandler<IAMApiKeyAuthenticationOptions> | |
{ | |
private readonly IApiKeyService _apiKeyService; | |
public IAMApiKeyAuthenticationHandler(IApiKeyService apiKeyService, IOptionsMonitor<IAMApiKeyAuthenticationOptions> options, | |
ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) | |
: base(options, logger, encoder, clock) | |
{ | |
_apiKeyService = apiKeyService; | |
} | |
protected override async Task<AuthenticateResult> HandleAuthenticateAsync() | |
{ | |
if (!Request.Headers.TryGetValue(Options.Headername, out var apiKeyHeaderValues)) | |
return AuthenticateResult.NoResult(); | |
var providedApiKey = apiKeyHeaderValues.FirstOrDefault(); | |
if (apiKeyHeaderValues.Count == 0 || string.IsNullOrWhiteSpace(providedApiKey)) | |
return AuthenticateResult.NoResult(); | |
var identificatie = await _apiKeyService.ValidateApiKey(providedApiKey); | |
if (identificatie is null) | |
return AuthenticateResult.NoResult(); | |
var claims = new List<Claim>(); | |
claims.Add(new Claim(IAMClaimTypes.AuthenticationMethod, AuthenticationMethod.IPEID.ToString())); | |
claims.Add(new Claim(IAMClaimTypes.Identifier, identificatie.Sleutel)); | |
claims.Add(new Claim(IAMClaimTypes.FullName, identificatie.Name)); | |
var identity = new ClaimsIdentity(claims, Scheme.Name); | |
var principal = new ClaimsPrincipal(identity); | |
var ticket = new AuthenticationTicket(principal, Scheme.Name); | |
return AuthenticateResult.Success(ticket); | |
} | |
} | |
} | |
ISystemClock was an old abstraction to help during testing. It was never promoted as an official feature. With the release of .NET 8 we got the ‘official’ TimeProvider abstraction:
public abstract class TimeProvider | |
{ | |
public static TimeProvider System { get; } | |
protected TimeProvider() | |
public virtual DateTimeOffset GetUtcNow() | |
public DateTimeOffset GetLocalNow() | |
public virtual TimeZoneInfo LocalTimeZone { get; } | |
public virtual long TimestampFrequency { get; } | |
public virtual long GetTimestamp() | |
public TimeSpan GetElapsedTime(long startingTimestamp) | |
public TimeSpan GetElapsedTime(long startingTimestamp, long endingTimestamp) | |
public virtual ITimer CreateTimer(TimerCallback callback, object? state,TimeSpan dueTime, TimeSpan period) | |
} | |
public interface ITimer : IDisposable, IAsyncDisposable | |
{ | |
bool Change(TimeSpan dueTime, TimeSpan period); | |
} |
If you want to learn more about this TimeProvider, I would recommend to check my previous post about the TimeProvider and ITimer
The fix itself was easy, we updated our own AuthenticationHandler constructor and removed the ISystemClock parameter:
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Security.Claims; | |
using System.Text; | |
using System.Text.Encodings.Web; | |
using System.Threading.Tasks; | |
using Microsoft.AspNetCore.Authentication; | |
using Microsoft.Extensions.Logging; | |
using Microsoft.Extensions.Options; | |
namespace IAM.Loket | |
{ | |
public class IAMApiKeyAuthenticationHandler : AuthenticationHandler<IAMApiKeyAuthenticationOptions> | |
{ | |
private readonly IApiKeyService _apiKeyService; | |
public IAMApiKeyAuthenticationHandler(IApiKeyService apiKeyService, IOptionsMonitor<IAMApiKeyAuthenticationOptions> options, | |
ILoggerFactory logger, UrlEncoder encoder) | |
: base(options, logger, encoder) | |
{ | |
_apiKeyService = apiKeyService; | |
} | |
} | |
//Removed the other code | |
} |
More information
.NET 8–It’s time to get rid of these flaky tests!
AuthenticationHandler<TOptions> Class (Microsoft.AspNetCore.Authentication) | Microsoft Learn