function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
wardward 

Confused why calls to /services/data/ works but everything else returns 401 error

I implemented Oauth 2.0 means using WebClient in C# to send GET and POST messages directly to Salesforce.

 

For those unfamilar with Oauth, maybe seeing my code samples will help them if I outline the steps in c# type syntax.

Maybe someone who know's a lot about configuring Salesforce will be able to tell me if they see something I'm doing wrong, at least I hope...

 

String oauth_scope = "refresh_token api full chatter_api id";

 

String url = String.Format( "{0}?client_id={1}&redirect_uri={2}&response_type=code&scope={3}", "https://login.salesforce.com/services/oauth2/authorize?", _consumerKey_web_app, nextanalytics.MyString.UrlEncode( SALESFORCE_REDIRECTS_TO_THIS_URL_AFTER_URL_AUTH ), nextanalytics.MyString.UrlEncode( oauth_scope ) ); HttpWebRequest myRequest = (HttpWebRequest) WebRequest.Create( url );;

 

 

 

This comes back fine. I get back a URL to my embedded browser with a code=VALUE on it.

I POST a response back to salesforce (again using WebClient.

 

WebClient client = new WebClient();
#if ! MONO
client.Proxy.Credentials = CredentialCache.DefaultCredentials;
#endif
try
{
NameValueCollection values = new NameValueCollection();
values.Add( "client_id", _consumerKey_web_app );
values.Add( "client_secret", _consumerSecret_web_app_offline );
values.Add( "grant_type", "authorization_code" );
values.Add( "redirect_uri", SALESFORCE_REDIRECTS_TO_THIS_URL_AFTER_URL_AUTH );
values.Add( "code", authorization_code );
Byte[] responseBytes = client.UploadValues( URL_TOKEN_ENDPOINT_SECOND, values );
String errMsg = parse_result_from_token_endpoint( Encoding.UTF8.GetString( responseBytes ) );

 

The parse_result function is just something I wrote that does the following:

HandlerForJSON parsed = new HandlerForJSON();
System.Collections.Hashtable hashtable = (System.Collections.Hashtable) parsed.JsonDecode( response );
short_lived_access_token = (String) parsed.hashtable["access_token"];
refresh_token = (String) parsed.hashtable["refresh_token"];
instance_url = (String) parsed.hashtable["instance_url"]; 
issued_at_num_secs_since_unix_epoc = (String) parsed.hashtable["issued_at"];

 

All my values look correct.

The refresh_token seems correct, and so does the short_lived_access_token. The instance_url is used (by me) to build other method calls. For example, I can submit this:

 

String new_url = instance_url + "/services/data/" + "?access_token=xyz..."

 

I get back a nice json string that is an array of versions, as documented.

I pick the the last one in the array, thinking it's going to be the most recent version.

With this dynamic "version" information, I'm supposed to be able construct a web url that looks like this:

 

"https://na11.salesforce.com/services/data/v28.0/"

 

From what I understand, if I submit that, with an un-expired access_token, it should give me back my "Resources" which would hypothetically look like:

{
"sobjects" : "/services/data/v28.0/sobjects",
"search" : "/services/data/v28.0/search",
"query" : "/services/data/v28.0/query",
"recent" : "/services/data/v28.0/recent"
}

 

But my problem is that I always get back an error 401 Unauthorized unless it the first call to get the version(s) 

 

With OAuth, 401 is often the error you get when your access_token expires and it's time to use the refresh_token. But my problem is that the access_token had just been issued, and used a split second earlier, so expiry is not likely the problem.  

 

I suspect it's something more fundamental, something about the configuration. Basically, I don't understand why I can query the versions but it fails on the immediate next call to get the Resources.

 

At first, I thought to look carefully at the scope(s) I was using.

String oauth_scope = "refresh_token api full chatter_api id"; 

I've tried different combinations and nothing seems to help.

 

I've double checked, and those scopes do match what I chose for the "connected App" that I created on the server. 

 

I also wondered if there is some security setting I'm supposed to do in the User Setup, App Setup, or Administration Setup. When I look, I can't see anything...

 

I'm stuck on this. I'm sure you know the feeling. Anyone out there knows how to get a connectedApp that uses Oauth 2.0 to be able to issue GET calls to various methods, more than just getting which instance_url to use?

 

 

 

Best Answer chosen by Admin (Salesforce Developers) 
SuperfellSuperfell

You need to pass the access token in the Authorization http header, not as a query parameter, e.g. Authorization: Bearer ${my Access Token}

All Answers

SuperfellSuperfell

You need to pass the access token in the Authorization http header, not as a query parameter, e.g. Authorization: Bearer ${my Access Token}

This was selected as the best answer
wardward

Yes, Simon, thanks, that was it.  It amplifies that even though Oauths can be similar between vendors, they're not identical. Google, Twitter, Facebook and I think LinkedIn all support the access_token=% on the arg list so it hadn't even occurred to me to put it in the header. The header is a superior approach.  I can't tell you what a relief it is ...  it is no fun stabbing at things in the dark when one has all sorts of other things to do.

 

For those that like code snippets, here was the fix, i.e. how to put the access_token into a web query (for use the REST API):

 

      HttpWebRequest myRequest = (HttpWebRequest) WebRequest.Create( url );
#if !MONO
      // inherit default proxy settings
      IWebProxy proxy = WebRequest.GetSystemWebProxy();
      proxy.Credentials = CredentialCache.DefaultCredentials;
      myRequest.Proxy = proxy;
#endif
      myRequest.Headers["Authorization"] = "Bearer " + short_lived_access_token;

      ServicePointManager.Expect100Continue = false;
      Int32 timeout = varParam.GetGATimeout();
      if( timeout > 10000 )
        myRequest.Timeout = timeout;

      myRequest.Method = "GET";
      myRequest.ContentType = "application/x-www-form-urlencoded";
      myRequest.ContentLength = 0;
      try
      {
        HttpWebResponse myResponse = (HttpWebResponse) myRequest.GetResponse();
        Stream responseBody = myResponse.GetResponseStream();
        StreamReader readStream = new StreamReader( responseBody, Encoding.GetEncoding( "utf-8" ) );
        response = readStream.ReadToEnd();
        LogTransaction( url, response );
      }
      catch( Exception e )
      {
        string err = e.Message;
        LogTransaction( url, err );
        err_msg = "Error: " + err;
      }

 

 

wardward
Oh, and I realized after your reply, the reason the first call worked was because that one call doesn't require an access_token.