SendEvernote: Using OpenAuth / OpenID with Evernote and C#

This is the first post in a series on how I learnt to use the Evernote SDK while working on my pet project, www.sendevernote.com.

I have an confession to make: I am a packrat! But wait, banish those thoughts of a small apartment in New York packed floor to ceiling in useless nick nacks and pieces of paper – I am a packrat of a virtual nature. I have been storing away all kinds of tidbits in to Evernote for over 5 years. Apart from the fact that I have a lot of notes I have yet to file (I’ll be posting about my solution for that problem in a future blog post), I can’t seem to find the time to review my notes. So I decided to create a website that automagically sends a random note from a notebook to you every day.

TL;DR; Check out https://github.com/shaunmccarthy/AsyncOAuth.Evernote.Simple where I have posted a simplified library for interacting with Evernote and OAuth.

So, my first problem – Authenticating against Evernote with OpenAuth.

Evernote & OpenAuth

So a quick google search for Evernote, C# and OpenAuth leads to a whole bunch of nothing, along with a few attempts at “Pin” based authentication. While that makes sense for a Console or Windows Form application, you really want to make the experience as seemless as possible for wesite users. If you have a look at http://dev.evernote.com/doc/articles/authentication.php it gives you a pretty good picture as to how the whole process works. Here’s a great screenshot from that:

Evernote and OAuth

Evernote and OAuth

The important “take homes”

  • We need to request temporary credentials from Evernote
  • We then redirect the user to Evernote to Login and Authorize access for our application to their notebooks
  • Evernote then redirects the user back to us, and we receive the invalable

.Net, OpenAuth & Evernote

There are a lot of OpenAuth libraries out there for .net. I went with AsyncOAuth (https://github.com/neuecc/AsyncOAuth) as it’s really simple to use, and Evernote only uses OAuth 1.0. To get the request token, you need to initialize a OAuthAuthorizer with your Evernote API credentials, which you can request from http://dev.evernote.com/doc/. You then call the GetRequestToken method, which makes a WebRequest out to Evernote and requests a RequestToken, the first step above. You need to provide a URL that Evernote will redirect a user back to after authorizing your application.

// The second item is your secret (this one isn't valid)
var authorizer = new OAuthAuthorizer("yourapiname", "badb123b192192");
var token = authorizer.GetRequestToken("http://evernote.repository.url.com/oauth", new Dictionary<string, string> { { "oauth_callback", "http://somepageonyoursite.com/ObtainTokenCredentials" } }, null).Result.Token;

After you retrieve the RequestToken, you need to redirect the user to the Evernote website to authorize that token. You can get the URL via the BuildAuthorizeUrl method. Since you are about to redirect the user to another site, you need to store the RequestToken somewhere where it can be retrieve later. For this example, I will use the session

Session["RequestToken"] = token;
// BE VERY CAREFUL HERE - OAuth.aciton is _case sensitive_
var callForwardUrl = base.BuildAuthorizeUrl("http://evernote.repository.url.com/OAuth.action", token);
// Your code to redirect the user - here I am using MVC
return Redirect(callForwardUrl);

Once the user authorizes your application, they will be redirected to your callback url with a parameter named “oauth_verifier” – if it’s not set, then the user did not authorize your application.

    if (oauth_verifier != null)
    {
        var result = OAuthAuthorizer.GetAccessToken(
            ConfigurationManager.AppSettings["Evernote.Url"] + "/oauth",
            SessionHelper.RequestToken as RequestToken,
            oauth_verifier, null, null).Result;

You then need to parse the Token and ExtraData fields for the information that Evernote has returned. Evernote doesn’t provide a secret with the AuthToken, so you can take just the key part. Items in ExtraData are stored UrlEncoded, so you need to decode them. The date in stored in javascript format, so you need to do some magic to get it in to a .net date. Here’s an example of parsing the data returned from Evernote (EvernoteCredentials is just a poco for storing the results):

EvernoteCredentials credentials = new EvernoteCredentials();
credentials.AuthToken = result.Token.Key;

// Parse the extra data
credentials.Shard = Uri.UnescapeDataString(result.ExtraData["edam_shard"].FirstOrDefault());
credentials.UserId = Uri.UnescapeDataString(result.ExtraData["edam_userId"].FirstOrDefault());
var expires = Uri.UnescapeDataString(result.ExtraData["edam_expires"].FirstOrDefault());
var expiresDateTime = new DateTime(1970, 1, 1).AddTicks(long.Parse(expires) * 10000);
credentials.Expires = DateTime.SpecifyKind(expiresDateTime, DateTimeKind.Utc);
credentials.NotebookUrl = Uri.UnescapeDataString(result.ExtraData["edam_noteStoreUrl"].FirstOrDefault());
credentials.WebApiUrlPrefix = Uri.UnescapeDataString(result.ExtraData["edam_webApiUrlPrefix"].FirstOrDefault());
A word of warning: one thing you will quickly run in to with OAuth is the following error:
ComputeHash is null, must initialize before call OAuthUtility.HashFunction = /* your computeHash code */ at once.

After some google-fu I went with the following implementation of a HMACSHA1 hash:

OAuthUtility.ComputeHash = (key, buffer) => { using (var hmac = new HMACSHA1(key)) { return hmac.ComputeHash(buffer); } };

There has to be a simpler way

I agree :) I decided to wrap most of complexities of the above code in a library that you are free to grab from github @ https://github.com/shaunmccarthy/AsyncOAuth.Evernote.Simple. I plan on releasing it to nuget as well, once I add a little polish. Here’s how you call it:

// Configure the Authorizer with the URL of the Evernote service,
// your key, and your secret.
var EvernoteAuthorizer = new EvernoteAuthorizer(
    "https://sandbox.evernote.com",
    "slyrp-1234",
    "7acafe123456badb123");

// First of all, get a request token from Evernote - this causes a
// webrequest from your server to Evernote.
// The callBackUrl is the URL you want the user to return to once
// they validate the app
var requestToken = EvernoteAuthorizer.GetRequestToken(callBackUrl);

// Persist this token, as we are going to redirect the user to
// Evernote to Authorize this app
Session["RequestToken"] = requestToken;

// Generate the Evernote URL that we will redirect the user to in
// order to
var callForwardUrl = EvernoteAuthorizer.BuildAuthorizeUrl(requestToken);

// Redirect the user (e.g. MVC)
return Redirect(callForwardUrl);

// ... Once the user authroizes the app, they get redirected to callBackUrl

// where we parse the request parameter oauth_validator and finally get
// our credentials
// null = they didn't authorize us
var credentials = EvernoteAuthorizer.ParseAccessToken(
    Request.QueryString["oauth_verifier"],
    Session["RequestToken"] as RequestToken);

// Example of how to use the credential with Evernote SDK
var noteStoreUrl = credentials.NotebookUrl;
var noteStoreTransport = new THttpClient(new Uri(noteStoreUrl));
var noteStoreProtocol = new TBinaryProtocol(noteStoreTransport);
var noteStore = new NoteStore.Client(noteStoreProtocol);
List notebooks = client.listNotebooks(credentials.AuthToken);
Photo Credit: VinothChandar via Compfight cc

3 Comments

  1. Andrew March 1, 2014 Reply

    Awesome tutorial. Probably the easiest one I’ve read. Was up and running within minutes! Thanks a ton.

    • Andrew March 1, 2014 Reply

      One thing I have noticed though, “EvernoteCredentials.NotebookUrl;” doesn’t work because NotebookUrl isn’t static. You would either have to go through the SessionHelper, or create a new EvernoteCredentials object.

      • Author
        Shaun McCarthy March 1, 2014 Reply

        Nice catch – it looks like I have a typo in my final example: EvernoteCredentials.AuthToken and EvernoteCredentials.NotebookUrl should be credentials.AuthToken and credentials.NotebookUrl respectively. I’ll make the change shortly.

Leave a reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>