ツイートを取得してウェブサイトに表示できるように、twitter で 2-legged oauth を取得しようとしています。このために、DotNetOpenAuth を使用したいと考えています。はじめに、DotNetOpenAuth に付属する OAuthConsumer の例を使用しました。
以下は、私が今持っている分離コードです。ここでは、3 脚パーツまたは 2 脚パーツのいずれかを使用します。3 本足のパーツは正常に動作します (これは、サンプルの Twitter.aspx に基づいています)。ただし、2 脚の部分 (GoogleApps2Legged.aspx に基づく) では、twitter.RequestNewClientAccount(); で 401 エラーが発生します。
private string AccessToken
{
get { return (string)Session["TwitterAccessToken"]; }
set { Session["TwitterAccessToken"] = value; }
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// 3 legged
var twitter = new WebConsumer(TwitterConsumer.ServiceDescription, TwitterConsumer.ShortTermUserSessionTokenManager);
//// Is Twitter calling back with authorization?
var accessTokenResponse = twitter.ProcessUserAuthorization();
if (accessTokenResponse != null)
{
this.AccessToken = accessTokenResponse.AccessToken;
}
else if (this.AccessToken == null)
{
// If we don't yet have access, immediately request it.
twitter.Channel.Send(twitter.PrepareRequestUserAuthorization());
}
var response = TwitterConsumer.GetUserTimeLine(twitter, this.AccessToken);
// 2 legged
var twitter = new WebConsumer(TwitterConsumer.ServiceDescription, TwitterConsumer.ShortTermUserSessionTokenManager);
this.AccessToken = twitter.RequestNewClientAccount();
var response = twitter.PrepareAuthorizedRequestAndSend(TwitterConsumer.GetUserTimelineStatusEndpoint, this.AccessToken);
}
}
TwitterConsumer.cs
//-----------------------------------------------------------------------
// <copyright file="TwitterConsumer.cs" company="Outercurve Foundation">
// Copyright (c) Outercurve Foundation. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.ApplicationBlock {
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Net;
using System.Web;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth;
using DotNetOpenAuth.OAuth.ChannelElements;
/// <summary>
/// A consumer capable of communicating with Twitter.
/// </summary>
public static class TwitterConsumer {
/// <summary>
/// The description of Twitter's OAuth protocol URIs for use with actually reading/writing
/// a user's private Twitter data.
/// </summary>
public static readonly ServiceProviderDescription ServiceDescription = new ServiceProviderDescription {
RequestTokenEndpoint = new MessageReceivingEndpoint("https://api.twitter.com/oauth/request_token", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
UserAuthorizationEndpoint = new MessageReceivingEndpoint("https://api.twitter.com/oauth/authorize", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
AccessTokenEndpoint = new MessageReceivingEndpoint("https://api.twitter.com/oauth/access_token", HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() },
};
/// <summary>
/// The description of Twitter's OAuth protocol URIs for use with their "Sign in with Twitter" feature.
/// </summary>
public static readonly ServiceProviderDescription SignInWithTwitterServiceDescription = 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.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() },
};
/// <summary>
/// The URI to get a user's favorites.
/// </summary>
private static readonly MessageReceivingEndpoint GetFavoritesEndpoint = new MessageReceivingEndpoint("http://api.twitter.com/1/favorites.xml", HttpDeliveryMethods.GetRequest);
/// <summary>
/// The URI to get the data on the user's home page.
/// </summary>
public static readonly MessageReceivingEndpoint GetFriendTimelineStatusEndpoint = new MessageReceivingEndpoint("http://api.twitter.com/1/statuses/friends_timeline.xml", HttpDeliveryMethods.GetRequest);
public static readonly MessageReceivingEndpoint UpdateProfileBackgroundImageEndpoint = new MessageReceivingEndpoint("http://api.twitter.com/1/account/update_profile_background_image.xml", HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest);
public static readonly MessageReceivingEndpoint UpdateProfileImageEndpoint = new MessageReceivingEndpoint("http://api.twitter.com/1/account/update_profile_image.xml", HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest);
public static readonly MessageReceivingEndpoint VerifyCredentialsEndpoint = new MessageReceivingEndpoint("http://api.twitter.com/1/account/verify_credentials.xml", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest);
public static readonly MessageReceivingEndpoint GetUserTimelineStatusEndpoint = new MessageReceivingEndpoint("http://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=concepts2go", HttpDeliveryMethods.GetRequest);
/// <summary>
/// The consumer used for the Sign in to Twitter feature.
/// </summary>
private static WebConsumer signInConsumer;
/// <summary>
/// The lock acquired to initialize the <see cref="signInConsumer"/> field.
/// </summary>
private static object signInConsumerInitLock = new object();
/// <summary>
/// Initializes static members of the <see cref="TwitterConsumer"/> class.
/// </summary>
static TwitterConsumer() {
// Twitter can't handle the Expect 100 Continue HTTP header.
ServicePointManager.FindServicePoint(GetFavoritesEndpoint.Location).Expect100Continue = false;
}
/// <summary>
/// Gets a value indicating whether the Twitter consumer key and secret are set in the web.config file.
/// </summary>
public static bool IsTwitterConsumerConfigured {
get {
return true;
}
}
/// <summary>
/// Gets the consumer to use for the Sign in to Twitter feature.
/// </summary>
/// <value>The twitter sign in.</value>
private static WebConsumer TwitterSignIn {
get {
if (signInConsumer == null) {
lock (signInConsumerInitLock) {
if (signInConsumer == null) {
signInConsumer = new WebConsumer(SignInWithTwitterServiceDescription, ShortTermUserSessionTokenManager);
}
}
}
return signInConsumer;
}
}
public static InMemoryTokenManager ShortTermUserSessionTokenManager {
get {
var store = HttpContext.Current.Session;
var tokenManager = (InMemoryTokenManager)store["TwitterShortTermUserSessionTokenManager"];
if (tokenManager == null) {
string consumerKey = "dgnwPLz40rcvmGKn0SugfA";
string consumerSecret = "IvaV0cA3N146mXTXPH5EaEHX8XbULch1vwBYpeI4Xc";
if (IsTwitterConsumerConfigured) {
tokenManager = new InMemoryTokenManager(consumerKey, consumerSecret);
store["TwitterShortTermUserSessionTokenManager"] = tokenManager;
} else {
throw new InvalidOperationException("No Twitter OAuth consumer key and secret could be found in web.config AppSettings.");
}
}
return tokenManager;
}
}
public static XDocument GetUpdates(ConsumerBase twitter, string accessToken) {
IncomingWebResponse response = twitter.PrepareAuthorizedRequestAndSend(GetFriendTimelineStatusEndpoint, accessToken);
return XDocument.Load(XmlReader.Create(response.GetResponseReader()));
}
public static IncomingWebResponse GetUserTimeLine(ConsumerBase twitter, string accessToken)
{
IncomingWebResponse response = twitter.PrepareAuthorizedRequestAndSend(GetUserTimelineStatusEndpoint, accessToken);
return response;
//return XDocument.Load(XmlReader.Create(response.GetResponseReader()));
}
public static XDocument GetFavorites(ConsumerBase twitter, string accessToken) {
IncomingWebResponse response = twitter.PrepareAuthorizedRequestAndSend(GetFavoritesEndpoint, accessToken);
return XDocument.Load(XmlReader.Create(response.GetResponseReader()));
}
public static XDocument UpdateProfileBackgroundImage(ConsumerBase twitter, string accessToken, string image, bool tile) {
var parts = new[] {
MultipartPostPart.CreateFormFilePart("image", image, "image/" + Path.GetExtension(image).Substring(1).ToLowerInvariant()),
MultipartPostPart.CreateFormPart("tile", tile.ToString().ToLowerInvariant()),
};
HttpWebRequest request = twitter.PrepareAuthorizedRequest(UpdateProfileBackgroundImageEndpoint, accessToken, parts);
request.ServicePoint.Expect100Continue = false;
IncomingWebResponse response = twitter.Channel.WebRequestHandler.GetResponse(request);
string responseString = response.GetResponseReader().ReadToEnd();
return XDocument.Parse(responseString);
}
public static XDocument UpdateProfileImage(ConsumerBase twitter, string accessToken, string pathToImage) {
string contentType = "image/" + Path.GetExtension(pathToImage).Substring(1).ToLowerInvariant();
return UpdateProfileImage(twitter, accessToken, File.OpenRead(pathToImage), contentType);
}
public static XDocument UpdateProfileImage(ConsumerBase twitter, string accessToken, Stream image, string contentType) {
var parts = new[] {
MultipartPostPart.CreateFormFilePart("image", "twitterPhoto", contentType, image),
};
HttpWebRequest request = twitter.PrepareAuthorizedRequest(UpdateProfileImageEndpoint, accessToken, parts);
IncomingWebResponse response = twitter.Channel.WebRequestHandler.GetResponse(request);
string responseString = response.GetResponseReader().ReadToEnd();
return XDocument.Parse(responseString);
}
public static XDocument VerifyCredentials(ConsumerBase twitter, string accessToken) {
IncomingWebResponse response = twitter.PrepareAuthorizedRequestAndSend(VerifyCredentialsEndpoint, accessToken);
return XDocument.Load(XmlReader.Create(response.GetResponseReader()));
}
public static string GetUsername(ConsumerBase twitter, string accessToken) {
XDocument xml = VerifyCredentials(twitter, accessToken);
XPathNavigator nav = xml.CreateNavigator();
return nav.SelectSingleNode("/user/screen_name").Value;
}
/// <summary>
/// Prepares a redirect that will send the user to Twitter to sign in.
/// </summary>
/// <param name="forceNewLogin">if set to <c>true</c> the user will be required to re-enter their Twitter credentials even if already logged in to Twitter.</param>
/// <returns>The redirect message.</returns>
/// <remarks>
/// Call <see cref="OutgoingWebResponse.Send"/> or
/// <c>return StartSignInWithTwitter().<see cref="MessagingUtilities.AsActionResult">AsActionResult()</see></c>
/// to actually perform the redirect.
/// </remarks>
public static OutgoingWebResponse StartSignInWithTwitter(bool forceNewLogin) {
var redirectParameters = new Dictionary<string, string>();
if (forceNewLogin) {
redirectParameters["force_login"] = "true";
}
Uri callback = MessagingUtilities.GetRequestUrlFromContext().StripQueryArgumentsWithPrefix("oauth_");
var request = TwitterSignIn.PrepareRequestUserAuthorization(callback, null, redirectParameters);
return TwitterSignIn.Channel.PrepareResponse(request);
}
/// <summary>
/// Checks the incoming web request to see if it carries a Twitter authentication response,
/// and provides the user's Twitter screen name and unique id if available.
/// </summary>
/// <param name="screenName">The user's Twitter screen name.</param>
/// <param name="userId">The user's Twitter unique user ID.</param>
/// <returns>
/// A value indicating whether Twitter authentication was successful;
/// otherwise <c>false</c> to indicate that no Twitter response was present.
/// </returns>
public static bool TryFinishSignInWithTwitter(out string screenName, out int userId) {
screenName = null;
userId = 0;
var response = TwitterSignIn.ProcessUserAuthorization();
if (response == null) {
return false;
}
screenName = response.ExtraData["screen_name"];
userId = int.Parse(response.ExtraData["user_id"]);
// If we were going to make this LOOK like OpenID even though it isn't,
// this seems like a reasonable, secure claimed id to allow the user to assume.
OpenId.Identifier fake_claimed_id = string.Format(CultureInfo.InvariantCulture, "http://twitter.com/{0}#{1}", screenName, userId);
return true;
}
}
}
InMemoryTokenManager.cs
//-----------------------------------------------------------------------
// <copyright file="InMemoryTokenManager.cs" company="Outercurve Foundation">
// Copyright (c) Outercurve Foundation. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.ApplicationBlock {
using System;
using System.Collections.Generic;
using System.Diagnostics;
using DotNetOpenAuth.OAuth.ChannelElements;
using DotNetOpenAuth.OAuth.Messages;
using DotNetOpenAuth.OpenId.Extensions.OAuth;
/// <summary>
/// A token manager that only retains tokens in memory.
/// Meant for SHORT TERM USE TOKENS ONLY.
/// </summary>
/// <remarks>
/// A likely application of this class is for "Sign In With Twitter",
/// where the user only signs in without providing any authorization to access
/// Twitter APIs except to authenticate, since that access token is only useful once.
/// </remarks>
public class InMemoryTokenManager : IConsumerTokenManager, IOpenIdOAuthTokenManager {
private Dictionary<string, string> tokensAndSecrets = new Dictionary<string, string>();
/// <summary>
/// Initializes a new instance of the <see cref="InMemoryTokenManager"/> class.
/// </summary>
/// <param name="consumerKey">The consumer key.</param>
/// <param name="consumerSecret">The consumer secret.</param>
public InMemoryTokenManager(string consumerKey, string consumerSecret) {
if (string.IsNullOrEmpty(consumerKey)) {
throw new ArgumentNullException("consumerKey");
}
this.ConsumerKey = consumerKey;
this.ConsumerSecret = consumerSecret;
}
/// <summary>
/// Gets the consumer key.
/// </summary>
/// <value>The consumer key.</value>
public string ConsumerKey { get; private set; }
/// <summary>
/// Gets the consumer secret.
/// </summary>
/// <value>The consumer secret.</value>
public string ConsumerSecret { get; private set; }
#region ITokenManager Members
/// <summary>
/// Gets the Token Secret given a request or access token.
/// </summary>
/// <param name="token">The request or access token.</param>
/// <returns>
/// The secret associated with the given token.
/// </returns>
/// <exception cref="ArgumentException">Thrown if the secret cannot be found for the given token.</exception>
public string GetTokenSecret(string token) {
return this.tokensAndSecrets[token];
}
/// <summary>
/// Stores a newly generated unauthorized request token, secret, and optional
/// application-specific parameters for later recall.
/// </summary>
/// <param name="request">The request message that resulted in the generation of a new unauthorized request token.</param>
/// <param name="response">The response message that includes the unauthorized request token.</param>
/// <exception cref="ArgumentException">Thrown if the consumer key is not registered, or a required parameter was not found in the parameters collection.</exception>
/// <remarks>
/// Request tokens stored by this method SHOULD NOT associate any user account with this token.
/// It usually opens up security holes in your application to do so. Instead, you associate a user
/// account with access tokens (not request tokens) in the <see cref="ExpireRequestTokenAndStoreNewAccessToken"/>
/// method.
/// </remarks>
public void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response) {
this.tokensAndSecrets[response.Token] = response.TokenSecret;
}
/// <summary>
/// Deletes a request token and its associated secret and stores a new access token and secret.
/// </summary>
/// <param name="consumerKey">The Consumer that is exchanging its request token for an access token.</param>
/// <param name="requestToken">The Consumer's request token that should be deleted/expired.</param>
/// <param name="accessToken">The new access token that is being issued to the Consumer.</param>
/// <param name="accessTokenSecret">The secret associated with the newly issued access token.</param>
/// <remarks>
/// <para>
/// Any scope of granted privileges associated with the request token from the
/// original call to <see cref="StoreNewRequestToken"/> should be carried over
/// to the new Access Token.
/// </para>
/// <para>
/// To associate a user account with the new access token,
/// <see cref="System.Web.HttpContext.User">HttpContext.Current.User</see> may be
/// useful in an ASP.NET web application within the implementation of this method.
/// Alternatively you may store the access token here without associating with a user account,
/// and wait until <see cref="WebConsumer.ProcessUserAuthorization()"/> or
/// <see cref="DesktopConsumer.ProcessUserAuthorization(string, string)"/> return the access
/// token to associate the access token with a user account at that point.
/// </para>
/// </remarks>
public void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret) {
this.tokensAndSecrets.Remove(requestToken);
this.tokensAndSecrets[accessToken] = accessTokenSecret;
}
/// <summary>
/// Classifies a token as a request token or an access token.
/// </summary>
/// <param name="token">The token to classify.</param>
/// <returns>Request or Access token, or invalid if the token is not recognized.</returns>
public TokenType GetTokenType(string token) {
throw new NotImplementedException();
}
#endregion
#region IOpenIdOAuthTokenManager Members
/// <summary>
/// Stores a new request token obtained over an OpenID request.
/// </summary>
/// <param name="consumerKey">The consumer key.</param>
/// <param name="authorization">The authorization message carrying the request token and authorized access scope.</param>
/// <remarks>
/// <para>The token secret is the empty string.</para>
/// <para>Tokens stored by this method should be short-lived to mitigate
/// possible security threats. Their lifetime should be sufficient for the
/// relying party to receive the positive authentication assertion and immediately
/// send a follow-up request for the access token.</para>
/// </remarks>
public void StoreOpenIdAuthorizedRequestToken(string consumerKey, AuthorizationApprovedResponse authorization) {
this.tokensAndSecrets[authorization.RequestToken] = string.Empty;
}
#endregion
}
}