401 Unauthorized when calling Stormpath REST API with WebRequest object? - c#

I am using Stormpath for my authentication service. I call the RestAPI of Stormpath by using HttpWebRequest.
I am also using HttpWebRequest to call the RestAPI but it does not work.
private void BtnGetResetApiClick(object sender, EventArgs e)
{
var username = "aaaa";
var password = "bbbb";
ServicePointManager.ServerCertificateValidationCallback = Callback;
var request = WebRequest.Create("https://api.stormpath.com/v1/tenants/current") as HttpWebRequest;
request.UserAgent = ".NET SDK";
request.Method = "GET";
request.Accept = "*/*";
var data = string.Format("{0}:{1}", username, HttpUtility.HtmlEncode(password));
var token = Convert.ToBase64String(Encoding.UTF8.GetBytes(data));
string authHeader = string.Format("Basic {0}", token);
request.Headers.Add("Authorization", authHeader);
request.ServerCertificateValidationCallback = Callback;
using (var response = request.GetResponse())
{
var stream = response.GetResponseStream();
if (stream != null)
{
var streamReader = new StreamReader(stream);
var str = streamReader.ReadToEnd();
streamReader.Close();
stream.Close();
}
}
}
private bool Callback(object obj, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
{
return true;
}
When calling:
var response = request.GetResponse()
I got an exception:
An unhandled exception of type 'System.Net.WebException' occurred in System.dll The remote server returned an error: (401) Unauthorized.
Can you help me to see if my code has something wrong?

Update - use the SDK, it's much easier!
If you're calling the Stormpath API from C# frequently, don't bother with writing requests by hand. Use the Stormpath .NET SDK instead. I'm the author. :)
Install it using install-package Stormpath.SDK from the Package Manager Console. Then, create an IClient object:
// In a production environment, you'll want to load these from
// environment variables or a secure file, instead of hardcoding!
var apiKey = ClientApiKeys.Builder()
.SetId("Your_Stormpath_API_key_ID")
.SetSecret("Your_Stormpath_API_key_secret")
.Build();
var client = Clients.Builder()
.SetApiKey(apiKey)
.Build();
Getting the tenant info is now just a simple call:
var tenant = await client.GetCurrentTenantAsync();
Console.WriteLine($"Current tenant is: {tenant.Name}");
If you really want to make raw requests, you can still do that! I'll explain below.
Constructing the Authorization header
A 401 Unauthorized response means that the API was not able to find a valid Authorization header in your request. To authenticate correctly, you need two things:
An authorization payload in the format apiKeyID:apiKeySecret
An Authorization header with value: Basic base64(payload)
Here's how to construct the complete header:
// API_KEY_ID and API_KEY_SECRET populated elsewhere
var authPayload = string.Format("{0}:{1}", API_KEY_ID, API_KEY_SECRET);
var authPayloadEncoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(authPayload));
request.Headers.Add(HttpRequestHeader.Authorization, "Basic " + authPayloadEncoded);
You don't need the ServerCertificateValidationCallback = Callback stuff. With the above header, the request will be seen by the API as a valid request (assuming your API Key ID and Secret are correct, of course).
Redirection handling
One thing to watch out for (this tripped me up at first!) is that WebRequest will follow HTTP 302 redirects automatically, but will not apply the existing headers to the new request.
The solution is to disable redirect following:
request.AllowAutoRedirect = false;
This means you'll have to handle 302 responses yourself, but it's the only way to correctly apply the Authorization header to each request.
Working example
I created a simple working example in this gist. Since I'll be creating requests multiple times, I wrote a helper function:
private static HttpWebRequest BuildRequest(string method, string uri)
{
var request = WebRequest.Create(uri) as HttpWebRequest;
request.UserAgent = "dotnet/csharp web-request";
request.Method = method;
request.ContentType = "application/json";
// Important, otherwise the WebRequest will try to auto-follow
// 302 redirects without applying the authorization header to the
// subsequent requests.
request.AllowAutoRedirect = false;
// Construct HTTP Basic authorization header
var authPayload = string.Format("{0}:{1}", API_KEY_ID, API_KEY_SECRET);
var authPayloadEncoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(authPayload));
request.Headers.Add(HttpRequestHeader.Authorization, "Basic " + authPayloadEncoded);
return request;
}
And a simple console app to demonstrate getting the current tenant URL and name:
// Get these from the Stormpath admin console
private static string API_KEY_ID = "Your_Stormpath_API_key_ID";
private static string API_KEY_SECRET = "Your_Stormpath_API_key_secret";
static void Main(string[] args)
{
// First, we need to get the current tenant's actual URL
string tenantUrl = null;
var getCurrentTenantRequest = BuildRequest("GET", "https://api.stormpath.com/v1/tenants/current");
try
{
using (var response = getCurrentTenantRequest.GetResponse())
{
tenantUrl = response.Headers["Location"];
}
}
catch (WebException wex)
{
Console.WriteLine("Request failed. {0}", wex.Message);
throw;
}
// Now that we have the real tenant URL, get the tenant info
string tenantData = null;
var getTenantInfoRequest = BuildRequest("GET", tenantUrl);
try
{
using (var response = getTenantInfoRequest.GetResponse())
using (var responseStream = response.GetResponseStream())
using (var reader = new StreamReader(responseStream))
{
tenantData = reader.ReadToEnd();
}
}
catch (WebException wex)
{
Console.WriteLine("Request failed. {0}", wex.Message);
throw;
}
// Use JSON.NET to parse the data and get the tenant name
var parsedData = JsonConvert.DeserializeObject<Dictionary<string, object>>(tenantData);
Console.WriteLine("Current tenant is: {0}", parsedData["name"]);
// Wait for user input
Console.ReadKey(false);
}
The code is pretty verbose because we're making raw requests to the API. Again, if you're making requests frequently, use the SDK instead!

Related

C# .net core POST Request

I am trying to setup a login via Oauth2 using EvE online's SSO system in .net core and am finding it impossible on the Post request stage. This is how I would have done it in the past with standard .net. Is anyone able to help me convert it over?
byte[] encodedKey = Encoding.UTF8.GetBytes(clientId + ":" + clientSecret);
//
HttpWebRequest request = HttpRequestHelper.CreateRequest(new Uri("https://login.eveonline.com/oauth/token"));
request.Host = Host;
request.Headers.Add("Authorization", "Basic " + encodedKey);
request.Method = "POST";
HttpRequestHelper.AddPostData(request, "grant_type=authorization_code&code=" + code);
string response = await requestAsync(request).ConfigureAwait(false);
var result = JsonConvert.DeserializeObject<AuthResponse>(response);
return result;
p.s. this is the post request format I am looking for
POST https://login.eveonline.com/oauth/token HTTP/1.1
Authorization: Basic bG9...ZXQ=
Content-Type: application/x-www-form-urlencoded
Host: login.eveonline.com
grant_type=authorization_code&code=gEyuYF_rf...ofM0
I wrote a module for EVEOnline SSO, in .net core 1.1 i'm using HttpClient to send the request
private readonly HttpClient _client;
public async Task<SsoResponse> AuthenticateAsync(string code)
{
if (string.IsNullOrEmpty(code))
throw new ArgumentNullException("Authentication code is null or empty");
// Build the link to the SSO we will be using.
var builder = new UriBuilder(_settings.BaseUrl)
{
Path = _settings.TokenPath,
Query = $"grant_type=authorization_code&code={code}"
};
var request = new HttpRequestMessage()
{
RequestUri = builder.Uri,
Method = HttpMethod.Post
};
// Set the necessary headers
request.Headers.Add("Authorization", $"{TokenType.Basic} {_authorizationString}");
request.Headers.Add("Host", builder.Host);
request.Headers.Add("User-Agent", _userAgent);
return await CallSsoAsync<SsoResponse>(request);
}
private async Task<T> CallSsoAsync<T>(HttpRequestMessage request)
{
T response = default(T);
using (HttpResponseMessage resp = await _client.SendAsync(request))
{
// Check whether the SSO answered with
// a positive HTTP Status Code
if (resp.IsSuccessStatusCode)
{
// Deserialize the object into the response model
// which will be returned to the application
response = JsonConvert.DeserializeObject<T>(await resp.Content.ReadAsStringAsync());
}
else
{
// Invalid code receieved, inform the user
throw new Exception("The SSO responded with a bad status code: " + resp.StatusCode);
}
}
return response;
}
Take a look at http://restsharp.org/. They also have a nuget package for .net core https://www.nuget.org/packages/RestSharp.NetCore/.
The usage is quite similiar to your example.

Microsoft Translator Bad request (400) issue

I've seen threads on this issue but my problem is particularly confusing. I have a free 2 million character subscription, a valid client id and secret. When I run my code I get to call the API a few times successfully (the most I've seen is 75 consecutive successful calls). Then every other call returns a Bad request response: The remote server returned an error: (400) Bad Request.
I create the token once with my credentials and never create it again. I loop through a file, parse it, and submit every parsed string for translation by calling the API. It seems that I reach some sort of limit that I'm now aware of.
When looking at my account, it doesn't seem to be discounting the characters that I've translated already which would make me highly suspicious that I have the wrong credentials when creating the token. I quadruple-checked that and everything seems to be ok.
Any guidance on what I may be missing here would be much appreciated.
Here's the code that creates the token. I do think though that there may be an unknown limitation that I'm not aware of with the free subscription.
static void gettoken()
{
//Get access token
string clientID = "my client id";
string clientSecret = "my secret";
String strTranslatorAccessURI = "https://datamarket.accesscontrol.windows.net/v2/OAuth2-13";
String strRequestDetails = string.Format("grant_type=client_credentials&client_id={0}&client_secret={1}&scope=http://api.microsofttranslator.com", clientID, clientSecret);
System.Net.WebRequest webRequest = System.Net.WebRequest.Create(strTranslatorAccessURI);
webRequest.ContentType = "application/x-www-form-urlencoded";
webRequest.Method = "POST";
byte[] bytes = System.Text.Encoding.ASCII.GetBytes(strRequestDetails);
webRequest.ContentLength = bytes.Length;
using (System.IO.Stream outputStream = webRequest.GetRequestStream())
{
outputStream.Write(bytes, 0, bytes.Length);
}
System.Net.WebResponse webResponse = webRequest.GetResponse();
System.Runtime.Serialization.Json.DataContractJsonSerializer serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(AdmAccessToken));
AdmAccessToken token = (AdmAccessToken)serializer.ReadObject(webResponse.GetResponseStream());
MyGlobals.headerValue = "Bearer " + token.access_token;
}
And here's the code that calls the API itself. I call the API method from a loop.
static void RunBing(string sterm)
{
//Submit the translation request
string txtToTranslate = sterm;
string uri = "http://api.microsofttranslator.com/v2/Http.svc/Translate?text=" + txtToTranslate + "&from=en&to=es";
System.Net.WebRequest translationWebRequest = System.Net.WebRequest.Create(uri);
translationWebRequest.Headers.Add("Authorization", MyGlobals.headerValue);
System.Net.WebResponse response = null;
try {
response = translationWebRequest.GetResponse();
}
catch (Exception e)
{
Console.WriteLine("Term failed: " + sterm);
Console.WriteLine(e);
return;
}
System.IO.Stream stream = response.GetResponseStream();
System.Text.Encoding encode = System.Text.Encoding.GetEncoding("utf-8");
System.IO.StreamReader translatedStream = new System.IO.StreamReader(stream, encode);
System.Xml.XmlDocument xTranslation = new System.Xml.XmlDocument();
xTranslation.LoadXml(translatedStream.ReadToEnd());
MyGlobals.xlation = xTranslation.InnerText;
}
After several successful calls to the API, I start to get the following message:
System.Net.WebException: The remote server returned an error: (400) Bad Request.
at System.Net.HttpWebRequest.GetResponse()
at Translate.TranslateText.Program.RunBing(String sterm)

OneLogin Create Session Login Token API returns status 400 with message: Bad Request

I am developing a C# application which needs to use the onelogin API to retrieve a session token. I am able to authenticate and and create a token with the following code:
WebRequest Authrequest = WebRequest.Create("https://api.us.onelogin.com/auth/oauth2/token");
Authrequest.Method = "POST";
Authrequest.ContentType = "application/json";
Authrequest.Headers.Add("cache-control", "no-cache");
Authrequest.Headers.Add("Authorization: client_id:XXXXXXX7bbf2c50200d8175206f664dc28ffd3ec66eef0bfedb68c3366420dc, client_secret:XXXXXXXXXX6ba2802187feb23f6450c6812b8e6639361d24aa83f12010f ");
using (var streamWriter = new StreamWriter(Authrequest.GetRequestStream()))
{
string Authjson = new JavaScriptSerializer().Serialize(new
{
grant_type = "client_credentials"
});
streamWriter.Write(Authjson);
}
WebResponse AuthReponse;
AuthReponse = Authrequest.GetResponse();
Stream receiveStream = AuthReponse.GetResponseStream ();
// Pipes the stream to a higher level stream reader with the required encoding format.
StreamReader readStream = new StreamReader (receiveStream);
JObject incdata = JObject.Parse(readStream.ReadToEnd());
string sToken = incdata["data"][0]["access_token"].Value<string>();
AuthReponse.Close();
However, when running the Create Session Login Token with the following code, it only returns a 400 error, and the message has no detail. Just Bad Request:
//Get the session token for the specified user, using the token recieved from previous web request
WebRequest request = WebRequest.Create("https://api.us.onelogin.com/api/1/login/auth");
request.Method = "POST";
request.ContentType = "application/json";
request.Headers.Add("authorization", "bearer:" + sToken);
using (var streamWriter2 = new StreamWriter(request.GetRequestStream()))
{
string json = JsonConvert.SerializeObject(new
{
username_or_email = sUsername,
password = sPassword,
subdomain = "comp-alt-dev"
});
streamWriter2.Write(json);
}
WebResponse response;
response = request.GetResponse();
string streamText = "";
var responseStream = response.GetResponseStream();
using (responseStream)
{
var streamReader = new StreamReader(responseStream);
using (streamReader)
{
streamText = streamReader.ReadToEnd();
streamReader.Close();
//
}
responseStream.Close();
}
Any ideas?
-Thank you
Also for anyone who may be getting this error. in C# the email is case sensitive. I tried User.email.com. In onelogin it was saved as user#email.com. changing the c# to lower case fixed it.
Can you let us know what payload you're sending across the wire to the .../1/login/auth endpoint as well as the response (either as others have suggested as packet snoop, or just as a debug output from the code)
400 means either bad json or the endpoint requires MFA, so this will narrow it down.
~thanks!
Just joining the troubleshooting effort =) -- I can replicate a 400 Bad Request status code with a "bad request" message when the request body contains a username_or_email and/or subdomain value that does not exist, or if the request body is empty.
Can you post what goes over the wire to the OneLogin endpoint...
OK Thanks. So it appears your subdomain does not exist. If you give me an email in the account I can find the correct subdomain value for you.

Lumens VC LC101 - Authorization error

I'm currently making a web application to control a Lumens LC101. When I login to the device I get a session id, which I use to call commands, for authorization. But when I call a command - for example keepalive - and posted the session with it, Lumens gives me an authorization error code: -14.
This is how I post the session and call a command:
ServicePointManager.Expect100Continue = false;
// Create HttpWebRequest
var httpWebRequest = (HttpWebRequest)WebRequest.Create("http://192.168.0.103/api/keepalive");
httpWebRequest.ContentType = "text/json";
httpWebRequest.Method = "POST";
// Post json data
using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream())) {
streamWriter.Write("{\"session\":\"" + session + "\"}");
streamWriter.Flush();
streamWriter.Close();
}
// Get response of HttpWebRequest
var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
// Read stream from response
using (var streamReader = new StreamReader(httpResponse.GetResponseStream())) {
string result = streamReader.ReadToEnd();
JObject jsonResult = JObject.Parse(result);
int code = Int32.Parse(jsonResult.GetValue("code").ToString());
// Return result
if (code >= 0) return result;
int code will be -14 in this case, because authorization failed. But I'm using the same session id, which I got from the login.
How do I make sure it won't give me an authorization error code: -14?
EDIT
This is how it's described in the documentation:
Here's the documentation. Session is described in paragraph 1.2

How to redirect a WCF to an external URL (Google AdWords Sign In) and handle the response?

I am building a WCF that encapsulates Google OAuth 2.0.
The plan: Client calls WCF endpoint which then redirects to the Google Sign In page for user authentication. On successful authentication, Google kicks back to the redirect URI and returns the Access Token in the response.
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class AdWordsOAuthService : IOAuthService
{
public string AuthenticateAndGetRefreshToken()
{
string refreshToken = string.Empty;
try
{
AuthenticateUser();
}
catch (Exception ex)
{
throw;
}
return refreshToken;
}
private void AuthenticateUser()
{
// build the SOAP header here...
string authUrl = "https://accounts.google.com/o/oauth2/auth"; //redirect to this url
string postData = "response_type=code"
+ "&client_id=" + HttpUtility.UrlEncode("9999999999999.apps.googleusercontent.com")
//+ "&client_secret=" + HttpUtility.UrlEncode("dsflkdfsljkdfskjldskjlfds")
+ "&redirect_uri=" + HttpUtility.UrlEncode("http://localhost:50306/NextOAuthService/AdWordsOAuthService/AuthenticateAndGetRefreshToken")
+ "&scope=" + HttpUtility.UrlEncode("https://adwords.google.com/api/adwords")
+ "&access_type=offline";
byte[] byteArray = Encoding.UTF8.GetBytes(postData);
//handle webrequest stuff...
var request = (HttpWebRequest)WebRequest.Create(authUrl);// + postData);
if (request != null)
{
request.Method = "POST";
request.Timeout = 20000;
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = byteArray.Length; // byteArray.Length;
Stream dataStream = request.GetRequestStream();
dataStream.Write(byteArray, 0, byteArray.Length);
dataStream.Close();
//attempt to redirect to the Adwords OAuth URL: https://adwords.google.com/api/adwords&?response.....blah blah
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.RedirectKeepVerb;
WebOperationContext.Current.OutgoingResponse.Location = response.ResponseUri.AbsoluteUri;
return;
}
}
}
What's Actually Happening: When the code attempts the redirect, I get following exception:
The content type text/html; charset=UTF-8 of the response message does not match the content type of the binding (application/soap+xml;> charset=utf-8)
When I examine the response (using fiddler), the response contains the actual HTML code for the Google OAuth Sign In page. So, instead of redirecting the WCF to the url, I am instead receiving an html response containing the page source.
How do I get around this and force the redirect?
I think you are miss understanding the calls a bit. The First URL that you are building there is the one that you should be displaying to the user asking if they want to let you access there data.
You need to wait for them to except. You need to deal with the Authentication Code. Google 3 Legged OAuth2 Flow
You should consider using Googles dot net client lib. It will handle all this for you.

Categories

Resources