I have created RemoteAuthenticationHandler, which looks like that:
public class AuthAndAuthHandler : RemoteAuthenticationHandler<AuthAndAuthSchemeOptions>
{
public AuthAndAuthHandler(IOptionsMonitor<AuthAndAuthSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
var rng = RandomNumberGenerator.Create();
var state = new byte[128];
var nonce = new byte[128];
var codeVerifier = new byte[64];
rng.GetBytes(state);
rng.GetBytes(nonce);
rng.GetBytes(codeVerifier);
var codeChallenge = SHA256.HashData(codeVerifier);
Response.Cookies.Append("Nonce", Convert.ToBase64String(SHA256.HashData(nonce)), new CookieOptions
{
Path = "/callback",
HttpOnly = true,
IsEssential = true,
Secure = true,
SameSite = SameSiteMode.Strict,
Expires = Clock.UtcNow.AddHours(1)
});
Response.Redirect($"{Options.Authority}/authorization?client_id={Options.ClientId}" +
$"&callback_uri={Request.Scheme}://{Request.Host}{Options.CallbackPath}&scopes={Options.Scopes}" +
$"&state={Convert.ToBase64String(state)}&nonce={Convert.ToBase64String(nonce)}&code_challenge={Convert.ToBase64String(codeChallenge)}");
}
protected override async Task<HandleRequestResult> HandleRemoteAuthenticateAsync()
{
throw new NotImplementedException();
}
}
And in HandleRemoteAuthenticateAsync()
method I have to verify state, which I will get after successful remote authorization. How can I do this, when after Challenge I'm losing earlier generated state and code verifier?
According to state, you can validate it with Microsoft.AspNetCore.Authentication.ISecureDataFormat<TData>
. It can be defined as property inside your custom options (pseudocode):
public class CustomRemoteOptions : RemoteAuthenticationOptions
{
public CustomRemoteOptions()
{
var dataProtector = DataProtectionProvider.CreateProtector(
typeof(CustomRemoteHandler).FullName);
StateDataFormat = new PropertiesDataFormat(dataProtector);
}
internal ISecureDataFormat<AuthenticationProperties> StateDataFormat { get; }
}
Then use it inside CustomRemoteHandler
:
public class CustomRemoteHandler : RemoteAuthenticationHandler<CustomRemoteOptions>
{
protected override Task HandleChallengeAsync(AuthenticationProperties properties)
{
string state = Options.StateDataFormat.Protect(properties);
// Then add the state to the URI.
}
protected override async Task<HandleRequestResult> HandleRemoteAuthenticateAsync()
{
var state = Request.Query["state"];
var properties = Options.StateDataFormat.Unprotect(state);
if (properties == null)
{
return HandleRequestResult.Fail("The Custom authentication state was missing or invalid.");
}
}
}
Note: if you also need to validate correlation id, then add GenerateCorrelationId(properties);
inside HandleChallengeAsync
and the following lines inside HandleRemoteAuthenticateAsync
:
if (!ValidateCorrelationId(properties))
{
return HandleRequestResult.Fail("Validation of correlation failed.", properties);
}
Note: to make the code testable you can move initialization of StateDataFormat
to custom IPostConfigureOptions
implementation.