In this part, we focus on obtaining an access token use case. As its name suggests, in this use case, the package tries to obtain an access token by adopting the following procedure. After studying the sequence diagram, you can see the source code and explanation of each participant entity in detail. Note that DefaultOAuth2AccessTokenProvider and DefaultOAuth2ClientGateway participants contain functionalities belonging to separate use cases. Therefore, we only explain details belonging to the mentioned use case.

1-DefaultOAuth2AccessTokenProvider

The purpose of the DefaultOAuth2AccessTokenProvider class is to provide the client code with a single method for obtaining OAuth2 access tokens.

internal sealed class DefaultOAuth2AccessTokenProvider : IOAuth2AccessTokenProvider
{
  private readonly IOAuth2ClientGateway _oauth2ClientGateway;
  private readonly IOAuth2ClientDataStore _oAuth2ClientDataStore;
  private readonly List<OAuth2Client> _oAuth2Clients;
  private readonly IDateTimeProvider _dateTimeProvider;

  public DefaultOAuth2AccessTokenProvider(
    IOAuth2ClientGateway iOAuth2ClientGateway,
    IOAuth2ClientDataStore oAuth2ClientDataStore,
    List<OAuth2Client> oAuth2Clients,
    IDateTimeProvider dateTimeProvider)
  {
    _oauth2ClientGateway = iOAuth2ClientGateway;
    _oAuth2ClientDataStore = oAuth2ClientDataStore;
    _oAuth2Clients = oAuth2Clients;
    _dateTimeProvider = dateTimeProvider;
  }

  public async Task<string> GetAccessToken(string clientName)
  {
    var oAuth2Client = GetOAuth2ClientFromRegisteredClients(clientName);
    var clientId = oAuth2Client.ClientCredentialOptions.ClientId;

    var accessTokenResponse = await
      GetAccessTokenResponseFromDataStore(clientId);
    if (accessTokenResponse == null)
    {
      return await GetOrGenerateAccessToken(oAuth2Client);
    }

    return await GetAccessTokenOrGenerateRefreshAccessToken
      (oAuth2Client, accessTokenResponse);
  }

  private async Task<string> GetOrGenerateAccessToken(OAuth2Client oAuth2Client)
  {
    var clientId = oAuth2Client.ClientCredentialOptions.ClientId;

    var authorizationCodeResponse =
      await GetAuthorizationCodeResponseFromDataStore(clientId);
    if (authorizationCodeResponse != null)
    {
      var authorizationCodeStatus = authorizationCodeResponse
        .GetStatus(_dateTimeProvider.GetUTCDateTimeNow);
      if (authorizationCodeStatus == AuthorizationCodeStatus.Valid)
      {
        var accessTokenResponse =
          await GetAccessTokenResponseFromGateway
          (clientId, authorizationCodeResponse.AuthorizationCode);

        if (accessTokenResponse == null)
        {
          throw await ClearDataStoreAndRaiseException(clientId);
        }

        await SetAccessTokenResponseToDataStore
          (oAuth2Client.ClientCredentialOptions, accessTokenResponse);

        return accessTokenResponse.AccessToken;
      }
    }

    throw await ClearDataStoreAndRaiseException(clientId);
  }
  private async Task<string> GetAccessTokenOrGenerateRefreshAccessToken
    (OAuth2Client oAuth2Client, AccessTokenResponse accessTokenResponse)
  {
    var clientId = oAuth2Client.ClientCredentialOptions.ClientId;

    var accessTokenStatus = accessTokenResponse
      .GetStatus(_dateTimeProvider.GetUTCDateTimeNow,
      oAuth2Client.RefreshTokenOptions.StaticExpirationValue,
      oAuth2Client.RefreshTokenOptions.CanRenewAfterExpiration);
    if (accessTokenStatus == AccessTokenStatus.Valid)
    {
      return accessTokenResponse.AccessToken;
    }
    else if (accessTokenStatus == AccessTokenStatus.Invalid ||
      accessTokenStatus == AccessTokenStatus.NearExpiration)
    {
      var newAccessTokenResponse =
        await GetRefreshAccessTokenResponseFromGatway
        (clientId, accessTokenResponse.RefreshToken);

      if (newAccessTokenResponse == null)
      {
        throw await ClearDataStoreAndRaiseException(clientId);
      }

      await SetAccessTokenResponseToDataStore
        (oAuth2Client.ClientCredentialOptions, newAccessTokenResponse);

      return newAccessTokenResponse.AccessToken;
    }

    throw await ClearDataStoreAndRaiseException(clientId);
  }

  private OAuth2Client GetOAuth2ClientFromRegisteredClients
    (string clientName)
  {
    var oAuth2Client = _oAuth2Clients.FirstOrDefault
      (t => t.ClientCredentialOptions.ClientName == clientName);
    if (oAuth2Client == null)
    {
      throw new OAuth2MultiClientIntegratorFailureException
        ("No suitable OAuthClient found.");
    }

    return oAuth2Client;
  }
  private async Task<InvalidOAuth2AccessTokenException>
    ClearDataStoreAndRaiseException(string clientId)
  {
    await _oAuth2ClientDataStore.ClearDataStore(clientId);
    return new InvalidOAuth2AccessTokenException(clientId);
  }

  private async Task<AccessTokenResponse>
    GetAccessTokenResponseFromDataStore(string clientId)
  {
    return await _oAuth2ClientDataStore
      .GetAccessTokenResponse(clientId);
  }
  private async Task<AuthorizationCodeResponse>
    GetAuthorizationCodeResponseFromDataStore(string clientId)
  {
    return await _oAuth2ClientDataStore.
      GetAuthorizationCodeResponse(clientId);
  }
  private async Task SetAccessTokenResponseToDataStore
    (ClientCredentialOptions clientCredentialOptions,
    AccessTokenResponse accessTokenResponse)
  {
    await _oAuth2ClientDataStore.SetAccessTokenResponse
      (clientCredentialOptions, accessTokenResponse);
  }

  private async Task<AccessTokenResponse>
    GetAccessTokenResponseFromGateway
    (string clientId, string authorizationCode)
  {
    return await _oauth2ClientGateway.GetAccessTokenResponse
      (clientId, authorizationCode);
  }
  private async Task<AccessTokenResponse>
    GetRefreshAccessTokenResponseFromGatway
    (string clientId, string refreshToken)
  {
    return await _oauth2ClientGateway
      .GetRefreshAccessTokenResponse
      (clientId, refreshToken);
  }
}

Let's go through each method of the DefaultOAuth2AccessTokenProvider class in detail:

Constructor: The class has a constructor that takes four parameters: iOAuth2ClientGateway, oAuth2ClientDataStore, oAuth2Clients, and dateTimeProvider. These parameters are dependencies injected into the class for various functionalities.

GetAccessToken Method: This method is responsible for providing the OAuth2 access token for a given client name. It first obtains the OAuth2 client based on the provided client name. It then tries to get the access token response from the data store. If it doesn't exist, it calls GetOrGenerateAccessToken to generate a new access token. If the access token response exists, it calls GetAccessTokenOrGenerateRefreshAccessToken to check if the token is valid or needs to be refreshed.

GetOrGenerateAccessToken Method: This method is used when there is no existing access token in the data store or when the stored authorization code is invalid. It first checks if there is a valid authorization code response in the data store. If there is a valid authorization code, it retrieves the access token response from the OAuth2 gateway and stores it in the data store before returning the access token. If there is no valid authorization code, or if there is an issue during the process, it clears the data store and raises a signaling exception named InvalidOAuth2AccessTokenException to restart the whole process from the login page to obtain a valid authorization code. In other words, we use InvalidOAuth2AccessTokenException to signal OAuth2AuthorizationRedirectSignalExceptionFilter to redirect the user to the authorization URI aka login page.

Other Private Helper Methods:

GetOAuth2ClientFromRegisteredClients: Retrieves the OAuth2 client from the list of registered clients based on the client name.

ClearDataStoreAndRaiseException: Clears the data store for a specific client and raises an exception.

GetAccessTokenResponseFromDataStore: Retrieves the access token response from the data store based on the client ID.

GetAuthorizationCodeResponseFromDataStore: Retrieves the authorization code response from the data store based on the client ID.

SetAccessTokenResponseToDataStore: Stores the access token response in the data store.

GetAccessTokenResponseFromGateway: Retrieves the access token response from the OAuth2 gateway.

2-OAuth2AuthorizationRedirectSignalExceptionFilter

internal sealed class OAuth2AuthorizationRedirectSignalExceptionFilter : ExceptionFilterAttribute
{
  public override async Task OnExceptionAsync(ExceptionContext exceptionContext)
  {
    if (!exceptionContext.ExceptionHandled &&
      exceptionContext.Exception is InvalidOAuth2AccessTokenException)
    {
      var oauth2AuthorizationUriGenerator = exceptionContext.HttpContext
        .RequestServices.GetRequiredService<IOAuth2AuthorizationUriGenerator>();

      var authorizationRedirectUri =
        exceptionContext.HttpContext.Request.CreateAuthorizationRedirectUri();
      var clientId = exceptionContext.Exception.Message;
      var authorizationUri = await oauth2AuthorizationUriGenerator.
        GenerateAuthorizationUri(clientId, authorizationRedirectUri);

      exceptionContext.ExceptionHandled = true;
      if (exceptionContext.HttpContext
        .Request.IsAjaxRequest())
      {
        exceptionContext.Result =
          new OkObjectResult(new
          {
            isRedirected = true,
            redirectUri = authorizationUri
          });
      }
      else
      {
        exceptionContext.HttpContext
          .Response.Redirect(authorizationUri);
      }
    }
  }
}

Let's break down the code and describe OAuth2AuthorizationRedirectSignalExceptionFilter 's functionality:

OnExceptionAsync Method: This method overrides the OnExceptionAsync method from the base class ExceptionFilterAttribute. It is called when an unhandled exception occurs during the processing of a request. The method begins by checking if the exception has been handled and if it is of type InvalidOAuth2AccessTokenException. If both conditions are met, it proceeds with handling the exception. It retrieves an instance of IOAuth2AuthorizationUriGenerator from the dependency injection container through the RequestServices property of the HttpContext. It then creates an authorization redirect URI using the CreateAuthorizationRedirectUri extension method on the HttpContext.Request object. The client ID is extracted from the exception message. The GenerateAuthorizationUri method of the oauth2AuthorizationUriGenerator is called to obtain the authorization URI. The exception is marked as handled (exceptionContext.ExceptionHandled = true) to prevent further processing of the exception. It checks if the request is an Ajax request using the IsAjaxRequest extension method on the HttpContext.Request object. If it is an Ajax request, an OkObjectResult is created with a JSON object containing information about the redirection, including the authorization URI. This result is set as the result of the exception context. If it is not an Ajax request, a redirect response is sent to the client, redirecting them to the authorization URI.

3-DefaultOAuth2AuthorizationUriGenerator

internal sealed class DefaultOAuth2AuthorizationUriGenerator : IOAuth2AuthorizationUriGenerator
{
  private readonly IOAuth2ClientDataStore _oAuth2ClientDataStore;
  private readonly List<OAuth2Client> _oAuth2Clients;

  public DefaultOAuth2AuthorizationUriGenerator(
    IOAuth2ClientDataStore oAuth2ClientDataStore,
    List<OAuth2Client> oAuth2Clients)
  {
    _oAuth2ClientDataStore = oAuth2ClientDataStore;
    _oAuth2Clients = oAuth2Clients;
  }

  public async Task<string> GenerateAuthorizationUri
    (string clientId, string authorizationRedirectUri)
  {
    var oAuth2Client = _oAuth2Clients.FirstOrDefault
      (t => t.ClientCredentialOptions.ClientId == clientId);
    if (oAuth2Client == null)
    {
      throw new OAuth2MultiClientIntegratorFailureException
        ("No suitable OAuthClient found.");
    }

    var authorizationState = $"{Guid.NewGuid()},{clientId}";
    await _oAuth2ClientDataStore.SetAuthorizationState
      (oAuth2Client.ClientCredentialOptions, authorizationState);

    var fullAuthorizationUri = oAuth2Client.AuthorizationCodeOptions
      .GenerateFullAuthorizationUri(clientId, authorizationState,
        authorizationRedirectUri);
    return fullAuthorizationUri;
  }
}

Let's study DefaultOAuth2AuthorizationUriGenerator to see what it does.

Constructor: The constructor accepts instances of IOAuth2ClientDataStore and a list of OAuth2Client objects. This not only promotes modularity but also ensures flexibility in handling OAuth2 client data.

GenerateAuthorizationUri Method: The heart of the class lies in the GenerateAuthorizationUri method. This method orchestrates a series of steps, from retrieving the appropriate OAuth2 client to generating a full authorization URI. We explore the nuances of client selection, authorization state creation, and seamless integration with the client data store.

4-DefaultOAuth2ClientGateway 

internal sealed class DefaultOAuth2ClientGateway : IOAuth2ClientGateway
{
  private readonly IHttpClientFactory _httpClientFactory;
  private readonly IHttpContextAccessor _httpContextAccessor;
  private readonly List<OAuth2Client> _oAuth2Clients;

  public DefaultOAuth2ClientGateway(IHttpClientFactory httpClientFactory,
    IHttpContextAccessor httpContextAccessor,
    List<OAuth2Client> oAuth2Clients)
  {
    _httpClientFactory = httpClientFactory;
    _httpContextAccessor = httpContextAccessor;
    _oAuth2Clients = oAuth2Clients;
  }

  public async Task<AccessTokenResponse> GetAccessTokenResponse
    (string clientId, string authorizationCode)
  {
    var oauth2Client = GetOAuth2Client(clientId);

    HttpClient httpClient = _httpClientFactory.CreateClient();

    var authValue = new AuthenticationHeaderValue
      ("Basic", Convert.ToBase64String
        (Encoding.UTF8.GetBytes(
        $"{oauth2Client.ClientCredentialOptions.ClientId}:" +
        $"{oauth2Client.ClientCredentialOptions.ClientSecret}")));
    httpClient.DefaultRequestHeaders.Authorization = authValue;

    var authorizationRedirectUri = _httpContextAccessor
      .HttpContext.Request.CreateAuthorizationRedirectUri();
    var requestBody = new FormUrlEncodedContent(
      oauth2Client.AccessTokenOptions.GenerateSettings
      (authorizationCode, authorizationRedirectUri));

    AccessTokenResponse accessTokenResponse = null;

    try
    {
      var response = await httpClient.PostAsync(
        oauth2Client.AccessTokenOptions.BaseAccessTokenUri, requestBody);

      var responseContent =
        await response.Content.ReadAsStringAsync();

      if (response.IsSuccessStatusCode)
      {
        accessTokenResponse = JsonConvert.
          DeserializeObject<AccessTokenResponse>(responseContent);

        SetRefreshTokenFieldIfNecessary(accessTokenResponse);
        SetExpiresInFieldIfNecessary(oauth2Client, accessTokenResponse);
      }
    }
    catch (Exception exception)
    {
      throw new OAuth2MultiClientIntegratorFailureException(exception.Message);
    }

    return accessTokenResponse;
  }
  public async Task<AccessTokenResponse> GetRefreshAccessTokenResponse
    (string clientId, string refreshToken)
  {
    var oauth2Client = GetOAuth2Client(clientId);

    HttpClient httpClient = _httpClientFactory.CreateClient();

    var authValue = new AuthorizationHeaderValue
      ("Basic", Convert.ToBase64String
        (Encoding.UTF8.GetBytes(
        $"{oauth2Client.ClientCredentialOptions.ClientId}:" +
        $"{oauth2Client.ClientCredentialOptions.ClientSecret}")));
    httpClient.DefaultRequestHeaders.Authorization = authValue;

    var requestBody = new FormUrlEncodedContent(
      oauth2Client.RefreshTokenOptions.GenerateSettings(refreshToken));

    AccessTokenResponse accessTokenResponse = null;

    try
    {
      var response = await httpClient.PostAsync(
        oauth2Client.RefreshTokenOptions.BaseRefreshTokenUri, requestBody);

      var responseContent =
        await response.Content.ReadAsStringAsync();

      if (response.IsSuccessStatusCode)
      {
        accessTokenResponse = JsonConvert.
          DeserializeObject<AccessTokenResponse>(responseContent);

        SetRefreshTokenFieldIfNecessary(accessTokenResponse);
        SetExpiresInFieldIfNecessary(oauth2Client, accessTokenResponse);
      }
    }
    catch (Exception exception)
    {
      throw new OAuth2MultiClientIntegratorFailureException(exception.Message);
    }

    return accessTokenResponse;
  }

  private OAuth2Client GetOAuth2Client(string clientId)
  {
    var oAuth2Client = _oAuth2Clients.FirstOrDefault
      (t => t.ClientCredentialOptions.ClientId == clientId);
    if (oAuth2Client == null)
    {
      throw new OAuth2MultiClientIntegratorFailureException
        ("No suitable OAuthClient found.");
    }

    return oAuth2Client;
  }
  private static void SetExpiresInFieldIfNecessary
    (OAuth2Client oauth2Client,
    AccessTokenResponse accessTokenResponse)
  {
    if (!oauth2Client.AccessTokenOptions.CanRenewAfterExpiration &&
      accessTokenResponse.ExpiresIn == 0)
    {
      accessTokenResponse.ExpiresIn =
        oauth2Client.AccessTokenOptions.StaticExpirationValue;
    }
  }
  private static void SetRefreshTokenFieldIfNecessary
    (AccessTokenResponse accessTokenResponse)
  {
    if (string.IsNullOrWhiteSpace(accessTokenResponse.RefreshToken))
    {
      accessTokenResponse.RefreshToken =
        accessTokenResponse.AccessToken;
    }
  }
}

Let's look at the DefaultOAuth2ClientGateway class in more detail.

Constructor: This constructor initializes the DefaultOAuth2ClientGateway class with essential dependencies, including an IHttpClientFactory for creating HTTP clients, an IHttpContextAccessor for accessing the current HTTP context, and a list of OAuth2Client instances containing OAuth2 client information. This setup ensures that the gateway has the necessary tools to interact with external OAuth2 services.

GetAccessTokenResponse Method: This method is a key component for obtaining an access token response by exchanging an authorization code with the OAuth2 service.

GetOAuth2Client Method: This private method retrieves the specific OAuth2Client instance associated with a given client ID from the list of clients. If no matching client is found, it throws an OAuth2MultiClientIntegratorFailureException.

SetExpiresInFieldIfNecessary Method: This method ensures that the expiration time (ExpiresIn) of the access token is appropriately set. It checks whether token renewal is allowed and whether the expiration value needs adjustment, updating the AccessTokenResponse accordingly.

SetRefreshTokenFieldIfNecessary Method: This method sets the refresh token field in the AccessTokenResponse if it is missing. If the refresh token is not provided by the OAuth2 service, it defaults to the access token value, ensuring a fallback mechanism for token refresh.

5-RegisterOAuth2IdentityServerCallbackEndpoint

internal static class ConfigureOAuthMultiClientIntegratorAppSettings
{
  public static IApplicationBuilder RegisterOAuth2IdentityServerCallbackEndpoint
    (this IApplicationBuilder applicationBuilder)
  {
    applicationBuilder.UseEndpoints(endpointRouteBuilder =>
    {
      endpointRouteBuilder.MapGet(
      OAuth2ClientManagerSettings.DefaultOAuth2RedirectUri,
      async httpContext =>
      {
        using var serviceScope = applicationBuilder.ApplicationServices.CreateScope();

        var oauth2TargetUriGenerator = serviceScope.ServiceProvider
          .GetRequiredService<IOAuth2TargetUriGenerator>();

        string authorizationCode = httpContext.Request.Query["code"]!;
        string authorizationState = httpContext.Request.Query["state"]!;

        string targetUri = await oauth2TargetUriGenerator
          .GenerateTargetUri(authorizationCode, authorizationState);

        httpContext.Response.Redirect(targetUri);
      });
    });

    return applicationBuilder;
  }
}

RegisterOAuth2IdentityServerCallbackEndpoint Method: This static method extends the IApplicationBuilder interface to register an endpoint for handling OAuth2 Identity Server callbacks. The purpose of this method is to seamlessly integrate the OAuth2 authorization server callback into the application's routing. When the authorization server authorizes the client successfully, it returns the authorization code and authorization state to the client by calling its endpoint. After that, the package navigates the client to its predefined target URI. Target URI most time is the first page of the web application or a web page that is supposed to provide features regarding the requested client.

6-DefaultDbBasedClientDataStore

internal sealed class DefaultOAuth2TargetUriGenerator : IOAuth2TargetUriGenerator
{
  private readonly IOAuth2ClientDataStore _oauth2ClientDataStore;
  private readonly List<OAuth2Client> _oAuth2Clients;
  private readonly IDateTimeProvider _dateTimeProvider;

  public DefaultOAuth2TargetUriGenerator(
    IOAuth2ClientDataStore oauth2ClientDataStore,
    List<OAuth2Client> oAuth2Clients,
    IDateTimeProvider dateTimeProvider)
  {
    _oauth2ClientDataStore = oauth2ClientDataStore;
    _oAuth2Clients = oAuth2Clients;
    _dateTimeProvider = dateTimeProvider;
  }
  public async Task<string> GenerateTargetUri
    (string authorizationCode, string authorizationState)
  {
    var dataNotReturnedCorrectly =
      authorizationCode == null ||
      authorizationState == null;
    if (dataNotReturnedCorrectly)
    {
      throw new OAuth2MultiClientIntegratorFailureException
        ("IdentityServer callback data is missing.");
    }

    var clientId = authorizationState!.Split(',').LastOrDefault();
    var oAuth2Client = _oAuth2Clients.FirstOrDefault
      (t => t.ClientCredentialOptions.ClientId == clientId);
    if (oAuth2Client == null)
    {
      throw new OAuth2MultiClientIntegratorFailureException
        ("No suitable OAuthClient found.");
    }

    var expectedAuthorizationState = await
      _oauth2ClientDataStore.GetAuthorizationState(clientId);
    if (expectedAuthorizationState != authorizationState)
    {
      throw new PossibleOAuth2CsrfAttackException();
    }

    var authorizationCodeResponse = new AuthorizationCodeResponse
        (authorizationCode!, oAuth2Client.AuthorizationCodeOptions
        .StaticExpirationValue, _dateTimeProvider.GetUTCDateTimeNow);

    await _oauth2ClientDataStore.SetAuthorizationCodeResponse
        (oAuth2Client.ClientCredentialOptions, authorizationCodeResponse);

    return oAuth2Client.AuthorizationCodeOptions.TargetUri;
  }
}

Let's go through each method of the DefaultDbBasedClientDataStore in detail:

Constructor: This constructor initializes the DefaultOAuth2TargetUriGenerator class with essential dependencies, including an IOAuth2ClientDataStore for managing OAuth2 client data, a list of OAuth2Client instances containing OAuth2 client information, and an IDateTimeProvider for handling date and time-related operations. This constructor ensures that the generator has the necessary tools to construct target URIs.

GenerateTargetUri Method: This method is responsible for generating a target URI based on the provided authorization code and authorization state.

7-DefaultDbBasedClientDataStore

internal sealed class DefaultDbBasedClientDataStore : IOAuth2ClientDataStore
{
  private readonly IOAuth2ServerAuthInfoRepository _oAuth2ServerAuthInfoRepository;

  public DefaultDbBasedClientDataStore(
    IOAuth2ServerAuthInfoRepository oauth2ServerAuthInfoRepository)
  {
    _oAuth2ServerAuthInfoRepository = oauth2ServerAuthInfoRepository;
  }

  public async Task<AuthorizationCodeResponse>
    GetAuthorizationCodeResponse(string clientId)
  {
    var oAuth2ServerAuthInfo = await
      _oAuth2ServerAuthInfoRepository.Get(clientId);
    if (oAuth2ServerAuthInfo != null)
    {
      return oAuth2ServerAuthInfo.AuthorizationCodeResponse;
    }

    return default;
  }
  public async Task SetAuthorizationCodeResponse(
    ClientCredentialOptions clientCredentialOptions,
    AuthorizationCodeResponse authorizationCodeResponse)
  {
    var oAuth2ServerAuthInfo = await _oAuth2ServerAuthInfoRepository
      .Get(clientCredentialOptions.ClientId);
    if (oAuth2ServerAuthInfo == null)
    {
      var newOAuth2ServerAuthInfo =
        new OAuth2ServerAuthInfo(clientCredentialOptions,
          authorizationCodeResponse);
      await _oAuth2ServerAuthInfoRepository
        .Add(newOAuth2ServerAuthInfo);
    }
    else
    {
      oAuth2ServerAuthInfo.UpdateAuthorizationCodeResponse
        (authorizationCodeResponse);
      await _oAuth2ServerAuthInfoRepository
        .Update(oAuth2ServerAuthInfo);
    }

    await _oAuth2ServerAuthInfoRepository.CommitAsync();
  }

  public async Task<string> GetAuthorizationState(string clientId)
  {
    var oAuth2ServerAuthInfo = await
      _oAuth2ServerAuthInfoRepository.Get(clientId);
    if (oAuth2ServerAuthInfo != null)
    {
      return oAuth2ServerAuthInfo.AuthorizationState;
    }

    return default;
  }
  public async Task SetAuthorizationState(
    ClientCredentialOptions clientCredentialOptions
    , string authorizationState)
  {
    var oAuth2ServerAuthInfo =
      await _oAuth2ServerAuthInfoRepository
      .Get(clientCredentialOptions.ClientId);
    if (oAuth2ServerAuthInfo == null)
    {
      var newOAuth2ServerAuthInfo =
        new OAuth2ServerAuthInfo(clientCredentialOptions,
        authorizationState);
      await _oAuth2ServerAuthInfoRepository
        .Add(newOAuth2ServerAuthInfo);
    }
    else
    {
      oAuth2ServerAuthInfo
        .UpdateAuthorizationState(authorizationState);
      await _oAuth2ServerAuthInfoRepository
        .Update(oAuth2ServerAuthInfo);
    }

    await _oAuth2ServerAuthInfoRepository.CommitAsync();
  }

  public async Task<AccessTokenResponse> GetAccessTokenResponse(string clientId)
  {
    var oAuth2ServerAuthInfo =
      await _oAuth2ServerAuthInfoRepository.Get(clientId);
    if (oAuth2ServerAuthInfo != null)
    {
      return oAuth2ServerAuthInfo.AccessTokenResponse;
    }

    return default;
  }
  public async Task SetAccessTokenResponse(
    ClientCredentialOptions clientCredentialOptions,
    AccessTokenResponse accessTokenResponse)
  {
    var oAuth2ServerAuthInfo =
      await _oAuth2ServerAuthInfoRepository
      .Get(clientCredentialOptions.ClientId);
    if (oAuth2ServerAuthInfo == null)
    {
      var newOAuth2ServerAuthInfo =
        new OAuth2ServerAuthInfo(clientCredentialOptions,
        accessTokenResponse);
      await _oAuth2ServerAuthInfoRepository
        .Add(newOAuth2ServerAuthInfo);
    }
    else
    {
      oAuth2ServerAuthInfo
        .UpdateAccessTokenResponse(accessTokenResponse);
      await _oAuth2ServerAuthInfoRepository
        .Update(oAuth2ServerAuthInfo);
    }

    await _oAuth2ServerAuthInfoRepository.CommitAsync();
  }

  public async Task ClearDataStore(string clientId)
  {
    var oAuth2ServerAuthInfo =
      await _oAuth2ServerAuthInfoRepository.Get(clientId);
    if (oAuth2ServerAuthInfo != null)
    {
      await _oAuth2ServerAuthInfoRepository
        .Delete(oAuth2ServerAuthInfo);
      await _oAuth2ServerAuthInfoRepository.CommitAsync();
    }
  }
}

Let's break down the code and describe DefaultDbBasedClientDataStore's functionality:

Constructor: Initializes the class with an instance of IOAuth2ServerAuthInfoRepository, representing the repository for storing and retrieving OAuth2 server authorization information.

GetAuthorizationCodeResponse Method: Retrieves the authorization code response associated with a specific client ID from the repository. Returns the authorization code response if found; otherwise, returns the default value.

SetAuthorizationCodeResponse Method: Updates or adds OAuth2 server authorization information, including the authorization code response, based on the provided client credentials and authorization code response. Ensures data consistency by committing changes to the repository after the update.

GetAuthorizationState Method: Retrieves the authorization state associated with a specific client ID from the repository. Returns the authorization state if found; otherwise, returns the default value.

SetAuthorizationState Method: Updates or adds OAuth2 server authorization information, including the authorization state, based on the provided client credentials and authorization state. Commits changes to the repository after the update.

GetAccessTokenResponse Method: Retrieves the access token response associated with a specific client ID from the repository. Returns the access token response if found; otherwise, returns the default value.

SetAccessTokenResponse Method: Updates or adds OAuth2 server authorization information, including the access token response, based on the provided client credentials and access token response.

ClearDataStore Method: Removes the OAuth2 server authorization information associated with a specific client ID from the repository.