I am trying to use the Vimeo API and I have been having some problems generating the signature....
Basically I have this class
public class OAuthParameters
{
public string RedirectUrl { get; set; }
public string ClientId { get; set; }
public string ClientSecret { get; set; }
protected string NormalizeParameters(SortedDictionary<string, string> parameters)
{
StringBuilder sb = new StringBuilder();
var i = 0;
foreach (var parameter in parameters)
{
if (i > 0)
sb.Append("&");
sb.AppendFormat("{0}={1}", parameter.Key, parameter.Value);
i++;
}
return sb.ToString();
}
private string GenerateBase(string nonce, string timeStamp, Uri url)
{
var parameters = new SortedDictionary<string, string>
{
{"oauth_consumer_key", ClientId},
{"oauth_signature_method", "HMAC-SHA1"},
{"oauth_timestamp", timeStamp},
{"oauth_nonce", nonce},
{"oauth_version", "1.0"}
};
var sb = new StringBuilder();
sb.Append("GET");
sb.Append("&" + Uri.EscapeDataString(url.AbsoluteUri));
sb.Append("&" + Uri.EscapeDataString(NormalizeParameters(parameters)));
return sb.ToString();
}
public string GenerateSignature(string nonce, string timeStamp, Uri url)
{
var signatureBase = GenerateBase(nonce, timeStamp, url);
var signatureKey = string.Format("{0}&{1}", ClientId, "");
var hmac = new HMACSHA1(Encoding.ASCII.GetBytes(signatureKey));
return Convert.ToBase64String(hmac.ComputeHash(new ASCIIEncoding().GetBytes(signatureBase)));
}
}
The code that calls this is in another class and it has this method
public string GetAuthorizationUrl(string Url)
{
var sb = new StringBuilder();
var nonce = Guid.NewGuid().ToString();
var timeStamp = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds.ToString();
var signature = parameters.GenerateSignature(nonce, timeStamp, new Uri(Url));
sb.Append(GenerateQueryStringOperator(sb.ToString()) + "oauth_consumer_key=" + Uri.EscapeDataString(parameters.ClientId));
sb.Append("&oauth_nonce=" + Uri.EscapeDataString(nonce));
sb.Append("&oauth_timestamp=" + Uri.EscapeDataString(timeStamp));
sb.Append("&oauth_signature_method=" + Uri.EscapeDataString("HMAC-SHA1"));
sb.Append("&oauth_version=" + Uri.EscapeDataString("1.0"));
sb.Append("&oauth_signature=" + Uri.EscapeDataString(signature));
return Url + sb.ToString();
}
private string GenerateQueryStringOperator(string currentUrl)
{
if (currentUrl.Contains("?"))
return "&";
else
return "?";
}
the Url I am passing (to get my auth token) is https://vimeo.com/oauth/request_token but whenever I call this I get an error stating
401 Unauthorized - Invalid signature - The oauth_signature passed was not valid.
This is driving me nuts. I can't see what could be causing the issue.
If anyone can give me some help, that would be great.
Update 1
Someone stated that using Uri.EscapeDataString was not the correct way to encode my strings. I was using this function before I moved the EscapeDataString:
protected string UrlEncode(string unencodedString)
{
var encodedString = new StringBuilder();
var unreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
foreach (char symbol in unencodedString)
if (unreservedChars.IndexOf(symbol) != -1)
encodedString.Append(symbol);
else
encodedString.Append('%' + String.Format("{0:X2}", (int)symbol));
return encodedString.ToString();
}
but this still not help. Also using http://hueniverse.com/oauth/guide/authentication/ did not provide any insights into what is causing my issue :(
Update 2
So, I ran the code through hueniverse and found that my timestamp was not exact (it had decimal places) so I changed the way I generate my time stamp using this function:
public string GenerateTimeStamp()
{
TimeSpan ts = DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0, 0));
string timeStamp = ts.TotalSeconds.ToString();
timeStamp = timeStamp.Substring(0, timeStamp.IndexOf("."));
return timeStamp;
}
So now when I look at my base signature I get:
POST&https%3A%2F%2Fvimeo.com%2Foauth%2Frequest_token%2F&oauth_consumer_key%3Dmykey%26oauth_nonce%3D66c80d6b-9ff6-404b-981b-56f40f356a31%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1370444366%26oauth_token%3D%26oauth_version%3D1.0
and if I compare it to the one generated from hueniverse, it generates:
POST&https%3A%2F%2Fvimeo.com%2Foauth%2Frequest_token%2F&oauth_consumer_key%3Dmykey%26oauth_nonce%3D00b81872-688b-4184-a978-206e5fbcc531%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1370446860%26oauth_token%3D%26oauth_version%3D1.0
If the length of the generated strings are anything to go by, then they are exact. My next test is to create it exactly the same timestamp and all to see if they generate the same signature....
The problem with the code is here.
In the GenerateSignature function, you are setting variable:
var signatureKey = string.Format("{0}&{1}", ClientId, "");
This should be:
var signatureKey = string.Format("{0}&{1}", ClientSecret , "");
The documentation for Uri.EscapeDataString states that it uses RFC 2396 by default (unless IRI or IDN parsing is enabled).
OAuth 1.0 requires that you encode your values using RFC 3986.
I recommend testing your output against the Hueniverse Guide. It is an incredibly helpful tool in debugging your oauth signatures, and should be much faster than performing actual API requests.
In the code above when you set the timeStamp:
var timeStamp = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds.ToString();
you will get a decimal timestamp, this will make your authorizedUrl not working
if you make truncate the result before converting it to string, this will work.
var timeStamp = Math.Truncate((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds).ToString();
Related
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();
}
For Create Registration on azure notification hub
We want authorization token for below rest api
https://{namespace}.servicebus.windows.net/{NotificationHub}/registrations/?api-version=2015-01
please see follwing link
https://msdn.microsoft.com/en-us/library/azure/dn223265.aspx
// Add Nuget package
Install-Package Microsoft.Azure.NotificationHubs
using Microsoft.Azure.NotificationHubs;//Add namespace
string accessTokenstring = GetAccessToken();
private string GetAccessToken()
{
var hubName = "<Notification Hub>";
string connectionString = "Endpoint=sb://<Notification Hub Namespace>.servicebus.windows.net/;SharedAccessKeyName=DefaultFullSharedAccessSignature;SharedAccessKey=<Key>";
var apiVersion = "?api-version=2015-01";
string endpoint = null;
string sasKeyValue = null;
string sasKeyName = null;
string targetUri = null;
var parts = connectionString.Split(';');
foreach (var part in parts)
{
if (part.IndexOf("Endpoint") == 0)
{
endpoint = "https" + part.Substring(11);
}
else if (part.IndexOf("SharedAccessKeyName") == 0)
{
sasKeyName = part.Substring(20);
}
else if (part.IndexOf("SharedAccessKey") == 0)
{
sasKeyValue = part.Substring(16);
}
}
targetUri = endpoint + hubName;
var registrationPath = targetUri + "/Registrations/";
var resourceUri = registrationPath + apiVersion;
TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
string accessTokenstring = SharedAccessSignatureTokenProvider.GetSharedAccessSignature(sasKeyName, sasKeyValue, resourceUri, sinceEpoch);
return accessTokenstring;
}
Trying to follow Direct Send (https://msdn.microsoft.com/en-us/library/mt608572.aspx) I faced the same problem.
I was able to use #Sama's solution just changing the "Registrations" to "messages". The Solution provided is working in .Net Core.
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;
}
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.