8

I'm trying to call Paypal api from my code. I set up the sandbox account and it works when I use curl but my code isn't working the same way, returning 401 Unauthorized instead.

Here's the curl command as documented by Paypal

curl https://api.sandbox.paypal.com/v1/oauth2/token -H "Accept: application/json" -H "Accept-Language: en_US" -u "A****:E****" -d "grant_type=client_credentials" 

UPDATE: Apparently the .Credentials doesn't do the trick, instead setting Authorization header manually works (see code)

Here's the code (trimmed to its essence):

 HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create("https://api.sandbox.paypal.com/v1/oauth2/token"); request.Method = "POST"; request.Accept = "application/json"; request.Headers.Add("Accept-Language:en_US") // this doesn't work: **request.Credentials = new NetworkCredential("A****", "E****");** // DO THIS INSTEAD **string authInfo = Convert.ToBase64String(System.Text.Encoding.Default.GetBytes("A****:E****"));** **request.Headers["Authorization"] = "Basic " + authInfo;** using (StreamWriter swt = new StreamWriter(request.GetRequestStream())) { swt.Write("grant_type=client_credentials"); } request.BeginGetResponse((r) => { try { HttpWebResponse response = request.EndGetResponse(r) as HttpWebResponse; // Exception here .... } catch (Exception x) { .... } // log the exception - 401 Unauthorized }, null); 

This is the request from code captured by Fiddler (raw), there are no authorization parameters for some reason:

POST https://api.sandbox.paypal.com/v1/oauth2/token HTTP/1.1 Accept: application/json Accept-Language: en_US Host: api.sandbox.paypal.com Content-Length: 29 Expect: 100-continue Connection: Keep-Alive grant_type=client_credentials 
6
  • There's a missing space in the accept header but I can't see anything else obvious. Have you tried capturing the two requests to see what's different, e.g. using wireshark or a proxy such as Fiddler? Commented Jun 2, 2013 at 22:03
  • @Rup I tried with Fiddler, still having trouble capturing the curl request but the code request doesn't contain Auth headers (see update) Commented Jun 2, 2013 at 22:09
  • 1
    Yes some HTTP libraries e.g. Apache's won't send the credentials unless asked for them by the remote server, but I didn't know .NET's did too. Or at least it ought to then reply to the 401 with them. There may be a way to force it too on the request object? Commented Jun 2, 2013 at 22:10
  • 1
    There's an unpleasant work-around in this old answer: construct your own basic authentication header. Or I was thinking of HttpWebRequest.PreAuthenticate. Commented Jun 2, 2013 at 22:15
  • @Rup yeah I found that and worked around the problem. Thank you for looking into it Commented Jun 2, 2013 at 22:53

4 Answers 4

6

Hoping the following code help to anyone who is still looking for a good piece of cake to get connected to PayPal.

As many people, I've been investing a lot of time trying to get my PayPal token access without success, until I found the following:

public class PayPalClient { public async Task RequestPayPalToken() { // Discussion about SSL secure channel // http://stackoverflow.com/questions/32994464/could-not-create-ssl-tls-secure-channel-despite-setting-servercertificatevalida ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; try { // ClientId of your Paypal app API string APIClientId = "**_[your_API_Client_Id]_**"; // secret key of you Paypal app API string APISecret = "**_[your_API_secret]_**"; using (var client = new System.Net.Http.HttpClient()) { var byteArray = Encoding.UTF8.GetBytes(APIClientId + ":" + APISecret); client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray)); var url = new Uri("https://api.sandbox.paypal.com/v1/oauth2/token", UriKind.Absolute); client.DefaultRequestHeaders.IfModifiedSince = DateTime.UtcNow; var requestParams = new List<KeyValuePair<string, string>> { new KeyValuePair<string, string>("grant_type", "client_credentials") }; var content = new FormUrlEncodedContent(requestParams); var webresponse = await client.PostAsync(url, content); var jsonString = await webresponse.Content.ReadAsStringAsync(); // response will deserialized using Jsonconver var payPalTokenModel = JsonConvert.DeserializeObject<PayPalTokenModel>(jsonString); } } catch (System.Exception ex) { //TODO: Log connection error } } } public class PayPalTokenModel { public string scope { get; set; } public string nonce { get; set; } public string access_token { get; set; } public string token_type { get; set; } public string app_id { get; set; } public int expires_in { get; set; } } 

This code works pretty well for me, hoping for you too. The credits belong to Patel Harshal who posted his solution here.

Sign up to request clarification or add additional context in comments.

1 Comment

THANKS! You saved half of my day.
3

This Works using HttpClient... 'RequestT' is a generic for the PayPal request arguments, however it is not used. The 'ResponseT' is used and it is the response from PayPal according to their documentation.

'PayPalConfig' class reads the clientid and secret from the web.config file using ConfigurationManager. The thing to remember is to set the Authorization header to "Basic" NOT "Bearer" and if and to properly construct the 'StringContent' object with right media type (x-www-form-urlencoded).

 //gets PayPal accessToken public async Task<ResponseT> InvokePostAsync<RequestT, ResponseT>(RequestT request, string actionUrl) { ResponseT result; // 'HTTP Basic Auth Post' <http://stackoverflow.com/questions/21066622/how-to-send-a-http-basic-auth-post> string clientId = PayPalConfig.clientId; string secret = PayPalConfig.clientSecret; string oAuthCredentials = Convert.ToBase64String(Encoding.Default.GetBytes(clientId + ":" + secret)); //base uri to PayPAl 'live' or 'stage' based on 'productionMode' string uriString = PayPalConfig.endpoint(PayPalConfig.productionMode) + actionUrl; HttpClient client = new HttpClient(); //construct request message var h_request = new HttpRequestMessage(HttpMethod.Post, uriString); h_request.Headers.Authorization = new AuthenticationHeaderValue("Basic", oAuthCredentials); h_request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); h_request.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue("en_US")); h_request.Content = new StringContent("grant_type=client_credentials", UTF8Encoding.UTF8, "application/x-www-form-urlencoded"); try { HttpResponseMessage response = await client.SendAsync(h_request); //if call failed ErrorResponse created...simple class with response properties if (!response.IsSuccessStatusCode) { var error = await response.Content.ReadAsStringAsync(); ErrorResponse errResp = JsonConvert.DeserializeObject<ErrorResponse>(error); throw new PayPalException { error_name = errResp.name, details = errResp.details, message = errResp.message }; } var success = await response.Content.ReadAsStringAsync(); result = JsonConvert.DeserializeObject<ResponseT>(success); } catch (Exception) { throw new HttpRequestException("Request to PayPal Service failed."); } return result; } 

IMPORTANT: use Task.WhenAll() to ensure you have a result.

 // gets access token with HttpClient call..and ensures there is a Result before continuing // so you don't try to pass an empty or failed token. public async Task<TokenResponse> AuthorizeAsync(TokenRequest req) { TokenResponse response; try { var task = new PayPalHttpClient().InvokePostAsync<TokenRequest, TokenResponse>(req, req.actionUrl); await Task.WhenAll(task); response = task.Result; } catch (PayPalException ex) { response = new TokenResponse { access_token = "error", Error = ex }; } return response; } 

1 Comment

I get AuthenticationException-WebException about SSL / TLS.
3

Paypal has deprecated TLS 1.1, and only accepts 1.2 now. Unfortunately .NET (prior to version 4.7) uses 1.1 by default, unless you configure it otherwise.

You can turn on TLS 1.2 with this line. I recomend placing it Application_Start or global.asax.

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; 

Comments

0

I too suffered from a lack of example code and various issues with response errors and codes.

I am a big fan of RestClient as it helps a lot with integrations and the growing number of RESTful API calls.

I hope this small snippet of code using RestSharp helps someone: -

 if (ServicePointManager.SecurityProtocol != SecurityProtocolType.Tls12) ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; // forced to modern day SSL protocols var client = new RestClient(payPalUrl) { Encoding = Encoding.UTF8 }; var authRequest = new RestRequest("oauth2/token", Method.POST) {RequestFormat = DataFormat.Json}; client.Authenticator = new HttpBasicAuthenticator(clientId, secret); authRequest.AddParameter("grant_type","client_credentials"); var authResponse = client.Execute(authRequest); // You can now deserialise the response to get the token as per the answer from @ryuzaki var payPalTokenModel = JsonConvert.DeserializeObject<PayPalTokenModel>(authResponse.Content); 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.