/// <summary>
        ///   Rigenera l'access token di OAuth2 partendo da un refresh token.
        /// </summary>
        /// <param name="owinContext">Il contesto HTTP di OWIN.</param>
        /// <param name="clientId">Il client ID a cui sono legati i token.</param>
        /// <param name="currentTokens">I token attualmente disponibili.</param>
        /// <returns>I nuovi token ottenuti in seguito al refresh.</returns>
        public async Task<OAuth2TokensCookie> RefreshAsync(IOwinContext owinContext, string clientId, OAuth2TokensCookie currentTokens)
        {
            // Registro la data di inizio in maniera conservativa.
            var accessTokenCreatedOn = _clock.UtcNow;

            var address = AuthorizationSettings.AccessTokenRefreshEndpointUri.AbsoluteUri;
            var clientSecret = AuthorizationSettings.Clients[clientId].ClientSecret;

            // Attivo il client per il refresh dei token.
            var tokenClient = new TokenClient(address, clientId, clientSecret);
            var tokenResponse = await tokenClient.RequestRefreshTokenAsync(currentTokens.RefreshToken);

            // Registro il piĆ¹ possibile in caso di errore.
            if (tokenResponse.IsError || tokenResponse.IsHttpError)
            {
                var tokenRespErr = string.IsNullOrWhiteSpace(tokenResponse.Error)
                    ? tokenResponse.HttpErrorReason
                    : tokenResponse.Error;

                var errMsg = $"{tokenResponse.HttpErrorStatusCode} - {tokenRespErr}";

                _log.Error(new LogMessage
                {
                    ShortMessage = errMsg,
                    LongMessage = tokenResponse.Raw,
                    Context = "Requesting new access and refresh tokens",
                    Arguments = new[]
                    {
                        KeyValuePair.Create("token_status_code", tokenResponse.HttpErrorStatusCode.ToString()),
                        KeyValuePair.Create("token_type", tokenResponse.TokenType)
                    }
                });

                // Lancio un'eccezione per uscire immediatamente.
                throw new Exception(errMsg);
            }

            return new OAuth2TokensCookie
            {
                AccessToken = tokenResponse.AccessToken,
                AccessTokenCreatedOn = accessTokenCreatedOn,
                AccessTokenExpiresIn = tokenResponse.ExpiresIn,
                RefreshToken = tokenResponse.RefreshToken,
                RandomToken = currentTokens.RandomToken
            };
        }
        /// <summary>
        ///   Richiede una batteria di token partendo da un dato authorization code.
        /// </summary>
        /// <param name="owinContext">Il contesto HTTP di OWIN.</param>
        /// <param name="clientId">Il client ID a cui sono legati i token.</param>
        /// <param name="authorizationCode">L'authorization code.</param>
        /// <returns>La batteria di token recuperati partendo dall'authorization code.</returns>
        public async Task<OAuth2TokensGenerationResult> RequestTokensAsync(IOwinContext owinContext, string clientId, string authorizationCode)
        {
            // Registro la data di inizio in maniera conservativa.
            var accessTokenCreatedOn = _clock.UtcNow;

            var address = AuthorizationSettings.AccessTokenRequestEndpointUri.AbsoluteUri;
            var clientSecret = AuthorizationSettings.Clients[clientId].ClientSecret;

            // Attivo il client per il recupero dei token.
            var tokenClient = new TokenClient(address, clientId, clientSecret);
            var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(authorizationCode, owinContext.Request.Uri.AbsoluteUri);

            if (tokenResponse.IsError)
            {
                Log.Error(tokenResponse.Error);
                throw new Exception(tokenResponse.Error);
            }

            // Copio tutti i token ricevuti in un cookie.
            var oauth2TokensCookie = new OAuth2TokensCookie
            {
                AccessToken = tokenResponse.AccessToken,
                AccessTokenCreatedOn = accessTokenCreatedOn,
                AccessTokenExpiresIn = tokenResponse.ExpiresIn,
                RefreshToken = tokenResponse.RefreshToken,
                RandomToken = Guid.NewGuid()
            };

            // Recupero l'identity token, se presente.
            var identityToken = string.IsNullOrWhiteSpace(tokenResponse.IdentityToken)
                ? Option.None<string>()
                : Option.Some(tokenResponse.IdentityToken);

            return new OAuth2TokensGenerationResult
            {
                TokensCookie = oauth2TokensCookie,
                IdentityToken = identityToken
            };
        }
        private async Task<OAuth2TokensCookie> RefreshAccessTokenAsync(IOwinContext owinContext, string clientId, OAuth2TokensCookie oauth2TokensCookie, string oauth2CsrfCookieContent)
        {
            // Indica se il token possa essere rinfrescato, o se vi siano altri tentativi concorrenti.
            var canRefresh = false;

            try
            {
                lock (_refreshingTokens)
                {
                    if (!_refreshingTokens.Contains(oauth2TokensCookie.RandomToken))
                    {
                        canRefresh = true;
                        _refreshingTokens.Add(oauth2TokensCookie.RandomToken);
                    }
                }
                if (!canRefresh)
                {
                    Log.Warn("Another thread or request is refreshing the same token, going to skip refresh at all");
                    return oauth2TokensCookie;
                }

                // Effettuo la richiesta per ottenere una nuova serie di token.
                oauth2TokensCookie = await _accessTokenRefresher.RefreshAsync(owinContext, clientId, oauth2TokensCookie);

                // Nel caso sia andato tutto bene, aggiorno il cookie con i token.
                var oauth2TokensCookieContent = await _tokensCookieEncoder.EncodeAsync(oauth2TokensCookie);

                // Elaboro le date di fine dei token e dei cookie.
                var accessTokenExpiresOn = oauth2TokensCookie.AccessTokenCreatedOn.AddSeconds(oauth2TokensCookie.AccessTokenExpiresIn);

                // Aggiungo il cookie con i token alla response.
                owinContext.Response.Cookies.Append(OAuth2TokensCookie.CookieName, oauth2TokensCookieContent, new CookieOptions
                {
                    HttpOnly = true, // Fondamentale che NON sia accessibile da JavaScript.
                    Expires = accessTokenExpiresOn
                });

                // Aggiungo il cookie di verifica per evitare XSRF.
                owinContext.Response.Cookies.Append(OAuth2CsrfCookie.CookieName, oauth2CsrfCookieContent, new CookieOptions
                {
                    HttpOnly = false, // Fondamentale che SIA accessibile anche da JavaScript.
                    Expires = accessTokenExpiresOn
                });

                return oauth2TokensCookie;
            }
            catch (Exception ex)
            {
                Log.Warn("An error occurred while trying to refresh the access token", ex);
                return oauth2TokensCookie;
            }
            finally
            {
                if (canRefresh)
                {
                    lock (_refreshingTokens)
                    {
                        _refreshingTokens.Remove(oauth2TokensCookie.RandomToken);
                    }
                }
            }
        }