OAuth Authentication, 2-legged, with C#/ASP.NET. Gets a 401 when I try to get the token -- code included

Highlighted
New Member

OAuth Authentication, 2-legged, with C#/ASP.NET. Gets a 401 when I try to get the token -- code included

Hi, I am trying to use OAuth authentication, coding in C#.NET, ASP.NET. I am using 2-legged authentication, so am doing a GET using a string and parameters created with the OAuthBase class written by another, and available elsewhere on the internet.

I am making the call without any sort of Credentials set for the request -- do I need to have those set, too? If so, almost seems redundant? Actually I tried it that way, using the API Key + "%" + username concatenated, plus the password, and it still got a 401.

Since I'm getting a 401, it looks like I'm not even getting into the server? Here's the source code, with the key and signature removed. There is an example of what the GET Uri looks like, too, with each parameter on a separate line (they are on one line of course in the real thing).

I would love to get this working, and will be able to give you a sample C#.NET OAuth example once it is working.

/////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.Web;
using System.Net;
using System.IO;
using System.Text;

namespace ConstantContact {
public class TestCC {
public TestCC() {
}

const string requestTokenUri = "https://api.constantcontact.com/ws/oauth_get_access_token";

// This returns the authorization token for 2-legged authorization.
// This first version just returns the unparsed response
//
public string Get2LeggedAuth(string consumerKey, string consumerSecret) {
OAuthBase oAuth = new OAuthBase();
Uri uri = new Uri(requestTokenUri);
string nonce = oAuth.GenerateNonce();
string timeStamp = oAuth.GenerateTimeStamp();
string normalizedUrl;
string reqParams;
string sig = oAuth.GenerateSignature(uri,
consumerKey, consumerSecret,
string.Empty, string.Empty,
"GET", timeStamp, nonce,
out normalizedUrl,
out reqParams);

sig = HttpUtility.UrlEncode(sig);
string tokenQuery = normalizedUrl + "?" + reqParams + "&oauth_signature=" + sig;

// This is an example of the tokenQuery, with the key and signature changed to "......" for security reasons
// I've broken the parameters into separate lines, but normally this would all be one line:
//
// https://api.constantcontact.com/ws/oauth_get_access_token
// ?oauth_consumer_key=........
// &oauth_nonce=9912056
// &oauth_signature_method=HMAC-SHA1
// &oauth_timestamp=1238708422
// &oauth_version=1.0
// &oauth_signature=........

string tokenString = DoGetQuery(tokenQuery);

// Next step after successfully getting the token string is to parse it -- returning it unparsed for now
return tokenString;
}

protected string DoGetQuery(string queryString) {
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(queryString);
req.Method = "GET";
string st = "";
try {
HttpWebResponse response = (HttpWebResponse)req.GetResponse();
// The GetResponse() is throwing an exception saying that there is a 401 response from the server, so this is as far as it gets.
StreamReader reader = new StreamReader(response.GetResponseStream());
st = reader.ReadToEnd();
} catch (Exception ex) {
return "ERROR: " + ex.Message;
}

return st;
}
}
}

Thanks -- RB
13 REPLIES 13
Moderator

RE: ASP OAuth 401 errors

Hi RB,

I'm looking at this now, it looks like you're using the OAuth class which is available as a Google Project. Any chance you can confirm the project you're using for testing purposes on our end? If we can run your example, we'll be able to see live the information hitting our servers and why it's returning a 401 (which typically means the OAuth failed or was invalid).
Dave Berard
Senior Product Manager, Constant Contact
Highlighted
New Member

RE: ASP OAuth 401 errors -- OAuthBase.cs source code

Dave,

Thanks for the reply -- I got distracted for a few days, but here is the OAuthBase code. It is mostly as I copied it, with just a change to get rid of the milliseconds part of the date, as suggested by another in a blog. Please feel free to contact me at the email address for my account. If you don't have access, give me yours and I'll contact you -- I don't want it broadcast. -- RB

----------------------------------------------- OAuthBase.cs ---------------------

using System;
using System.Security.Cryptography;
using System.Collections.Generic;
using System.Text;
using System.Web;

namespace ConstantContact {

public class OAuthBase {

///
/// Provides a predefined set of algorithms that are supported officially by the protocol
///

public enum SignatureTypes {
HMACSHA1,
PLAINTEXT,
RSASHA1
}

///
/// Provides an internal structure to sort the query parameter
///

protected class QueryParameter {
private string name = null;
private string value = null;

public QueryParameter(string name, string value) {
this.name = name;
this.value = value;
}

public string Name {
get {
return name;
}
}

public string Value {
get {
return value;
}
}
}

///
/// Comparer class used to perform the sorting of the query parameters
///

protected class QueryParameterComparer : IComparer {

#region IComparer Members

public int Compare(QueryParameter x, QueryParameter y) {
if (x.Name == y.Name) {
return string.Compare(x.Value, y.Value);
} else {
return string.Compare(x.Name, y.Name);
}
}

#endregion
}

protected const string OAuthVersion = "1.0";
protected const string OAuthParameterPrefix = "oauth_";

//
// List of know and used oauth parameters' names
//
protected const string OAuthConsumerKeyKey = "oauth_consumer_key";
protected const string OAuthCallbackKey = "oauth_callback";
protected const string OAuthVersionKey = "oauth_version";
protected const string OAuthSignatureMethodKey = "oauth_signature_method";
protected const string OAuthSignatureKey = "oauth_signature";
protected const string OAuthTimestampKey = "oauth_timestamp";
protected const string OAuthNonceKey = "oauth_nonce";
protected const string OAuthTokenKey = "oauth_token";
protected const string OAuthTokenSecretKey = "oauth_token_secret";

protected const string HMACSHA1SignatureType = "HMAC-SHA1";
protected const string PlainTextSignatureType = "PLAINTEXT";
protected const string RSASHA1SignatureType = "RSA-SHA1";

protected Random random = new Random();

protected string unreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";

///
/// Helper function to compute a hash value
///

/// The hashing algoirhtm used. If that algorithm needs some initialization, like HMAC and its derivatives, they should be initialized prior to passing it to this function
/// The data to hash
/// a Base64 string of the hash value
private string ComputeHash(HashAlgorithm hashAlgorithm, string data) {
if (hashAlgorithm == null) {
throw new ArgumentNullException("hashAlgorithm");
}

if (string.IsNullOrEmpty(data)) {
throw new ArgumentNullException("data");
}

byte[] dataBuffer = System.Text.Encoding.ASCII.GetBytes(data);
byte[] hashBytes = hashAlgorithm.ComputeHash(dataBuffer);

return Convert.ToBase64String(hashBytes);
}

///
/// Internal function to cut out all non oauth query string parameters (all parameters not begining with "oauth_")
///

/// The query string part of the Url
/// A list of QueryParameter each containing the parameter name and value
private List GetQueryParameters(string parameters) {
if (parameters.StartsWith("?")) {
parameters = parameters.Remove(0, 1);
}

List result = new List();

if (!string.IsNullOrEmpty(parameters)) {
string[] p = parameters.Split('&');
foreach (string s in p) {
if (!string.IsNullOrEmpty(s) && !s.StartsWith(OAuthParameterPrefix)) {
if (s.IndexOf('=') > -1) {
string[] temp = s.Split('=');
result.Add(new QueryParameter(temp, temp));
} else {
result.Add(new QueryParameter(s, string.Empty));
}
}
}
}

return result;
}

///
/// This is a different Url Encode implementation since the default .NET one outputs the percent encoding in lower case.
/// While this is not a problem with the percent encoding spec, it is used in upper case throughout OAuth
///

/// The value to Url encode
/// Returns a Url encoded string
protected string UrlEncode(string value) {
StringBuilder result = new StringBuilder();

foreach (char symbol in value) {
if (unreservedChars.IndexOf(symbol) != -1) {
result.Append(symbol);
} else {
result.Append('%' + String.Format("{0:X2}", (int)symbol));
}
}

return result.ToString();
}

///
/// Normalizes the request parameters according to the spec
///

/// The list of parameters already sorted
/// a string representing the normalized parameters
protected string NormalizeRequestParameters(IList parameters) {
StringBuilder sb = new StringBuilder();
QueryParameter p = null;
for (int i = 0; i < parameters.Count; i++) {
p = parameters;
sb.AppendFormat("{0}={1}", p.Name, p.Value);

if (i < parameters.Count - 1) {
sb.Append("&");
}
}

return sb.ToString();
}

///
/// Generate the signature base that is used to produce the signature
///

/// The full url that needs to be signed including its non OAuth url parameters
/// The consumer key
/// The token, if available. If not available pass null or an empty string
/// The token secret, if available. If not available pass null or an empty string
/// The http method used. Must be a valid HTTP method verb (POST,GET,PUT, etc)
/// The signature type. To use the default values use OAuthBase.SignatureTypes.
/// The signature base
public string GenerateSignatureBase(Uri url, string consumerKey, string token, string tokenSecret, string httpMethod, string timeStamp, string nonce, string signatureType, out string normalizedUrl, out string normalizedRequestParameters) {
if (token == null) {
token = string.Empty;
}

if (tokenSecret == null) {
tokenSecret = string.Empty;
}

if (string.IsNullOrEmpty(consumerKey)) {
throw new ArgumentNullException("consumerKey");
}

if (string.IsNullOrEmpty(httpMethod)) {
throw new ArgumentNullException("httpMethod");
}

if (string.IsNullOrEmpty(signatureType)) {
throw new ArgumentNullException("signatureType");
}

normalizedUrl = null;
normalizedRequestParameters = null;

List parameters = GetQueryParameters(url.Query);
parameters.Add(new QueryParameter(OAuthVersionKey, OAuthVersion));
parameters.Add(new QueryParameter(OAuthNonceKey, nonce));
parameters.Add(new QueryParameter(OAuthTimestampKey, timeStamp));
parameters.Add(new QueryParameter(OAuthSignatureMethodKey, signatureType));
parameters.Add(new QueryParameter(OAuthConsumerKeyKey, consumerKey));

if (!string.IsNullOrEmpty(token)) {
parameters.Add(new QueryParameter(OAuthTokenKey, token));
}

parameters.Sort(new QueryParameterComparer());

normalizedUrl = string.Format("{0}://{1}", url.Scheme, url.Host);
if (!((url.Scheme == "http" && url.Port == 80) || (url.Scheme == "https" && url.Port == 443))) {
normalizedUrl += ":" + url.Port;
}
normalizedUrl += url.AbsolutePath;
normalizedRequestParameters = NormalizeRequestParameters(parameters);

StringBuilder signatureBase = new StringBuilder();
signatureBase.AppendFormat("{0}&", httpMethod.ToUpper());
signatureBase.AppendFormat("{0}&", UrlEncode(normalizedUrl));
signatureBase.AppendFormat("{0}", UrlEncode(normalizedRequestParameters));

return signatureBase.ToString();
}

///
/// Generate the signature value based on the given signature base and hash algorithm
///

/// The signature based as produced by the GenerateSignatureBase method or by any other means
/// The hash algorithm used to perform the hashing. If the hashing algorithm requires initialization or a key it should be set prior to calling this method
/// A base64 string of the hash value
public string GenerateSignatureUsingHash(string signatureBase, HashAlgorithm hash) {
return ComputeHash(hash, signatureBase);
}

///
/// Generates a signature using the HMAC-SHA1 algorithm
///

/// The full url that needs to be signed including its non OAuth url parameters
/// The consumer key
/// The consumer seceret
/// The token, if available. If not available pass null or an empty string
/// The token secret, if available. If not available pass null or an empty string
/// The http method used. Must be a valid HTTP method verb (POST,GET,PUT, etc)
/// A base64 string of the hash value
public string GenerateSignature(Uri url, string consumerKey, string consumerSecret, string token, string tokenSecret, string httpMethod, string timeStamp, string nonce, out string normalizedUrl, out string normalizedRequestParameters) {
return GenerateSignature(url, consumerKey, consumerSecret, token, tokenSecret, httpMethod, timeStamp, nonce, SignatureTypes.HMACSHA1, out normalizedUrl, out normalizedRequestParameters);
}

///
/// Generates a signature using the specified signatureType
///

/// The full url that needs to be signed including its non OAuth url parameters
/// The consumer key
/// The consumer seceret
/// The token, if available. If not available pass null or an empty string
/// The token secret, if available. If not available pass null or an empty string
/// The http method used. Must be a valid HTTP method verb (POST,GET,PUT, etc)
/// The type of signature to use
/// A base64 string of the hash value
public string GenerateSignature(Uri url, string consumerKey, string consumerSecret, string token, string tokenSecret, string httpMethod, string timeStamp, string nonce, SignatureTypes signatureType, out string normalizedUrl, out string normalizedRequestParameters) {
normalizedUrl = null;
normalizedRequestParameters = null;

switch (signatureType) {
case SignatureTypes.PLAINTEXT:
return HttpUtility.UrlEncode(string.Format("{0}&{1}", consumerSecret, tokenSecret));
case SignatureTypes.HMACSHA1:
string signatureBase = GenerateSignatureBase(url, consumerKey, token, tokenSecret, httpMethod, timeStamp, nonce, HMACSHA1SignatureType, out normalizedUrl, out normalizedRequestParameters);

HMACSHA1 hmacsha1 = new HMACSHA1();
hmacsha1.Key = Encoding.ASCII.GetBytes(string.Format("{0}&{1}", UrlEncode(consumerSecret), string.IsNullOrEmpty(tokenSecret) ? "" : UrlEncode(tokenSecret)));

return GenerateSignatureUsingHash(signatureBase, hmacsha1);
case SignatureTypes.RSASHA1:
throw new NotImplementedException();
default:
throw new ArgumentException("Unknown signature type", "signatureType");
}
}

///
/// Generate the timestamp for the signature
///

///
public virtual string GenerateTimeStamp() {
// Default implementation of UNIX time of the current UTC time
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
string timeStamp = ts.TotalSeconds.ToString();
timeStamp = timeStamp.Substring(0, timeStamp.IndexOf("."));
return timeStamp;
}

///
/// Generate a nonce
///

///
public virtual string GenerateNonce() {
// Just a simple implementation of a random number between 123400 and 9999999
return random.Next(123400, 9999999).ToString();
}

}
}

//------------------------------------------- END OF OAuthBase.cs ----------------------

Thanks -- RB
Highlighted
Moderator

Hi RB, I'm actually very

Hi RB,

I'm actually very familiar with this OAuth implementation. This is one of the two OAuth open source Google projects and a great reference. I've had the same problems you're seeing using this implementation though. We're currently looking into it on our end, but I can't guarantee a quick resolution to the issues.

Unfortunately what I would have to recommend is to us digest or basic authentication until we see what the problem is with this implementation of OAuth.
Dave Berard
Senior Product Manager, Constant Contact
Highlighted
Valued Developer

re: OAuth Challenges - Consider Basic Authentication over HTTPS

Given recent challenges in our API user base and in the OAuth community in general, we have decided to extend support for Basic Authentication over HTTPS. The Basic Authentication model, while not our preferred approach, should prove simpler to adopt for many developers.



You can read more on the Basic Authentication model here.


Thanks,
Tom M
Group Product Manager – Content Editing
Constant Contact
Highlighted
New Member

Using OAuth in testing environment.

Hi All,


Recently I have started to develop "Integration with Google Health" project using OAuth. I need to know

whether it is possible or not to develop this in testing environment before registering my application. I mean

what shall I use for 'oauth_consumer_key'. 'localhost' is the correct option? If so, then what should I pass for

"consumer secret". I am developing my application using .NET. Can anybody send me any sample code which

uses testing environment and successful implementation of "OAuthGetRequestToken", "OAuthGetAccessToken"

and "OAuthGetauthorizeToken". You can also mail me at arindam.mallik@gmail.com


Thanks

Arindam


 


 


 


 


 


 


 

A. Mallik

Highlighted
Moderator

We are not currently offering

We are not currently offering support for OAuth.  You can see our supported form of Authentication here.

Dave Berard
Senior Product Manager, Constant Contact
Highlighted
New Member

google oauth -base string matches but says signature invalid

I was trying to implement google oauth to access google contacts in

visual basic.I tried-



A) Authorization header of a GET or POST request. Use "Authorization:

OAuth". All parameters listed above can go in the header, except for

scope and xoauth_displayname, which must go either in the body or in

the URL as a query parameter. The example below puts them in the body

of the request.

B) Body of a POST request. The content type must be "Content-Type:

application/x-www-form-


urlencoded".

C) As URL query parameters in a GET request.



q-1) I dont know how to add things in authorization header. I can add

headers in request like

           webReq.Headers.Add("oauth_consumer_key", ConsumerKey)

          'webReq.Headers.Add("oauth_signature_method", "HMAC-SHA1")

          'webReq.Headers.Add("oauth_signature",

HttpUtility.UrlEncode(sig))

   how to add all these values in authorization header ??



can we do like this-

          'Dim str As String = "oAuth, oauth_consumer_key=" &

ConsumerKey & ", oauth_signature_method=HMAC-SHA1" & ",

oauth_signature=" & HttpUtility.UrlEncode(sig) & ", oauth_timestamp="

& GenerateTimeStamp() & ", oauth_nonce=" & GenerateNonce() &

",oauth_version=1.0 "



          'webReq.Headers.Add("Authorization", str)



q-2) I tried putting all values in body of request- (B). I got 400 bad

request

q-3) I tried putting all values in query parameter of getrequest -

(C)-   400 bad request



q-4)  I tried hitting the url string formed by putting all parameters

in query (C)



url-

https://www.google.com/accounts/OAuthGetRequestToken?oauth_consumer_key=abc.in&oauth_version=1.0&oau...



it says  signature invalid

base string-

GET&https%3A%2F%2Fwww.google.com%2Faccounts

%2FOAuthGetRequestToken&oauth_consumer_key%3Dabc.in%26oauth_nonce

%3D905231%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp

%3D1262061486%26oauth_version%3D1.0%26scope%3Dhttp%253A%252F

%252Fwww.google.com%252Fbase%252Ffeeds%252F





base string is in correct format. I have matched it against

googleOAUTH playground

I create signature using-

1) httpmethod -

2) URl - https://www.google.com/accounts/OAuthGetRequestToken -

urlencode

3) oauth parameters- url encoded

generateSignatureFun-

          parameters.Add(New QueryParameter(OAuthVersionKey,

OAuthVersion))

          parameters.Add(New QueryParameter(OAuthNonceKey, nonce))

          parameters.Add(New QueryParameter(OAuthTimestampKey,

timeStamp))

          parameters.Add(New QueryParameter(OAuthSignatureMethodKey,

signatureType))

          parameters.Add(New QueryParameter(OAuthConsumerKeyKey,

consumerKey))

          parameters.Add(New QueryParameter(scopeKey, scope))



q-5) the oAuth.vb file i took from google code doesnt has last line I

mean the file i took from google code doesnt include scope while

generating signature, but oauthplayground does so I also included

that.  M I doing right?



generated signature was in the format -

sig -----  xS6iS/opo8xmU5EFdtlP3pAEiRA=

I again urlencoded it- HttpUtility.UrlEncode(sig), resutled signature

was ----

xS6iS%2fopo8xmU5EFdtlP3pAEiRA%3d



dont know where i am getting wrong? every time i try different things

while running with VB code, I get only one error- 400 bad request,

nothing to know what thing i am doing wrong



q-6) is thr a way to just print whatever request I am sending before

sending to check that it is in correct format or not

 

 

Nishant Gauttam

SchoolSearch.IN

http:www.schoolsearch.in
Highlighted
Moderator

Please see this

Please see this response:


 


http://developer.constantcontact.com/node/850#comment-1658

Dave Berard
Senior Product Manager, Constant Contact
Highlighted
Participant

Hello All OAUTH 401

Hello All OAUTH 401 Error,


Did any body able to bild the application to grant access to the constant contact accounts using oauth mechanism using c# and .NET ,


please let me know the source code.  i am getting 401 unauthorized error.


 

Developer Portal

View API documentation, code samples, get your API key.