I have a WCF Webservice which checks if the user is valid.
If the user is valid I want to generate a token which expires after 24 hours.
public bool authenticateUserManual(string userName, string password,string language,string token)
{
if (Membership.ValidateUser(userName,password))
{
//////////
string token = ????
//////////
return true;
}
else
{
return false;
}
}
There are two possible approaches; either you create a unique value and store somewhere along with the creation time, for example in a database, or you put the creation time inside the token so that you can decode it later and see when it was created.
To create a unique token:
string token = Convert.ToBase64String(Guid.NewGuid().ToByteArray());
Basic example of creating a unique token containing a time stamp:
byte[] time = BitConverter.GetBytes(DateTime.UtcNow.ToBinary());
byte[] key = Guid.NewGuid().ToByteArray();
string token = Convert.ToBase64String(time.Concat(key).ToArray());
To decode the token to get the creation time:
byte[] data = Convert.FromBase64String(token);
DateTime when = DateTime.FromBinary(BitConverter.ToInt64(data, 0));
if (when < DateTime.UtcNow.AddHours(-24)) {
// too old
}
Note: If you need the token with the time stamp to be secure, you need to encrypt it. Otherwise a user could figure out what it contains and create a false token.
I like Guffa's answer and since I can't comment I will provide the answer Udil's question here.
I needed something similar but I wanted certein logic in my token, I wanted to:
See the expiration of a token
Use a guid to mask validate (global application guid or user guid)
See if the token was provided for the purpose I created it (no reuse..)
See if the user I send the token to is the user that I am validating it for
Now points 1-3 are fixed length so it was easy, here is my code:
Here is my code to generate the token:
public string GenerateToken(string reason, MyUser user)
{
byte[] _time = BitConverter.GetBytes(DateTime.UtcNow.ToBinary());
byte[] _key = Guid.Parse(user.SecurityStamp).ToByteArray();
byte[] _Id = GetBytes(user.Id.ToString());
byte[] _reason = GetBytes(reason);
byte[] data = new byte[_time.Length + _key.Length + _reason.Length+_Id.Length];
System.Buffer.BlockCopy(_time, 0, data, 0, _time.Length);
System.Buffer.BlockCopy(_key , 0, data, _time.Length, _key.Length);
System.Buffer.BlockCopy(_reason, 0, data, _time.Length + _key.Length, _reason.Length);
System.Buffer.BlockCopy(_Id, 0, data, _time.Length + _key.Length + _reason.Length, _Id.Length);
return Convert.ToBase64String(data.ToArray());
}
Here is my Code to take the generated token string and validate it:
public TokenValidation ValidateToken(string reason, MyUser user, string token)
{
var result = new TokenValidation();
byte[] data = Convert.FromBase64String(token);
byte[] _time = data.Take(8).ToArray();
byte[] _key = data.Skip(8).Take(16).ToArray();
byte[] _reason = data.Skip(24).Take(2).ToArray();
byte[] _Id = data.Skip(26).ToArray();
DateTime when = DateTime.FromBinary(BitConverter.ToInt64(_time, 0));
if (when < DateTime.UtcNow.AddHours(-24))
{
result.Errors.Add( TokenValidationStatus.Expired);
}
Guid gKey = new Guid(_key);
if (gKey.ToString() != user.SecurityStamp)
{
result.Errors.Add(TokenValidationStatus.WrongGuid);
}
if (reason != GetString(_reason))
{
result.Errors.Add(TokenValidationStatus.WrongPurpose);
}
if (user.Id.ToString() != GetString(_Id))
{
result.Errors.Add(TokenValidationStatus.WrongUser);
}
return result;
}
private static string GetString(byte[] reason) => Encoding.ASCII.GetString(reason);
private static byte[] GetBytes(string reason) => Encoding.ASCII.GetBytes(reason);
The TokenValidation class looks like this:
public class TokenValidation
{
public bool Validated { get { return Errors.Count == 0; } }
public readonly List<TokenValidationStatus> Errors = new List<TokenValidationStatus>();
}
public enum TokenValidationStatus
{
Expired,
WrongUser,
WrongPurpose,
WrongGuid
}
Now I have an easy way to validate a token, no Need to Keep it in a list for 24 hours or so.
Here is my Good-Case Unit test:
private const string ResetPasswordTokenPurpose = "RP";
private const string ConfirmEmailTokenPurpose = "EC";//change here change bit length for reason section (2 per char)
[TestMethod]
public void GenerateTokenTest()
{
MyUser user = CreateTestUser("name");
user.Id = 123;
user.SecurityStamp = Guid.NewGuid().ToString();
var token = sit.GenerateToken(ConfirmEmailTokenPurpose, user);
var validation = sit.ValidateToken(ConfirmEmailTokenPurpose, user, token);
Assert.IsTrue(validation.Validated,"Token validated for user 123");
}
One can adapt the code for other business cases easely.
Happy Coding
Walter
Use Dictionary<string, DateTime> to store token with timestamp:
static Dictionary<string, DateTime> dic = new Dictionary<string, DateTime>();
Add token with timestamp whenever you create new token:
dic.Add("yourToken", DateTime.Now);
There is a timer running to remove any expired tokens out of dic:
timer = new Timer(1000*60); //assume run in 1 minute
timer.Elapsed += timer_Elapsed;
static void timer_Elapsed(object sender, ElapsedEventArgs e)
{
var expiredTokens = dic.Where(p => p.Value.AddDays(1) <= DateTime.Now)
.Select(p => p.Key);
foreach (var key in expiredTokens)
dic.Remove(key);
}
So, when you authenticate token, just check whether token exists in dic or not.
you need to store the token while creating for 1st registration. When you retrieve data from login table you need to differentiate entered date with current date if it is more than 1 day (24 hours) you need to display message like your token is expired.
To generate key refer here
I also had a similar but slightly different problem. I needed a token that could be spent within 2 minutes on a different server. ServerA and serverB shares a private key. Of course in the user browser, the token is public.
The web server A creates a string with a datetime in it. Then it hashes the string. The user in the browser uses the token. When server B receives the token does the same hash and compares the result.
ServerA and serverB run c# code.
The problem is similar but not the same, but maybe the code can help someone anyway ....
public class TokenHelper
{
private static byte[] GetHash(string inputString)
{
using (HashAlgorithm algorithm = MD5.Create())
return algorithm.ComputeHash(Encoding.UTF8.GetBytes(inputString));
}
private static string GetHashString(string inputString)
{
StringBuilder sb = new StringBuilder();
foreach (byte b in GetHash(inputString))
sb.Append(b.ToString("X2"));
return sb.ToString();
}
public static Guid GetTokenForGuest(EnumWebsiteName website, string privateKey)
{
string datetime = DateTime.Now.ToString("dd/MM/yyyy hh:mm");
var hashString = GetHashString($"{datetime}|{website}|{privateKey}");
return new Guid(hashString);
}
public static bool CheckTokenForGuest(Guid token, EnumWebsiteName website, string privateKey)
{
string datetime = DateTime.Now.ToString("dd/MM/yyyy hh:mm");
var hashString = GetHashString($"{datetime}|{website}|{privateKey}");
var test1 = new Guid(hashString);
if (test1.CompareTo(token)==0) {
return true;
}
string datetime2 = DateTime.Now.AddMinutes(-1).ToString("dd/MM/yyyy hh:mm");
var hashString2 = GetHashString($"{datetime2}|{website}|{privateKey}");
var test2 = new Guid(hashString2);
if (test2.CompareTo(token) == 0)
{
return true;
}
return false;
}
}
This way a token will exist up-to 24 hours. here is the code to generate token which will valid up-to 24 Hours. this code we use but i did not compose it.
public static string GenerateToken()
{
int month = DateTime.Now.Month;
int day = DateTime.Now.Day;
string token = ((day * 100 + month) * 700 + day * 13).ToString();
return token;
}
Related
I got "The account name or password that you have entered is incorrect" error when trying login using this api endpoint: https://steamcommunity.com/login/dologin/
I am using the credentials I use when logging in via Steam app or Steam web, so I don't think I have a problem with my credentials.
Here code which code I use:
public bool DoLogin(string username, string password)
{
var data = new NameValueCollection { { "username", username } };
// First get the RSA key with which we will encrypt our password.
string response = Fetch("https://steamcommunity.com/login/getrsakey", "POST", data, false);
GetRsaKey rsaJson = JsonConvert.DeserializeObject<GetRsaKey>(response);
// Validate, if we could get the rsa key.
if (!rsaJson.success)
{
return false;
}
// RSA Encryption.
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
RSAParameters rsaParameters = new RSAParameters
{
Exponent = HexToByte(rsaJson.publickey_exp),
Modulus = HexToByte(rsaJson.publickey_mod)
};
rsa.ImportParameters(rsaParameters);
// Encrypt the password and convert it.
byte[] bytePassword = Encoding.ASCII.GetBytes(password);
byte[] encodedPassword = rsa.Encrypt(bytePassword, false);
string encryptedBase64Password = Convert.ToBase64String(encodedPassword);
SteamResult loginJson = null;
CookieCollection cookieCollection;
string steamGuardText = "";
string steamGuardId = "";
// Do this while we need a captcha or need email authentification. Probably you have misstyped the captcha or the SteamGaurd code if this comes multiple times.
do
{
Console.WriteLine("SteamWeb: Logging In...");
bool captcha = loginJson != null && loginJson.captcha_needed;
bool steamGuard = loginJson != null && loginJson.emailauth_needed;
string time = Uri.EscapeDataString(rsaJson.timestamp);
string capGid = string.Empty;
// Response does not need to send if captcha is needed or not.
// ReSharper disable once MergeSequentialChecks
if (loginJson != null && loginJson.captcha_gid != null)
{
capGid = Uri.EscapeDataString(loginJson.captcha_gid);
}
data = new NameValueCollection { { "password", encryptedBase64Password }, { "username", username } };
// Captcha Check.
string capText = "";
if (captcha)
{
Console.WriteLine("SteamWeb: Captcha is needed.");
System.Diagnostics.Process.Start("https://steamcommunity.com/public/captcha.php?gid=" + loginJson.captcha_gid);
Console.WriteLine("SteamWeb: Type the captcha:");
string consoleText = Console.ReadLine();
if (!string.IsNullOrEmpty(consoleText))
{
capText = Uri.EscapeDataString(consoleText);
}
}
data.Add("captchagid", captcha ? capGid : "-1");
data.Add("captcha_text", captcha ? capText : "");
// Captcha end.
// Added Header for two factor code.
data.Add("twofactorcode", "");
// Added Header for remember login. It can also set to true.
data.Add("remember_login", "false");
// SteamGuard check. If SteamGuard is enabled you need to enter it. Care probably you need to wait 7 days to trade.
// For further information about SteamGuard see: https://support.steampowered.com/kb_article.php?ref=4020-ALZM-5519&l=english.
if (steamGuard)
{
Console.WriteLine("SteamWeb: SteamGuard is needed.");
Console.WriteLine("SteamWeb: Type the code:");
string consoleText = Console.ReadLine();
if (!string.IsNullOrEmpty(consoleText))
{
steamGuardText = Uri.EscapeDataString(consoleText);
}
steamGuardId = loginJson.emailsteamid;
// Adding the machine name to the NameValueCollection, because it is requested by steam.
Console.WriteLine("SteamWeb: Type your machine name:");
consoleText = Console.ReadLine();
var machineName = string.IsNullOrEmpty(consoleText) ? "" : Uri.EscapeDataString(consoleText);
data.Add("loginfriendlyname", machineName != "" ? machineName : "defaultSteamBotMachine");
}
data.Add("emailauth", steamGuardText);
data.Add("emailsteamid", steamGuardId);
// SteamGuard end.
// Added unixTimestamp. It is included in the request normally.
var unixTimestamp = (int)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
// Added three "0"'s because Steam has a weird unix timestamp interpretation.
data.Add("donotcache", unixTimestamp + "000");
data.Add("rsatimestamp", time);
// Sending the actual login.
using (HttpWebResponse webResponse = Request("https://steamcommunity.com/login/dologin/", "POST", data, false))
{
var stream = webResponse.GetResponseStream();
if (stream == null)
{
return false;
}
using (StreamReader reader = new StreamReader(stream))
{
string json = reader.ReadToEnd();
loginJson = JsonConvert.DeserializeObject<SteamResult>(json);
cookieCollection = webResponse.Cookies;
}
}
} while (loginJson.captcha_needed || loginJson.emailauth_needed);
// If the login was successful, we need to enter the cookies to steam.
if (loginJson.success)
{
_cookies = new CookieContainer();
foreach (Cookie cookie in cookieCollection)
{
_cookies.Add(cookie);
}
SubmitCookies(_cookies);
return true;
}
else
{
Console.WriteLine("SteamWeb Error: " + loginJson.message);
return false;
}
}
enter image description here
Is there another solution how to login to steam and get html?
Ok, so I checked your encrypting and it looks fine. It is a little bit of chaos in your code so i will explain it with my:
I prefer to use RestSharp and Newton Soft JSON to do this, so a little mandatory stuff in Class body:
private IRestClient restClientTemporary;
private string getKeysURL = "/login/getrsakey/";
private string loginWithKey = "/login/dologin/";
Create RestClient, and RestRequests:
restClientTemporary = new RestClient("https://steamcommunity.com");
var request = new RestRequest(getKeysURL, Method.POST);
request.AddParameter("username", "YourSteamLogin");
var resp = restClientTemporary.Execute(request);
GetRsaResult response = Newtonsoft.Json.JsonConvert.DeserializeObject<GetRsaResult>(resp.Content);
Then i used your code as method to encrypt:
public static string EncryptionSof(string password, GetRsaResult response)
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
RSAParameters rsaParameters = new RSAParameters
{
Exponent = HexToByte(response.publickey_exp),
Modulus = HexToByte(response.publickey_mod)
};
rsa.ImportParameters(rsaParameters);
// Encrypt the password and convert it.
byte[] bytePassword = Encoding.ASCII.GetBytes(password);
byte[] encodedPassword = rsa.Encrypt(bytePassword, false);
return Convert.ToBase64String(encodedPassword);
}
And then used this method:
string password = EncryptionSof("admin123/*its your steam password i think*/", response);
Next step is make request to get login data:
var loginRequest = new RestRequest(loginWithKey);
loginRequest.AddParameter("username", "YourSteamLogin");
loginRequest.AddParameter("password", password);
loginRequest.AddParameter("rsatimestamp", response.timestamp);
loginRequest.AddParameter("remember_login", false);
//Captcha stuff if needed:
loginRequest.AddParameter("captchagid", 3086601225255895896);
loginRequest.AddParameter("captcha_text", "LHYJ2P");
And finally execute it:
var responseFinal = restClientTemporary.Execute(loginRequest);
In response i received everything i need in responseFinal.Content:
{
"success":true,
"requires_twofactor":false,
"login_complete":true,
"transfer_urls":[
"https:\\/\\/store.steampowered.com\\/login\\/transfer",
"https:\\/\\/help.steampowered.com\\/login\\/transfer"
],
"transfer_parameters":{
"steamid":"12344567898765432",
"token_secure":"xDDDDDDDDD",
"auth":"LOOOOOL",
"remember_login":false
}
}
GetRsaResult model looks like:
public class GetRsaResult
{
public bool success { get; set; }
public string publickey_mod { get; set; }
public string publickey_exp { get; set; }
public string timestamp { get; set; }
public string token_gid { get; set; }
}
Ah, and i forget about changing 16-string to byte[], its method found on Stackoverflow:
public static byte[] HexToByte(string hex)
{
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}
And you have to remember one thing You never want to send plaintext password to steam. First request /login/getrsakey/ is just only for get keys to encrypt password. Steam gives you key to encrypt your password. So, you will use your plaintext password (in my sample its admin123 ) in your program to encrypt it with keys you received from Steam.
In second request /login/dologin/ you must send encrypted password (result of EncryptionSoF method)
I'm trying to get my integrations updated to handle this forced use of token authentication for NetSuite web services. However, I'm stuck getting the following:
[System.Web.Services.Protocols.SoapException] Ambiguous authentication
I've tried various things with no improvement. Doesn't seem to matter if I use a token created beforehand, or have one generated. I'm not creating a "Cookie Container", or adding a standard Passport object. I've tried a few different methods for generating a signature and a nonce value. I've even switched between SHA1 and SHA256 thinking that might make a difference. I'm going to include my code here. Hopefully someone can see what I'm doing wrong.
FYI, there are some components in here from trying what I found in this post: Ambiguous Authentication in Netsuite Token Based API call
static void Main(string[] args) {
NameValueCollection _dataCollection = ConfigurationManager.AppSettings;
NSCreds crd = new NSCreds(_dataCollection); /// just building a data object to handle credentials and keys
NSWS ns = new NSWS(crd, _dataCollection["appId"]); // token passport gets built here
// now to make a call to just get a single file from the File Cabinet
RecordRef pullFile = new RecordRef();
pullFile.type = RecordType.file;
pullFile.typeSpecified = true;
pullFile.internalId = _dataCollection["fileId"];
ReadResponse rres = ns.service.get(pullFile); // this line throws the error highlighted above
}
public NSWS(NSCreds c, String appId) {
CheckConnectionSecurity(); // makes sure connection security is set to TLS 1.2
_pageSize = 100;
service = new NetSuiteService();
service.Timeout = 1000 * 60 * 60 * 2;
service.tokenPassport = prepareTokenPassport(c);
ApplicationInfo appInfo = new ApplicationInfo();
appInfo.applicationId = appId;
service.applicationInfo = appInfo;
}
public TokenPassport prepareTokenPassport(NSCreds c) {
long TimeStamp = ComputeTimestamp();
String nonce = CreateNonce_1();
NSToken token = null;
if (String.IsNullOrEmpty(c.tokenId) && String.IsNullOrEmpty(c.tokenSecret)) {
token = GetToken(c.customerKey, c); // make calls to NetSuite to generate token data and build custom object
} else {
token = new NSToken(c.tokenId,c.tokenSecret); // build custom object from apps settings data
}
String signature = ComputeSignature(c.account, c.customerKey, c.customerSecret, token.tokenId, token.tokenSecret, nonce, TimeStamp);
TokenPassport tokenPassport = new TokenPassport();
tokenPassport.account = c.account;
tokenPassport.consumerKey = c.customerKey;
tokenPassport.token = token.tokenId;
tokenPassport.nonce = nonce;
tokenPassport.timestamp = TimeStamp;
TokenPassportSignature signatureElement = new TokenPassportSignature();
signatureElement.algorithm = "HMAC-SHA1"; // "HMAC-SHA256";
signatureElement.Value = signature;
tokenPassport.signature = signatureElement;
return tokenPassport;
}
private static long ComputeTimestamp() {
return ((long)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds);
}
private String CreateNonce_1() {
int length = 20;
String AllowedChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
StringBuilder nonce = new StringBuilder();
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
byte[] rnd = new byte[1];
for (int i = 0; i < length; i++) {
while (true) {
rng.GetBytes(rnd);
char c = (char)rnd[0];
if (AllowedChars.IndexOf(c) != (-1)) {
nonce.Append(rnd[0]);
break;
}
}
}
return nonce.ToString();
}
private static string CreateNonce_2() {
return Guid.NewGuid().ToString("N");
}
private String CreateNonce_3() {
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
byte[] data = new byte[20];
rng.GetBytes(data);
int value = Math.Abs(BitConverter.ToInt32(data, 0));
return value.ToString();
}
private string ComputeSignature(String account, String cKey, String cSecret, String token, String tokenSecret, String nonce, long timeStamp) {
String baseString = String.Format("{0}&{1}&{2}&{3}&{4}",account,cKey,token,nonce,timeStamp);
String key = String.Format("{0}&{1}", cSecret, tokenSecret);
// previous method for encoding the signature
// Mac is a custom object found from another post here
// EncryptionMethods is an enumeration from that same post
/*
//using (var secretKey = new SecretKeySpec(GetBytes(key), EncryptionMethods.HMACSHA256))
using (var secretKey = new SecretKeySpec(GetBytes(key), EncryptionMethods.HMACSHA1))
using (Mac mac = new Mac(secretKey, baseString)) {
return mac.AsBase64();
}
*/
//HMACSHA256 hashObject = new HMACSHA256(Encoding.UTF8.GetBytes(key));
HMACSHA1 hashObject = new HMACSHA1(Encoding.UTF8.GetBytes(key));
byte[] signature = hashObject.ComputeHash(Encoding.UTF8.GetBytes(baseString));
string encodedSignature = Convert.ToBase64String(signature);
return encodedSignature;
}
It turns out that the problem was setting the Application ID while also specifying a Token Passport. Doing so actually creates a conflict with the system not knowing which to use for authentication, since the token itself references the Application ID internally. So, removed the bit where the Application ID was being set to the service object and everything started working correctly.
Try to use this code for token based authentication
TokenPassport tokenPassport = new TokenPassport();
tokenPassport.account = account; //Account ID
tokenPassport.consumerKey = consumerKey; //Consumer Key
tokenPassport.token = tokenId; // Token ID
tokenPassport.nonce = nonce; //It is some calculated value with the help of RNGCryptoServiceProvider class.
tokenPassport.timestamp = timestamp;
tokenPassport.signature = signature; // It is TokenPassportSignature
TokenPassportSignature also uses Account ID, Token ID & Token Secret. It has some algorithms.
I am trying to get access to my Withings/Nokia scales data via oauth (.net core C#).
Instructions can be found at:
https://oauth.withings.com/en/api/oauthguide
And API guide here:
https://developer.health.nokia.com/api#step1
I have achieved Part 1 - I get an auth token and secret.
Part 2 - manually I have retrieved a code authorizing my app's usage of my withings scales data - i.e. auth code as a result of the call back (via the API developers page). I am presuming this only needs to be done once to authorize my app's access permanently. I have hardcoded this value into my code and update it if I re-authorize the app.
Now I am stuck on Part 3 - getting the access token/secret.
ERROR = Invalid signature
(using the above page I have been able to retrieve my 4 years worth of scales data so I know it should work).
My base signature is identical to the above API test page (apart from the nonce, signature and timestamp).
My url is identical to to the above API test page (apart from the nonce and timestamp).
The mystery for me is why this works for Part 1 and not Part 3.
Is it the code that is bad or simply that the request token must be authorized against the application/users data before a request can be made?
But surely I don't have to re-authorize with the user every time??
I originally messed up the Part 1 and gave an invalid signature error - this was clearly an issue with the signature - but I have re-checked the signature in Part 3 and it is good.
private const string AUTH_VERSION = "1.0";
private const string SIGNATURE_METHOD = "HMAC-SHA1";
private const string BASE_URL_REQUEST_AUTH_TOKEN = "https://developer.health.nokia.com/account/request_token";
private const string BASE_URL_REQUEST_ACCESS_TOKEN = "https://developer.health.nokia.com/account/access_token";
...
Withings w = new Withings();
OAuthToken t = await w.GetOAuthToken();
string token = t.OAuth_Token;
string secret = t.OAuth_Token_Secret;
OAuthAccessToken at = await w.GetOAuthAccess(t);
string aToken = at.OAuth_Token;
string aTokenSecret = at.OAuth_Token_Secret;
...
public async Task<OAuthAccessToken> GetOAuthAccess(OAuthToken authToken)
{
OAuthAccessToken token = new OAuthAccessToken();
try
{
string random = GetRandomString();
string timestamp = GetTimestamp();
string baseSignature = GetOAuthAccessSignature(authToken, random, timestamp);
string hashSignature = ComputeHash(baseSignature, CONSUMER_SECRET, authToken.OAuth_Token_Secret);
string codeSignature = UrlEncode(hashSignature);
string requestUrl = GetOAuthAccessUrl(authToken, codeSignature, random, timestamp);
HttpResponseMessage response = await client.GetAsync(requestUrl);
string responseBodyAsText = await response.Content.ReadAsStringAsync();
string[] parameters = responseBodyAsText.Split('&');
token.OAuth_Token = parameters[0].Split('=')[1].ToString();
token.OAuth_Token_Secret = parameters[1].Split('=')[1].ToString();
}
catch (Exception ex)
{
}
return token;
}
private string GetOAuthAccessSignature(OAuthToken authToken, string random, string timestamp)
{
var urlDict = new SortedDictionary<string, string>
{
//{ "oauth_consumer_key", CONSUMER_KEY},
{ "oauth_nonce", random},
{ "oauth_signature_method", UrlEncode(SIGNATURE_METHOD)},
{ "oauth_timestamp", timestamp},
{ "oauth_token", END_USER_AUTHORISATION_REQUEST_TOKEN },
{ "oauth_version", AUTH_VERSION}
};
StringBuilder sb = new StringBuilder();
sb.Append("GET&" + UrlEncode(BASE_URL_REQUEST_ACCESS_TOKEN) + "&oauth_consumer_key%3D" + CONSUMER_KEY);
int count = 0;
foreach (var urlItem in urlDict)
{
count++;
if (count >= 1) sb.Append(UrlEncode("&"));
sb.Append(UrlEncode(urlItem.Key + "=" + urlItem.Value));
}
return sb.ToString();
}
private string GetOAuthAccessUrl(OAuthToken authToken, string signature, string random, string timestamp)
{
var urlDict = new SortedDictionary<string, string>
{
{ "oauth_consumer_key", CONSUMER_KEY},
{ "oauth_nonce", random},
{ "oauth_signature", signature },
{ "oauth_signature_method", UrlEncode(SIGNATURE_METHOD)},
{ "oauth_timestamp", timestamp},
{ "oauth_token", END_USER_AUTHORISATION_REQUEST_TOKEN },
{ "oauth_version", AUTH_VERSION}
};
StringBuilder sb = new StringBuilder();
sb.Append(BASE_URL_REQUEST_ACCESS_TOKEN + "?");
int count = 0;
foreach (var urlItem in urlDict)
{
count++;
if (count > 1) sb.Append("&");
sb.Append(urlItem.Key + "=" + urlItem.Value);
}
return sb.ToString();
}
Notes:
I have ordered my parameters
I have a decent urlencode (correct me wrong)
I have a hmac-sha1 hashing (correct me wrong)
Not interested in using open libraries - I want to fix this code without third party tools
Below is the helper methods I am using:
private string ComputeHash(string data, string consumerSecret, string tokenSecret = null)
{
// Construct secret key based on consumer key (and optionally include token secret)
string secretKey = consumerSecret + "&";
if (tokenSecret != null) secretKey += tokenSecret;
// Initialise with secret key
System.Security.Cryptography.HMACSHA1 hmacsha = new System.Security.Cryptography.HMACSHA1(Encoding.ASCII.GetBytes(secretKey));
hmacsha.Initialize();
// Convert data into byte array
byte[] dataBuffer = Encoding.ASCII.GetBytes(data);
// Computer hash of data byte array
byte[] hashBytes = hmacsha.ComputeHash(dataBuffer);
// Return the base 64 of the result
return Convert.ToBase64String(hashBytes);
}
// Get random string
private string GetRandomString()
{
return Guid.NewGuid().ToString().Replace("-", "");
}
// Get timestamp
private string GetTimestamp()
{
var ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
return Convert.ToInt64(ts.TotalSeconds).ToString();
}
// Url Encode (as Uri.Escape is reported to be not appropriate for this purpose)
protected string UnreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
protected string UrlEncode(string value)
{
var result = new StringBuilder();
foreach (var symbol in value)
{
if (UnreservedChars.IndexOf(symbol) != -1)
result.Append(symbol);
else
result.Append('%' + $"{(int)symbol:X2}");
}
return result.ToString();
}
Thanks,Dan.
Solved - this was a problem with my understanding of how oauth works.
Step 1 - Get a token (this allows you to make requests based on your api account application)
Step 2 - Create a URL (using the 2 minute token above) that redirects the user to authorize your Withings api applications to use a specific user's account. The same token is returned as you passed it - but now it will be allowed to make the request in step 3.
Step 3 - Requests an access token - this will give you an token and secret string that permits your continued access to this user's account (for your api account application).
Step 4 - Requesting data - similar in method to all previous steps - quite easy. Returns a big long string of data. Read the API documents as you can filter - which is what I will be doing as I have about 4/5 years worth of 'interesting' weight data.
I was doing step 1 and then doing step 3 thinking that the code returned from step 2 (not having noticed it was the same as the one put in) could be stored and used for step 3 without having to re-authorize.
You can actually (and what I have done) is follow the API demo interface to generate the auth token and secret in step 3 and that's all you need to continue to request data. You only need user authorization once and store step 3 auth token/secret against a user account / a store of some sort.
Also note that you can invoke notifications - new weight triggers your website to automatically refresh the data. Great if you just want to login and see the latest data without having the manually trigger a refresh of the data / cause a further delay.
Be aware that the API has filtering options - so make sure you read up on those.
Here's some (basic) code which may be of some use:
public async Task<string> GetData_BodyMeasures()
{
string rawdata = "";
try
{
string random = GetRandomString();
string timestamp = GetTimestamp();
string baseSignature = GetDataSignature_BodyMeasure(random, timestamp);
string hashSignature = ComputeHash(baseSignature, CONSUMER_SECRET, ACCESS_OAUTH_TOKEN_SECRET);
string codeSignature = UrlEncode(hashSignature);
string requestUrl = GetData_BodyMeasure_Url(codeSignature, random, timestamp);
HttpResponseMessage response = await client.GetAsync(requestUrl);
string responseBodyAsText = await response.Content.ReadAsStringAsync();
rawdata = responseBodyAsText;
}
catch (Exception ex)
{
}
return rawdata;
}
private string GetDataSignature_BodyMeasure(string random, string timestamp)
{
var urlDict = new SortedDictionary<string, string>
{
{ "oauth_consumer_key", CONSUMER_KEY},
{ "oauth_nonce", random},
{ "oauth_signature_method", SIGNATURE_METHOD},
{ "oauth_timestamp", timestamp},
{ "oauth_token", ACCESS_OAUTH_TOKEN },
{ "oauth_version", AUTH_VERSION},
{ "userid", USER_ID }
};
StringBuilder sb = new StringBuilder();
sb.Append("GET&" + UrlEncode(BASE_URL_REQUEST_BODY_MEASURE) + "&action%3Dgetmeas");
int count = 0;
foreach (var urlItem in urlDict)
{
count++;
if (count >= 1) sb.Append(UrlEncode("&"));
sb.Append(UrlEncode(urlItem.Key + "=" + urlItem.Value));
}
return sb.ToString();
}
private string GetData_BodyMeasure_Url(string signature, string random, string timestamp)
{
var urlDict = new SortedDictionary<string, string>
{
{ "action", "getmeas"},
{ "oauth_consumer_key", CONSUMER_KEY},
{ "oauth_nonce", random},
{ "oauth_signature", signature },
{ "oauth_signature_method", UrlEncode(SIGNATURE_METHOD)},
{ "oauth_timestamp", timestamp},
{ "oauth_token", ACCESS_OAUTH_TOKEN },
{ "oauth_version", AUTH_VERSION},
{ "userid", USER_ID }
};
StringBuilder sb = new StringBuilder();
sb.Append(BASE_URL_REQUEST_BODY_MEASURE + "?");
int count = 0;
foreach (var urlItem in urlDict)
{
count++;
if (count >= 1) sb.Append("&");
sb.Append(urlItem.Key + "=" + urlItem.Value);
}
return sb.ToString();
}
When our mobile app user sends app-invite to fb user and he accepts it, the server should give a reward to the first one. So I need a way to verify whether the invite was sent.
var fb = new FacebookClient(APP_ID + "|" + SECRET_ID);
fb.AppId = APP_ID;
fb.AppSecret = SECRET_ID;
dynamic result = fb.Get(???);
I searched on GraphAPI docs and it seems that I need to retrieve users apprequests. How to do that from the server side and where to look at to perform such verification?
UPDATE
Ok, now I know that it's allowed to reward only for accepted invites. I can record who invites who in the db and give a reward only when a new invited user joins. But I still need a way to verify that these invites were actually sent.
UPDATE2
As the documentation states apprequests call from application returns all the requests sent from this application. So I think it would be enough for me to just check that there are any requests from this app:
dynamic result = fb.Get("/" + facebookId + "/apprequests");
IEnumerable data = result.data;
return data.Cast<object>().Count() != 0;
But I can't check it now. Can anyone confirm that if a user sends invite to app to another user this invite will be seen through apprequests from the application access token?
my code for this:
public static FacebookRequestData GetInviteHash()
{
string requestId = Request["request_ids"];
var accessToken = GetAccessToken(ConfigurationManager.AppSettings["FacebookAppId"], ConfigurationManager.AppSettings["FacebookSecret"]);
string response;
using (var webClient = new WebClient())
{
response = webClient.DownloadString(string.Format("https://graph.facebook.com/{0}?{1}", requestId, accessToken));
}
var javaScriptSerializer = new JavaScriptSerializer();
return javaScriptSerializer.Deserialize<FacebookRequestData>(javaScriptSerializer.Deserialize<FacebookRequestInfo>(response).data);
}
private static string GetAccessToken(string appId, string password)
{
using (var webClient = new WebClient())
{
return webClient.DownloadString(string.Format("https://graph.facebook.com/oauth/access_token?client_id={0}&client_secret={1}&grant_type=client_credentials", appId, password));
}
}
private class FacebookRequestInfo
{
public string data { get; set; }
}
FacebookRequestData - my custom class with structure of fields that I posted to fb earlier
Done it:
public static bool CheckInvite(string fromId, string toId)
{
var fb = new FacebookClient(APP_ID + "|" + SECRET_ID);
fb.AppId = APP_ID;
fb.AppSecret = SECRET_ID;
dynamic result = fb.Get(string.Format("/{0}/apprequests", toId));
foreach (var el in result.data)
if ((string)el.from.id == fromId)
{
DateTime dateTime = DateTime.Parse((string)el.created_time, CultureInfo.InvariantCulture);
if ((DateTime.Now - dateTime).TotalMinutes < 15)
{
return true;
}
}
return false;
}
This is what I have so far, but it isn't working as I don't understand how DotNetOpenAuth is supposed to work. I only need it to sign the outcome with my key, but I am not having luck. Everything seems to point towards me needing to get the client to authorize my access, but I just need to get it signed as I don't need the user for this request.
Refer to http://developer.netflix.com/docs/read/Security , the section labeled "Netflix API Requests"
public class class1
{
private void Main()
{
string consumerKey = "<MyAPIKey>";
string consumerSecret = "<MyAPISharedSecret>";
var tokenManager = new InMemoryTokenManager(consumerKey, consumerSecret);
MessageReceivingEndpoint oauthEndpoint =
new MessageReceivingEndpoint(new Uri("http://api-public.netflix.com/catalog/titles/index"),
HttpDeliveryMethods.PostRequest);
WebConsumer consumer = new WebConsumer(
new ServiceProviderDescription
{
RequestTokenEndpoint = oauthEndpoint,
UserAuthorizationEndpoint = oauthEndpoint,
AccessTokenEndpoint = oauthEndpoint,
TamperProtectionElements =
new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement()},
},
tokenManager);
var result = consumer.Channel.Request(new AccessProtectedResourceRequest());
}
internal class InMemoryTokenManager : IConsumerTokenManager
{
private Dictionary<string, string> tokensAndSecrets = new Dictionary<string, string>();
public InMemoryTokenManager(string consumerKey, string consumerSecret)
{
if (string.IsNullOrEmpty(consumerKey))
{
throw new ArgumentNullException("consumerKey");
}
this.ConsumerKey = consumerKey;
this.ConsumerSecret = consumerSecret;
}
public string ConsumerKey { get; private set; }
public string ConsumerSecret { get; private set; }
public string GetTokenSecret(string token)
{
return this.tokensAndSecrets[token];
}
public void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response)
{
this.tokensAndSecrets[response.Token] = response.TokenSecret;
}
public void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken,
string accessTokenSecret)
{
this.tokensAndSecrets.Remove(requestToken);
this.tokensAndSecrets[accessToken] = accessTokenSecret;
}
public TokenType GetTokenType(string token)
{
throw new NotImplementedException();
}
}
}
Your actual question should be something like 'Is it possible to use DotNetOpenAuth to sign requests with or without access token?", to answer that question I should say I don't know and even I can't find it out by reading DotNetOpenAuth codebase.
There is no single page of documentation available for DotNetOpenAuth and the codebase is so huge that you can't read it and understand what is supported by it or not.
I guess making non-authenticated request is not an issue as it is simply a query string parameter added to your request.
But to make signed requests you need to follow a simple process:
Collecting request parameters
Calculating signature
Making request(signed/protected)
Collecting request parameters
These are basically two categories of parameters, oauth specific parameters and Netflix API specific parameters.
Among the OAuth specific parameters is nonce, this is the code in which you can use to generate a nonce value:
public static string GenerateNonce()
{
byte[] bytes = new byte[32];
var first = Guid.NewGuid().ToByteArray();
var second = Guid.NewGuid().ToByteArray();
for (var i = 0; i < 16; i++)
bytes[i] = first[i];
for (var i = 16; i < 32; i++)
bytes[i] = second[i - 16];
var result = Convert.ToBase64String(bytes, Base64FormattingOptions.None);
result = new string(result.ToCharArray().Where(char.IsLetter).ToArray());
return result;
}
And another OAuth specific parameter is timestamp, this is the code in which you can use to calculate timestamp:
DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds
Other oauth specific parameters are easy to provision and no need to write a specific code for them.
API specific parameters are any value you add to query string or to the authorization headers(except the oauth_signature itself) or to the body request(if request content type is application/x-www-form-urlencoded).
Calculating signature
To make either a signed request or a protected signature you need to calculate a signature, which the process is almost the same, except the way that you create signing key:
Calculate signature base string
Calculate signing key
Creating the signature by signing the signature base string using signing key
To calculate signature base string you need to first concatenate all parameters into a string and the percent encode the whole string. This is the code which helps you doing percent encoding:
public static string Encode(string source)
{
Func<char, string> encodeCharacter = c => {
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '.' || c == '-' || c == '_' || c == '~'))
return new string(c, 1);
return EncodeCharacter(c);
};
return string.Concat(source.ToCharArray().Select(encodeCharacter));
}
Also you need to sort parameters in alphabetical order and be concatenated using '&'. Here is the code which you may have to write to do this:
public static string CalculateParameterString(KeyValuePair<string, string>[] parameters)
{
var q = from entry in parameters
let encodedkey = PercentEncode.Encode(entry.Key)
let encodedValue = PercentEncode.Encode(entry.Value)
let encodedEntry = encodedkey + "=" + encodedValue
orderby encodedEntry
select encodedEntry;
var result = string.Join("&", q.ToArray());
return result;
}
Lets call the above string 'parameters string'. Then to calculate signature base string all you need is to concatenate http verb of your request, your request's url and parameters string together using '&'. Also you need to percent encode them first. Here is the code which does that:
public static string CalcualteSignatureBaseString(string httpMethod, string baseUri, string parametersString)
{
return httpMethod.ToUpper() + "&" + PercentEncode.Encode(baseUri) + "&" + PercentEncode.Encode(parametersString);
}
Once you have created signature base string then you the next step is to calculate signing key.
If you just need to make a signed request, then you create signing key based on your consumer key(shared secret) only. This the signing key to be used to make a signed request.
During authorization process, if you just made a request token request and recieved a temporary oauth token, then your singing key is based on your consumer key and that oauth token. This is the signing key used to make request to get the access token.
If a user authorized your application and you have the relevant access token, then your signing key would be your consumer key and access token. This is the signing key to make a protected request.
This is the code that will generate the signing key:
public static string GetSigningKey(string ConsumerSecret, string OAuthTokenSecret = null)
{
return ConsumerSecret + "&" + (OAuthTokenSecret != null ? OAuthTokenSecret : "");
}
In your case, to make a signed request, you just need pass null value for OAuthTokenSecret parameter.
Ok, now you have a signature base string, all you need to do now is to sign using HMAC-SHA1 algorithm:
public static string Sign(string signatureBaseString, string signingKey)
{
var keyBytes = System.Text.Encoding.ASCII.GetBytes(signingKey);
using (var myhmacsha1 = new System.Security.Cryptography.HMACSHA1(keyBytes)) {
byte[] byteArray = System.Text.Encoding.ASCII.GetBytes(signatureBaseString);
var stream = new MemoryStream(byteArray);
var signedValue = myhmacsha1.ComputeHash(stream);
var result = Convert.ToBase64String(signedValue, Base64FormattingOptions.None);
return result;
}
}
To sum up this is the whole process for calculating signature:
public virtual string GetSignature(string consumerSecret, string tokenSecret, string uri, string method, params ParameterSet[] parameters)
{
var allParameters = parameters.SelectMany(p => p.ToList()).ToArray();
var parametersString = CalculateSignatureBaseString(allParameters);
var signatureBaseString = OAuth1aUtil.CalcualteSignatureBaseString(method, uri, parametersString);
var sigingKey = GetSigningKey(consumerSecret, tokenSecret);
var signature = Sign(signatureBaseString, sigingKey);
return signature;
}
Making request
Now you just need to make a valid http request and add the oauth parameters to the request as the 'Authorization' header.