internal string EncodeToken(ClaimsPrincipal user, StoredTokenData token)
        {
            byte[] encoded = GitHubTokenEncoder.EncodeToken(new GitHubTokenData(token.UserId,
                                                                                token.TokenId,
                                                                                token.Expiration,
                                                                                _resolver.GetAccessToken(user)));

            byte[] prot = _dataProtector.Protect(encoded);

            return(Convert.ToBase64String(prot));
        }
        private bool TryDecodeToken(string protectedToken, out GitHubTokenData token)
        {
            Span <byte> tokenBytes = stackalloc byte[1024];

            if (!Convert.TryFromBase64String(protectedToken, tokenBytes, out int bytesWritten))
            {
                token = default;
                return(false);
            }

            // DataProtector can't handle spans, so we have to copy out the value
            var toUnprotect = new byte[bytesWritten];

            tokenBytes.Slice(0, bytesWritten).CopyTo(toUnprotect.AsSpan());

            byte[] unprotected;
            try
            {
                unprotected = _dataProtector.Unprotect(toUnprotect);
            }
            catch (Exception e)
            {
                Logger.LogWarning("IDataProtector.Unprotect exception: {exception}", e);
                token = default;
                return(false);
            }

            if (!GitHubTokenEncoder.TryDecodeToken(unprotected, Logger, out GitHubTokenData candidateToken))
            {
                Logger.LogWarning("Malformed token, aborting");
                token = default;
                return(false);
            }

            if (candidateToken.Expiration < DateTimeOffset.UtcNow)
            {
                Logger.LogInformation("Expired token used, expired on {expiration}, from user {user}, with id {id}",
                                      candidateToken.Expiration,
                                      candidateToken.UserId,
                                      candidateToken.TokenId);
                token = default;
                return(false);
            }

            token = candidateToken;
            return(true);
        }