Ok, so its done! This works.
First, grab the latest DotNetOpenAuth library, which is here:
http://www.dotnetopenauth.netNext, make a new desktop project, like a console application. Note, that this explanation is really just to gain understanding, and that it involves interrupting the process to hit the authentication web page, authenticating and injecting the validation token. You could make this more seamless by using a web application with an embedded browser or something.
Anyways, moving along.
Create a second project, a class library called yahoo.fantasysports
Add reference to the DotNetOpenAuth and the Log4net assemblies.
Immediately create a class, called "InMemoryTokenManager"
CODEBOX
using System;
using System.Collections.Generic;
using System.Diagnostics;
using DotNetOpenAuth.OAuth.ChannelElements;
using DotNetOpenAuth.OAuth.Messages;
using DotNetOpenAuth.OpenId.Extensions.OAuth;
namespace yahoo.fantastysports {
/// <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>
internal 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
}
}
Next, create a second class called "YahooFantasySportsService"
CODEBOX
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DotNetOpenAuth.OAuth;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth.ChannelElements;
namespace yahoo.fantastysports {
public static class YahooFantasySportsService {
public static readonly ServiceProviderDescription Description = new ServiceProviderDescription {
RequestTokenEndpoint = new MessageReceivingEndpoint("https://api.login.yahoo.com/oauth/v2/get_request_token", HttpDeliveryMethods.PostRequest),
UserAuthorizationEndpoint = new MessageReceivingEndpoint("https://api.login.yahoo.com/oauth/v2/request_auth", HttpDeliveryMethods.GetRequest),
AccessTokenEndpoint = new MessageReceivingEndpoint("https://api.login.yahoo.com/oauth/v2/get_token", HttpDeliveryMethods.GetRequest),
TamperProtectionElements = new ITamperProtectionChannelBindingElement[] {
new HmacSha1SigningBindingElement()
}
};
}
}
Next Create a class called "OAuthWrapper"
CODEBOX
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using DotNetOpenAuth.OAuth.ChannelElements;
namespace yahoo.fantastysports {
public class OAuthWrapper {
public DotNetOpenAuth.OAuth.DesktopConsumer consumer { get; set; }
private string RequestToken = "";
public string ConsumerKey { get; set; }
public string ConsumerSecret { get; set; }
public OAuthWrapper(string consumer_key, string consumer_secret) {
this.ConsumerKey = consumer_key;
this.ConsumerSecret = consumer_secret;
consumer = new DotNetOpenAuth.OAuth.DesktopConsumer(
YahooFantasySportsService.Description,
new InMemoryTokenManager(ConsumerKey, ConsumerSecret));
return;
}
public string BeginAuth() {
var requestArgs = new Dictionary<string, string>();
return this.consumer.RequestUserAuthorization(requestArgs, null, out this.RequestToken).AbsoluteUri;
}
public string CompleteAuth(string verifier) {
var response = this.consumer.ProcessUserAuthorization(this.RequestToken, verifier);
return response.AccessToken;
}
public IConsumerTokenManager TokenManager {
get {
return (IConsumerTokenManager)consumer.TokenManager;
}
}
}
}
So this is what you need to authenticate to Yahoo Fantasy Sports, and what will be needed to sign requests to make calls to the services. Of course it would make sense to create methods for such calls, and mange their usage. Now go back to that console application we made.
Add an Application Configuration File. And add this to it:
CODEBOX
<appSettings>
<add key="key" value="your_key"/>
<add key="secret" value="your secret"/>
</appSettings>
Now, all of that boils down to this... in your Program.cs file, make this call.
CODEBOX
yahoo.fantastysports.OAuthWrapper wrapper = new yahoo.fantastysports.OAuthWrapper(ConfigurationManager.AppSettings["key"], ConfigurationManager.AppSettings["secret"]);
Now you can start the authentication process.
CODEBOX
string url = wrapper.BeginAuth();
URL is the address you will need to hit to authenticate and allow access to your data. So make sure you break after you call this, and paste the URL into a browser. Do the authentication, and you will get a short validation token. Copy that.
Then you can call in the immediate window, or just set a string variable to the value of that token, and call:
CODEBOX
wrapper.CompleteAuth(validator_token);
Bam! your in!
Play around with it, and I'm sure it will start to make sense. I'm still making sense of it myself. =)
Back to the idea of having an embedded browser, it would be really easy to extract that validationtoken programatically from an embedded browser, and streamline the process a lot more. And if you're not using a desktop application, I believe you can specify a url for the token to be posted back to you, further simplifying this process.
Also, that class we made, InMemoryTokenManager, that would likely be changed to store tokens in a database, so that you dont need to authenticate every time, you could just refresh the tokens when they expire.
Hope this is helpful, any questions or comments, please by all means.
A big thanks to
http://www.theleagueofpaul.com/blog/2010/0...oauth-together/ for helping me piece this libraries usage together.