File: TwitterTests.cs
Web Access
Project: src\src\Security\Authentication\test\Microsoft.AspNetCore.Authentication.Test.csproj (Microsoft.AspNetCore.Authentication.Test)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Net.Http.Headers;
 
namespace Microsoft.AspNetCore.Authentication.Twitter;
 
public class TwitterTests : RemoteAuthenticationTests<TwitterOptions>
{
    protected override string DefaultScheme => TwitterDefaults.AuthenticationScheme;
    protected override Type HandlerType => typeof(TwitterHandler);
    protected override bool SupportsSignIn { get => false; }
    protected override bool SupportsSignOut { get => false; }
 
    protected override void RegisterAuth(AuthenticationBuilder services, Action<TwitterOptions> configure)
    {
        services.AddTwitter(o =>
        {
            ConfigureDefaults(o);
            configure.Invoke(o);
        });
    }
 
    protected override void ConfigureDefaults(TwitterOptions o)
    {
        o.ConsumerKey = "PLACEHOLDER";
        o.ConsumerSecret = "PLACEHOLDER";
        o.SignInScheme = "auth1";
    }
 
    [Fact]
    public async Task ChallengeWillTriggerApplyRedirectEvent()
    {
        using var host = await CreateHost(o =>
        {
            o.ConsumerKey = "Test Consumer Key";
            o.ConsumerSecret = "Test Consumer Secret";
            o.Events = new TwitterEvents
            {
                OnRedirectToAuthorizationEndpoint = context =>
                {
                    context.Response.Redirect(context.RedirectUri + "&custom=test");
                    return Task.FromResult(0);
                }
            };
            o.BackchannelHttpHandler = new TestHttpMessageHandler
            {
                Sender = BackchannelRequestToken
            };
        },
        async context =>
        {
            await context.ChallengeAsync("Twitter");
            return true;
        });
        using var server = host.GetTestServer();
        var transaction = await server.SendAsync("http://example.com/challenge");
        Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
        var query = transaction.Response.Headers.Location.Query;
        Assert.Contains("custom=test", query);
    }
 
    /// <summary>
    /// Validates the Twitter Options to check if the Consumer Key is missing in the TwitterOptions and if so throws the ArgumentException
    /// </summary>
    /// <returns></returns>
    [Fact]
    public async Task ThrowsIfClientIdMissing()
    {
        using var host = await CreateHost(o =>
        {
            o.ConsumerSecret = "Test Consumer Secret";
        });
 
        using var server = host.GetTestServer();
        await Assert.ThrowsAsync<ArgumentNullException>("ConsumerKey", async () => await server.SendAsync("http://example.com/challenge"));
    }
 
    /// <summary>
    /// Validates the Twitter Options to check if the Consumer Secret is missing in the TwitterOptions and if so throws the ArgumentException
    /// </summary>
    /// <returns></returns>
    [Fact]
    public async Task ThrowsIfClientSecretMissing()
    {
        using var host = await CreateHost(o =>
        {
            o.ConsumerKey = "Test Consumer Key";
        });
 
        using var server = host.GetTestServer();
        await Assert.ThrowsAsync<ArgumentNullException>("ConsumerSecret", async () => await server.SendAsync("http://example.com/challenge"));
    }
 
    [Fact]
    public async Task BadSignInWillThrow()
    {
        using var host = await CreateHost(o =>
        {
            o.ConsumerKey = "Test Consumer Key";
            o.ConsumerSecret = "Test Consumer Secret";
        });
 
        // Send a bogus sign in
        using var server = host.GetTestServer();
        var error = await Assert.ThrowsAnyAsync<Exception>(() => server.SendAsync("https://example.com/signin-twitter"));
        Assert.Equal("Invalid state cookie.", error.GetBaseException().Message);
    }
 
    [Fact]
    public async Task SignInThrows()
    {
        using var host = await CreateHost(o =>
        {
            o.ConsumerKey = "Test Consumer Key";
            o.ConsumerSecret = "Test Consumer Secret";
        });
        using var server = host.GetTestServer();
        var transaction = await server.SendAsync("https://example.com/signIn");
        Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode);
    }
 
    [Fact]
    public async Task SignOutThrows()
    {
        using var host = await CreateHost(o =>
        {
            o.ConsumerKey = "Test Consumer Key";
            o.ConsumerSecret = "Test Consumer Secret";
        });
        using var server = host.GetTestServer();
        var transaction = await server.SendAsync("https://example.com/signOut");
        Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode);
    }
 
    [Fact]
    public async Task ForbidThrows()
    {
        using var host = await CreateHost(o =>
        {
            o.ConsumerKey = "Test Consumer Key";
            o.ConsumerSecret = "Test Consumer Secret";
        });
        using var server = host.GetTestServer();
        var transaction = await server.SendAsync("https://example.com/signOut");
        Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode);
    }
 
    [Fact]
    public async Task ChallengeWillTriggerRedirection()
    {
        using var host = await CreateHost(o =>
        {
            o.ConsumerKey = "Test Consumer Key";
            o.ConsumerSecret = "Test Consumer Secret";
            o.BackchannelHttpHandler = new TestHttpMessageHandler
            {
                Sender = BackchannelRequestToken
            };
        },
        async context =>
        {
            await context.ChallengeAsync("Twitter");
            return true;
        });
        using var server = host.GetTestServer();
        var transaction = await server.SendAsync("http://example.com/challenge");
        Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
        var location = transaction.Response.Headers.Location.AbsoluteUri;
        Assert.Contains("https://api.twitter.com/oauth/authenticate?oauth_token=", location);
    }
 
    [Fact]
    public async Task HandleRequestAsync_RedirectsToAccessDeniedPathWhenExplicitlySet()
    {
        using var host = await CreateHost(o =>
        {
            o.ConsumerKey = "Test Consumer Key";
            o.ConsumerSecret = "Test Consumer Secret";
            o.BackchannelHttpHandler = new TestHttpMessageHandler
            {
                Sender = BackchannelRequestToken
            };
            o.AccessDeniedPath = "/access-denied";
            o.Events.OnRemoteFailure = context => throw new InvalidOperationException("This event should not be called.");
        },
        async context =>
        {
            var properties = new AuthenticationProperties();
            properties.Items["testkey"] = "testvalue";
            await context.ChallengeAsync("Twitter", properties);
            return true;
        });
        using var server = host.GetTestServer();
        var transaction = await server.SendAsync("http://example.com/challenge");
        Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
        var location = transaction.Response.Headers.Location.AbsoluteUri;
        Assert.Contains("https://api.twitter.com/oauth/authenticate?oauth_token=", location);
        Assert.True(transaction.Response.Headers.TryGetValues(HeaderNames.SetCookie, out var setCookie));
        Assert.True(SetCookieHeaderValue.TryParseList(setCookie.ToList(), out var setCookieValues));
        Assert.Single(setCookieValues);
        var setCookieValue = setCookieValues.Single();
        var cookie = new CookieHeaderValue(setCookieValue.Name, setCookieValue.Value);
 
        var request = new HttpRequestMessage(HttpMethod.Get, "/signin-twitter?denied=ABCDEFG");
        request.Headers.Add(HeaderNames.Cookie, cookie.ToString());
        var client = server.CreateClient();
        var response = await client.SendAsync(request);
 
        Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
        Assert.Equal("http://localhost/access-denied?ReturnUrl=%2Fchallenge", response.Headers.Location.ToString());
    }
 
    [Fact]
    public async Task BadCallbackCallsAccessDeniedWithState()
    {
        using var host = await CreateHost(o =>
        {
            o.ConsumerKey = "Test Consumer Key";
            o.ConsumerSecret = "Test Consumer Secret";
            o.BackchannelHttpHandler = new TestHttpMessageHandler
            {
                Sender = BackchannelRequestToken
            };
            o.Events = new TwitterEvents()
            {
                OnAccessDenied = context =>
                {
                    Assert.NotNull(context.Properties);
                    Assert.Equal("testvalue", context.Properties.Items["testkey"]);
                    context.Response.StatusCode = StatusCodes.Status406NotAcceptable;
                    context.HandleResponse();
                    return Task.CompletedTask;
                }
            };
        },
        async context =>
        {
            var properties = new AuthenticationProperties();
            properties.Items["testkey"] = "testvalue";
            await context.ChallengeAsync("Twitter", properties);
            return true;
        });
        using var server = host.GetTestServer();
        var transaction = await server.SendAsync("http://example.com/challenge");
        Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
        var location = transaction.Response.Headers.Location.AbsoluteUri;
        Assert.Contains("https://api.twitter.com/oauth/authenticate?oauth_token=", location);
        Assert.True(transaction.Response.Headers.TryGetValues(HeaderNames.SetCookie, out var setCookie));
        Assert.True(SetCookieHeaderValue.TryParseList(setCookie.ToList(), out var setCookieValues));
        Assert.Single(setCookieValues);
        var setCookieValue = setCookieValues.Single();
        var cookie = new CookieHeaderValue(setCookieValue.Name, setCookieValue.Value);
 
        var request = new HttpRequestMessage(HttpMethod.Get, "/signin-twitter?denied=ABCDEFG");
        request.Headers.Add(HeaderNames.Cookie, cookie.ToString());
        var client = server.CreateClient();
        var response = await client.SendAsync(request);
 
        Assert.Equal(HttpStatusCode.NotAcceptable, response.StatusCode);
    }
 
    [Fact]
    public async Task TwitterError_Json_ThrowsParsedException()
    {
        using var host = await CreateHost(o =>
        {
            o.ConsumerKey = "Test Consumer Key";
            o.ConsumerSecret = "Test Consumer Secret";
            o.BackchannelHttpHandler = new TestHttpMessageHandler
            {
                Sender = JsonErroredBackchannelRequestToken
            };
        },
        async context =>
        {
            await context.ChallengeAsync("Twitter");
            return true;
        });
        using var server = host.GetTestServer();
 
        var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
        {
            await server.SendAsync("http://example.com/challenge");
        });
 
        var expectedErrorMessage = "An error has occurred while calling the Twitter API, error's returned:" + Environment.NewLine
            + "Code: 32, Message: 'Could not authenticate you.'";
 
        Assert.Equal(expectedErrorMessage, exception.Message);
    }
 
    [Fact]
    public async Task TwitterError_UnknownContentType_ThrowsHttpException()
    {
        using var host = await CreateHost(o =>
        {
            o.ConsumerKey = "Test Consumer Key";
            o.ConsumerSecret = "Test Consumer Secret";
            o.BackchannelHttpHandler = new TestHttpMessageHandler
            {
                Sender = UnknownContentTypeErroredBackchannelRequestToken
            };
        },
        async context =>
        {
            await context.ChallengeAsync("Twitter");
            return true;
        });
        using var server = host.GetTestServer();
 
        await Assert.ThrowsAsync<HttpRequestException>(async () =>
        {
            await server.SendAsync("http://example.com/challenge");
        });
    }
 
    [Fact]
    public async Task BadCallbackCallsRemoteAuthFailedWithState()
    {
        using var host = await CreateHost(o =>
        {
            o.ConsumerKey = "Test Consumer Key";
            o.ConsumerSecret = "Test Consumer Secret";
            o.BackchannelHttpHandler = new TestHttpMessageHandler
            {
                Sender = BackchannelRequestToken
            };
            o.Events = new TwitterEvents()
            {
                OnRemoteFailure = context =>
                {
                    Assert.NotNull(context.Failure);
                    Assert.Equal("Access was denied by the resource owner or by the remote server.", context.Failure.Message);
                    Assert.NotNull(context.Properties);
                    Assert.Equal("testvalue", context.Properties.Items["testkey"]);
                    context.Response.StatusCode = StatusCodes.Status406NotAcceptable;
                    context.HandleResponse();
                    return Task.CompletedTask;
                }
            };
        },
        async context =>
        {
            var properties = new AuthenticationProperties();
            properties.Items["testkey"] = "testvalue";
            await context.ChallengeAsync("Twitter", properties);
            return true;
        });
 
        using var server = host.GetTestServer();
        var transaction = await server.SendAsync("http://example.com/challenge");
        Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
        var location = transaction.Response.Headers.Location.AbsoluteUri;
        Assert.Contains("https://api.twitter.com/oauth/authenticate?oauth_token=", location);
        Assert.True(transaction.Response.Headers.TryGetValues(HeaderNames.SetCookie, out var setCookie));
        Assert.True(SetCookieHeaderValue.TryParseList(setCookie.ToList(), out var setCookieValues));
        Assert.Single(setCookieValues);
        var setCookieValue = setCookieValues.Single();
        var cookie = new CookieHeaderValue(setCookieValue.Name, setCookieValue.Value);
 
        var request = new HttpRequestMessage(HttpMethod.Get, "/signin-twitter?denied=ABCDEFG");
        request.Headers.Add(HeaderNames.Cookie, cookie.ToString());
        var client = server.CreateClient();
        var response = await client.SendAsync(request);
 
        Assert.Equal(HttpStatusCode.NotAcceptable, response.StatusCode);
    }
 
    [Fact]
    public async Task CanSignIn()
    {
        var stateFormat = new SecureDataFormat<RequestToken>(new RequestTokenSerializer(), new EphemeralDataProtectionProvider(NullLoggerFactory.Instance).CreateProtector("TwitterTest"));
        using var host = await CreateHost((options) =>
        {
            options.ConsumerKey = "Test App Id";
            options.ConsumerSecret = "PLACEHOLDER";
            options.SaveTokens = true;
            options.StateDataFormat = stateFormat;
            options.BackchannelHttpHandler = new TestHttpMessageHandler
            {
                Sender = req =>
                {
                    if (req.RequestUri.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped) == "https://api.twitter.com/oauth/access_token")
                    {
                        var res = new HttpResponseMessage(HttpStatusCode.OK);
                        var content = new Dictionary<string, string>()
                        {
                            ["oauth_token"] = "Test Access Token",
                            ["oauth_token_secret"] = "PLACEHOLDER",
                            ["user_id"] = "123456",
                            ["screen_name"] = "@dotnet"
                        };
                        res.Content = new FormUrlEncodedContent(content);
                        return res;
                    }
                    return null;
                }
            };
        });
 
        var token = new RequestToken()
        {
            Token = "TestToken",
            TokenSecret = "PLACEHOLDER",
            Properties = new()
        };
 
        var correlationKey = ".xsrf";
        var correlationValue = "TestCorrelationId";
        token.Properties.Items.Add(correlationKey, correlationValue);
        token.Properties.RedirectUri = "/me";
        var state = stateFormat.Protect(token);
        using var server = host.GetTestServer();
        var transaction = await server.SendAsync(
            "https://example.com/signin-twitter?oauth_token=TestToken&oauth_verifier=TestVerifier",
            $".AspNetCore.Correlation.{correlationValue}=N;__TwitterState={UrlEncoder.Default.Encode(state)}");
        Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
        Assert.Equal("/me", transaction.Response.Headers.GetValues("Location").First());
 
        var authCookie = transaction.AuthenticationCookieValue;
        transaction = await server.SendAsync("https://example.com/me", authCookie);
        Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode);
        var expectedIssuer = TwitterDefaults.AuthenticationScheme;
        Assert.Equal("@dotnet", transaction.FindClaimValue(ClaimTypes.Name, expectedIssuer));
        Assert.Equal("123456", transaction.FindClaimValue(ClaimTypes.NameIdentifier, expectedIssuer));
        Assert.Equal("123456", transaction.FindClaimValue("urn:twitter:userid", expectedIssuer));
        Assert.Equal("@dotnet", transaction.FindClaimValue("urn:twitter:screenname", expectedIssuer));
 
        transaction = await server.SendAsync("https://example.com/tokens", authCookie);
        Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode);
        Assert.Equal("Test Access Token", transaction.FindTokenValue("access_token"));
        Assert.Equal("PLACEHOLDER", transaction.FindTokenValue("access_token_secret"));
    }
 
    [Fact]
    public async Task CanFetchUserDetails()
    {
        var verifyCredentialsEndpoint = "https://api.twitter.com/1.1/account/verify_credentials.json";
        var finalVerifyCredentialsEndpoint = string.Empty;
        var finalAuthorizationParameter = string.Empty;
        var stateFormat = new SecureDataFormat<RequestToken>(new RequestTokenSerializer(), new EphemeralDataProtectionProvider(NullLoggerFactory.Instance).CreateProtector("TwitterTest"));
        using var host = await CreateHost((options) =>
        {
            options.ConsumerKey = "Test App Id";
            options.ConsumerSecret = "PLACEHOLDER";
            options.RetrieveUserDetails = true;
            options.StateDataFormat = stateFormat;
            options.BackchannelHttpHandler = new TestHttpMessageHandler
            {
                Sender = req =>
                {
                    if (req.RequestUri.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped) == "https://api.twitter.com/oauth/access_token")
                    {
                        var res = new HttpResponseMessage(HttpStatusCode.OK);
                        var content = new Dictionary<string, string>()
                        {
                            ["oauth_token"] = "Test Access Token",
                            ["oauth_token_secret"] = "PLACEHOLDER",
                            ["user_id"] = "123456",
                            ["screen_name"] = "@dotnet"
                        };
                        res.Content = new FormUrlEncodedContent(content);
                        return res;
                    }
                    if (req.RequestUri.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped) ==
                        new Uri(verifyCredentialsEndpoint).GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped))
                    {
                        finalVerifyCredentialsEndpoint = req.RequestUri.ToString();
                        finalAuthorizationParameter = req.Headers.Authorization.Parameter;
                        var res = new HttpResponseMessage(HttpStatusCode.OK);
                        var graphResponse = "{ \"email\": \"Test email\" }"{ \"email\": \"Test email\" }";
                        res.Content = new StringContent(graphResponse, Encoding.UTF8);
                        return res;
                    }
                    return null;
                }
            };
        });
 
        var token = new RequestToken()
        {
            Token = "TestToken",
            TokenSecret = "PLACEHOLDER",
            Properties = new()
        };
 
        var correlationKey = ".xsrf";
        var correlationValue = "TestCorrelationId";
        token.Properties.Items.Add(correlationKey, correlationValue);
        token.Properties.RedirectUri = "/me";
        var state = stateFormat.Protect(token);
        using var server = host.GetTestServer();
        var transaction = await server.SendAsync(
            "https://example.com/signin-twitter?oauth_token=TestToken&oauth_verifier=TestVerifier",
            $".AspNetCore.Correlation.{correlationValue}=N;__TwitterState={UrlEncoder.Default.Encode(state)}");
        Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
        Assert.Equal("/me", transaction.Response.Headers.GetValues("Location").First());
 
        Assert.Equal(1, finalVerifyCredentialsEndpoint.Count(c => c == '?'));
        Assert.Contains("include_email=true", finalVerifyCredentialsEndpoint);
 
        Assert.Contains("oauth_consumer_key=", finalAuthorizationParameter);
        Assert.Contains("oauth_nonce=", finalAuthorizationParameter);
        Assert.Contains("oauth_signature=", finalAuthorizationParameter);
        Assert.Contains("oauth_signature_method=", finalAuthorizationParameter);
        Assert.Contains("oauth_timestamp=", finalAuthorizationParameter);
        Assert.Contains("oauth_token=", finalAuthorizationParameter);
        Assert.Contains("oauth_version=", finalAuthorizationParameter);
 
        var authCookie = transaction.AuthenticationCookieValue;
        transaction = await server.SendAsync("https://example.com/me", authCookie);
        Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode);
        var expectedIssuer = TwitterDefaults.AuthenticationScheme;
        Assert.Equal("@dotnet", transaction.FindClaimValue(ClaimTypes.Name, expectedIssuer));
        Assert.Equal("123456", transaction.FindClaimValue(ClaimTypes.NameIdentifier, expectedIssuer));
        Assert.Equal("123456", transaction.FindClaimValue("urn:twitter:userid", expectedIssuer));
        Assert.Equal("@dotnet", transaction.FindClaimValue("urn:twitter:screenname", expectedIssuer));
        Assert.Equal("Test email", transaction.FindClaimValue(ClaimTypes.Email, expectedIssuer));
    }
 
    private static async Task<IHost> CreateHost(Action<TwitterOptions> options, Func<HttpContext, Task<bool>> handler = null)
    {
        var host = new HostBuilder()
            .ConfigureWebHost(builder =>
                builder.UseTestServer()
                    .Configure(app =>
                    {
                        app.UseAuthentication();
                        app.Use(async (context, next) =>
                        {
                            var req = context.Request;
                            var res = context.Response;
                            if (req.Path == new PathString("/signIn"))
                            {
                                await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync("Twitter", new ClaimsPrincipal()));
                            }
                            else if (req.Path == new PathString("/signOut"))
                            {
                                await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync("Twitter"));
                            }
                            else if (req.Path == new PathString("/forbid"))
                            {
                                await Assert.ThrowsAsync<InvalidOperationException>(() => context.ForbidAsync("Twitter"));
                            }
                            else if (req.Path == new PathString("/me"))
                            {
                                await res.DescribeAsync(context.User);
                            }
                            else if (req.Path == new PathString("/tokens"))
                            {
                                var result = await context.AuthenticateAsync(TestExtensions.CookieAuthenticationScheme);
                                var tokens = result.Properties.GetTokens();
                                await res.DescribeAsync(tokens);
                            }
                            else if (handler == null || !await handler(context))
                            {
                                await next(context);
                            }
                        });
                    })
                    .ConfigureServices(services =>
                    {
                        Action<TwitterOptions> wrapOptions = o =>
                        {
                            o.SignInScheme = "External";
                            options(o);
                        };
                        services.AddAuthentication(TestExtensions.CookieAuthenticationScheme)
                            .AddCookie(TestExtensions.CookieAuthenticationScheme, o => o.ForwardChallenge = TwitterDefaults.AuthenticationScheme)
                            .AddTwitter(wrapOptions);
                    }))
            .Build();
 
        await host.StartAsync();
        return host;
    }
 
    private HttpResponseMessage BackchannelRequestToken(HttpRequestMessage req)
    {
        if (req.RequestUri.AbsoluteUri == "https://api.twitter.com/oauth/request_token")
        {
            return new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content =
                    new StringContent("oauth_callback_confirmed=true&oauth_token=test_oauth_token&oauth_token_secret=test_oauth_token_secret",
                        Encoding.UTF8,
                        "application/x-www-form-urlencoded")
            };
        }
        throw new NotImplementedException(req.RequestUri.AbsoluteUri);
    }
 
    private HttpResponseMessage JsonErroredBackchannelRequestToken(HttpRequestMessage req)
    {
        if (req.RequestUri.AbsoluteUri == "https://api.twitter.com/oauth/request_token")
        {
            return new HttpResponseMessage(HttpStatusCode.Forbidden)
            {
                Content =
                    new StringContent("{\"errors\":[{\"code\":32,\"message\":\"Could not authenticate you.\"}]}"{\"errors\":[{\"code\":32,\"message\":\"Could not authenticate you.\"}]}",
                        Encoding.UTF8,
                        "application/json")
            };
        }
        throw new NotImplementedException(req.RequestUri.AbsoluteUri);
    }
 
    private HttpResponseMessage UnknownContentTypeErroredBackchannelRequestToken(HttpRequestMessage req)
    {
        if (req.RequestUri.AbsoluteUri == "https://api.twitter.com/oauth/request_token")
        {
            return new HttpResponseMessage(HttpStatusCode.Forbidden)
            {
                Content =
                    new StringContent("example response text",
                        Encoding.UTF8,
                        "text/html")
            };
        }
        throw new NotImplementedException(req.RequestUri.AbsoluteUri);
    }
}