Getting Twitter Access Secret using DotNetOpenAuth

2020-03-04 09:30发布

问题:

I'm creating an app with MVC4 that will authorize users using Twitter and lets them tweet from the app as well. I'm able to get the user authenticated without a problem using the BuiltInOAuthClient.Twitter that is in MVC4. http://www.asp.net/web-pages/tutorials/security/enabling-login-from-external-sites-in-an-aspnet-web-pages-site

I have the access token, and oauth_verifier, but I need to get the acess_secret back from Twitter as well. https://dev.twitter.com/docs/auth/implementing-sign-twitter

What I'm missing is how to pass the oauth_verifier back to Twitter to get the access secret using OAuthWebSecurity.

Again, I can use Twitter for the login ok, but I need to be able to use twitter as the user as well. I've done this with the TweetSharp library before, but am trying to use DotNetOpenAuth on this project.

UPDATE: I'm using the OAuthWebSecurity class as described in the first link to manage authentication. OAuthWebSecurity.RegisterClient in the AuthConfig expects a DotNetOpenAuth.AspNet.IAuthenticationClient. You can't swap that out with the TwitterConsumer class as suggested.

I can use the "built in" DotNetOpenAuth authentication piece as described in the first link, OR I can use custom code to do the full authorization, but I'm trying to find a way to do both.

I can do it separately, but then the user is presented with the Twitter dialog twice (once to login and once to authorize). I'm hoping there's a way to use the already wired up authentication piece that uses OAuthWebSecurity but ad the authorization piece as well.

回答1:

I've been banging my head against a wall with this for a few days now, but I finally have something that works. Would be interested to know if it's a valid solution though!

First off, create a new OAuthClient:

public class TwitterClient : OAuthClient
{
    /// <summary>
    /// The description of Twitter's OAuth protocol URIs for use with their "Sign in with Twitter" feature.
    /// </summary>
    public static readonly ServiceProviderDescription TwitterServiceDescription = new ServiceProviderDescription
    {
        RequestTokenEndpoint =
            new MessageReceivingEndpoint(
                "https://api.twitter.com/oauth/request_token",
                HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
        UserAuthorizationEndpoint =
            new MessageReceivingEndpoint(
                "https://api.twitter.com/oauth/authenticate",
                HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
        AccessTokenEndpoint =
            new MessageReceivingEndpoint(
                "https://api.twitter.com/oauth/access_token",
                HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
        TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() },
    };

    public TwitterClient(string consumerKey, string consumerSecret) :
        base("twitter", TwitterServiceDescription, consumerKey, consumerSecret) { }

    /// Check if authentication succeeded after user is redirected back from the service provider.
    /// The response token returned from service provider authentication result. 
    protected override AuthenticationResult VerifyAuthenticationCore(AuthorizedTokenResponse response)
    {
        string accessToken = response.AccessToken;
        string accessSecret = (response as ITokenSecretContainingMessage).TokenSecret;
        string userId = response.ExtraData["user_id"];
        string userName = response.ExtraData["screen_name"];

        var extraData = new Dictionary<string, string>()
                            {
                                {"accesstoken", accessToken},
                                {"accesssecret", accessSecret}
                            };
        return new AuthenticationResult(
            isSuccessful: true,
            provider: ProviderName,
            providerUserId: userId,
            userName: userName,
            extraData: extraData);
    }
}

The important part is where you cast the response to an ITokenSecretContainingMessage. It appears that the response has the TokenSecret all along, but it is only on an internal property. By casting it, you get access to a public property. I can't say that I'm a fan of doing this, but then I also don't understand why DotNetOpenAuth the Asp.Net team have hidden the property in the first place. There must be a good reason.

You then register this client in AuthConfig:

OAuthWebSecurity.RegisterClient( new TwitterClient(
    consumerKey: "",
    consumerSecret: ""), "Twitter", null);

Now, in the ExternalLoginCallback method on the AccountController, the accessSecret is available in the ExtraData dictionary.



回答2:

The DotNetOpenAuth.AspNet.Clients.TwitterClient class only allows authentication, not authorization. So you wouldn't be able to post tweets as that user if you use that class.

Instead, you can use DotNetOpenAuth.ApplicationBlock.TwitterConsumer, which does not share this limitation and you can even copy the source code for this type into your application and extend it as necessary.

You should be able to enhance the TwitterConsumer class (once you've copied it into your own project) to implement the required interface so that the OAuthWebSecurity class will accept it. Otherwise, you can just use TwitterConsumer directly yourself to both authenticate and authorize your web app so the user only sees Twitter once but you get all the control you need. After all, folks using ASP.NET have been using TwitterConsumer to both login and authorize for subsequent calls to Twitter for long before OAuthWebSecurity even existed.



回答3:

For a WebForms project template which references Microsoft.AspNet.Membership.OpenAuth in AuthConfig.cs instead of Microsoft.Web.WebPages.OAuth (MVC4 Internet Application) I was able to modify Paul Manzotti's answer to get it to work:

  1. Create a custom twitter client class that derives from DotNetOpenAuth.AspNet.Clients.TwitterClient

    public class CustomTwitterClient : TwitterClient { public CustomTwitterClient(string consumerKey, string consumerSecret) : base(consumerKey, consumerSecret) { }

    protected override AuthenticationResult VerifyAuthenticationCore(AuthorizedTokenResponse response)
    {
        //return base.VerifyAuthenticationCore(response);
        string accessToken = response.AccessToken;
        string accessSecret = (response as ITokenSecretContainingMessage).TokenSecret;
        string userId = response.ExtraData["user_id"];
        string userName = response.ExtraData["screen_name"];
    
        var extraData = new Dictionary<string, string>()
                        {
                            {"accesstoken", accessToken},
                            {"accesssecret", accessSecret}
                        };
        return new AuthenticationResult(
            isSuccessful: true,
            provider: ProviderName,
            providerUserId: userId,
            userName: userName,
            extraData: extraData);
    }
    

    }

  2. Add the custom client in AuthConfig.cs

    public static void RegisterOpenAuth()
    {
        OpenAuth.AuthenticationClients.Add("Twitter", () => new CustomTwitterClient(
            consumerKey: ConfigurationManager.AppSettings["twitterConsumerKey"], 
            consumerSecret: ConfigurationManager.AppSettings["twitterConsumerSecret"]));
    }
    

Ta-dow! Now you can haz access secret.



回答4:

You can extract the oauth_token_secret from OAuthWebSecurity by designing your own TokenManager. You can register the token manager when you register your Twitter client in OAuthWebSecurity.RegisterClient.

I used this method to extract the needed values to be able to bypass the authorization step of the Linq-to-Twitter lib.

I will soon post my solution at my blog.