Facebook data deletion callback gives "Unable to confirm request was received" - c#

I just convert PHP sample code to C#. This is my endpoint:
public async Task<IHttpActionResult> Delete([FromBody] FbModel fbModel)
{
var res = fbModel.signed_request.Split(new char[] { '.' }, 2);
var secret = "ababababababababababababa";
if (res.Length > 1)
{
var sig = res[0];
var json = base64decode(res[1]);
var data = Newtonsoft.Json.JsonConvert.DeserializeObject<FacebookDeletionDto>(json);
if (string.IsNullOrEmpty(data.algorithm) || data.algorithm.ToUpper() != "HMAC-SHA256")
throw new Exception("Unknown algorithm:" + data.algorithm + ". Expected HMAC-SHA256");
var expected_sig = hmacSHA256(res[1], secret);
if (expected_sig != sig)
throw new Exception("Invalid signature:" + sig + ". Expected" + expected_sig);
var returnJson = new { Url = $"https://myperfectsite.com/fb/info/{data.user_id}", confirmation_code = $"{data.user_id}" };
return Ok(returnJson);
}
return null;
}
This code running perfectly and gives me json.
My endpoint return URL and confirmation code in JSON. But in facebook confirmation page it gives me this error :"Unable to confirm request was received
"App name" sent an invalid response to your request. Contact "App name" directly to request it delete info it has about you."

Facebook provides only the following info:
Return a JSON response that contains a URL where the user can check the status
of their deletion request and an alphanumeric confirmation code.
The JSON response has the following form:
{ url: '<url>', confirmation_code: '<code>' }
I ran into the same problem where FB would not accept our server response.
We ultimately fixed the problem by outputting the JSON response in EXACTLY the same sample format. Property names lowercase and not quoted, single quotes around the values, and a value that was alphanumeric (no symbols) and not too long (20 char length worked).
EDIT: Looking back at it, looks like we also used a dynamic type for the response.
dynamic response = new ExpandoObject();
response.url = "<url>";
response.confirmation_code = "<code>";
return response;

I notice that while in facebook documentation "expected_sig" must be equal to "sig" which must be the result of base64_url decoding of $encoded_sig in your code "expected_sig" is compared to the sign not decoded.
Hope this could be helpful.
var sig = res[0];
var json = base64decode(res[1]);
...
if (expected_sig != sig)
throw new Exception(...)
Correct should be as follow:
var sig = base64decode(res[0]);
var json = base64decode(res[1]);
...
if (expected_sig != sig)
throw new Exception(...)

Instead of:
Delete([FromBody] FbModel fbModel)
I use:
string signed_request = Request.Form["signed_request"];
Here is my working code:
public async Task<IActionResult> Delete()
{
string signed_request = Request.Form["signed_request"];
if (!String.IsNullOrEmpty(signed_request))
{
string[] split = signed_request.Split('.');
string signatureRaw = base64decode(split[0]);
string dataRaw = base64decode(split[1]);
// the decoded signature
byte[] signature = Convert.FromBase64String(signatureRaw);
byte[] dataBuffer = Convert.FromBase64String(dataRaw);
// JSON object
var json = Encoding.UTF8.GetString(dataBuffer);
byte[] appSecretBytes = Encoding.UTF8.GetBytes("SecretKey");
System.Security.Cryptography.HMAC hmac = new System.Security.Cryptography.HMACSHA256(appSecretBytes);
byte[] expectedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(split[1]));
if (!expectedHash.SequenceEqual(signature))
{
throw new Exception("Invalid signature");
}
var fbUser = JsonConvert.DeserializeObject<FacebookUserDTO>(json);
return Ok(new { url = $"https://myperfectsite.com/fb/info/{fbUser.user_id}", confirmation_code = $"{fbUser.user_id}" });
}
}

Related

Azure Storage Rest API (Put Blob API)

I am trying to put a blob with Azure rest api. I made a "GET" request successfully but i had issues with "PUT" request. When I try to make "PUT" request i get a 403 error(Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.). I have seen same post in stackoverflow but it didn't help me. Any suggestions?
string uri = string.Format("https://{0}.blob.core.windows.net/{1}/LibraryForm.png", storageAccountName,containerName);
Byte[] requestPayload = File.ReadAllBytes(imagepath);
using (var httpRequestMessage = new HttpRequestMessage(HttpMethod.Put, uri)
{ Content = (requestPayload == null) ? null : new ByteArrayContent(requestPayload) })
{
// Add the request headers for x-ms-date and x-ms-version.
DateTime now = DateTime.UtcNow;
httpRequestMessage.Headers.Add("x-ms-date", now.ToString("R", CultureInfo.InvariantCulture));
httpRequestMessage.Headers.Add("x-ms-version", "2020-06-12");
httpRequestMessage.Headers.Add("x-ms-blob-type", "BlockBlob");
httpRequestMessage.Headers.Add("x-ms-blob-content-type", "image/png");
// Add the authorization header.
httpRequestMessage.Headers.Authorization = GetAuthorizationHeader(
storageAccountName, storageAccountKey, now, httpRequestMessage);
// Send the request.
using (HttpResponseMessage httpResponseMessage = await new HttpClient().SendAsync(httpRequestMessage))
{
if (httpResponseMessage.StatusCode == HttpStatusCode.OK)
{
var str = httpResponseMessage.Content.ReadAsStringAsync().Result;
return str;
}
}
}
internal static AuthenticationHeaderValue GetAuthorizationHeader(string storageAccountName, string storageAccountKey, DateTime now,
HttpRequestMessage httpRequestMessage, string ifMatch = "", string md5 = "")
{
// This is the raw representation of the message signature.
HttpMethod method = httpRequestMessage.Method;
String MessageSignature = String.Format("{0}\n\n\n{1}\n{5}\n\n\n\n{2}\n\n\n\n{3}{4}",
method.ToString(),
(method == HttpMethod.Get || method == HttpMethod.Head) ? String.Empty
: httpRequestMessage.Content.Headers.ContentLength.ToString(),
ifMatch,
GetCanonicalizedHeaders(httpRequestMessage),
GetCanonicalizedResource(httpRequestMessage.RequestUri, storageAccountName),
md5);
// Now turn it into a byte array.
byte[] SignatureBytes = Encoding.UTF8.GetBytes(MessageSignature);
// Create the HMACSHA256 version of the storage key.
HMACSHA256 SHA256 = new HMACSHA256(Convert.FromBase64String(storageAccountKey));
// Compute the hash of the SignatureBytes and convert it to a base64 string.
string signature = Convert.ToBase64String(SHA256.ComputeHash(SignatureBytes));
// This is the actual header that will be added to the list of request headers.
// You can stop the code here and look at the value of 'authHV' before it is returned.
AuthenticationHeaderValue authHV = new AuthenticationHeaderValue("SharedKey",
storageAccountName + ":" + Convert.ToBase64String(SHA256.ComputeHash(SignatureBytes)));
return authHV;
}
private static string GetCanonicalizedHeaders(HttpRequestMessage httpRequestMessage)
{
var headers = from kvp in httpRequestMessage.Headers
where kvp.Key.StartsWith("x-ms-", StringComparison.OrdinalIgnoreCase)
orderby kvp.Key
select new { Key = kvp.Key.ToLowerInvariant(), kvp.Value };
StringBuilder sb = new StringBuilder();
// Create the string in the right format; this is what makes the headers "canonicalized" --
// it means put in a standard format. http://en.wikipedia.org/wiki/Canonicalization
foreach (var kvp in headers)
{
StringBuilder headerBuilder = new StringBuilder(kvp.Key);
char separator = ':';
// Get the value for each header, strip out \r\n if found, then append it with the key.
foreach (string headerValues in kvp.Value)
{
string trimmedValue = headerValues.TrimStart().Replace("\r\n", String.Empty);
headerBuilder.Append(separator).Append(trimmedValue);
// Set this to a comma; this will only be used
// if there are multiple values for one of the headers.
separator = ',';
}
sb.Append(headerBuilder.ToString()).Append("\n");
}
return sb.ToString();
}
private static string GetCanonicalizedResource(Uri address, string storageAccountName)
{
StringBuilder sb = new StringBuilder("/").Append(storageAccountName).Append(address.AbsolutePath);
// It will have more entries if you have more query parameters.
NameValueCollection values = HttpUtility.ParseQueryString(address.Query);
foreach (var item in values.AllKeys.OrderBy(k => k))
{
sb.Append('\n').Append(item).Append(':').Append(values[item]);
}
return sb.ToString().ToLower();
}
If you want to upload a file to Azure Blob with rest API, please refer to the fooliwng code
I define a class to get ShareKey
internal static class AzureStorageAuthenticationHelper
{
internal static AuthenticationHeaderValue GetAuthorizationHeader(
string storageAccountName, string storageAccountKey, DateTime now,
HttpRequestMessage httpRequestMessage, string ifMatch = "", string md5 = "")
{
// This is the raw representation of the message signature.
HttpMethod method = httpRequestMessage.Method;
String MessageSignature = String.Format("{0}\n\n\n{1}\n{5}\n\n\n\n{2}\n\n\n\n{3}{4}",
method.ToString(),
(method == HttpMethod.Get || method == HttpMethod.Head) ? String.Empty
: httpRequestMessage.Content.Headers.ContentLength.ToString(),
ifMatch,
GetCanonicalizedHeaders(httpRequestMessage),
GetCanonicalizedResource(httpRequestMessage.RequestUri, storageAccountName),
md5);
// Now turn it into a byte array.
byte[] SignatureBytes = Encoding.UTF8.GetBytes(MessageSignature);
// Create the HMACSHA256 version of the storage key.
HMACSHA256 SHA256 = new HMACSHA256(Convert.FromBase64String(storageAccountKey));
// Compute the hash of the SignatureBytes and convert it to a base64 string.
string signature = Convert.ToBase64String(SHA256.ComputeHash(SignatureBytes));
// This is the actual header that will be added to the list of request headers.
// You can stop the code here and look at the value of 'authHV' before it is returned.
AuthenticationHeaderValue authHV = new AuthenticationHeaderValue("SharedKey",
storageAccountName + ":" + Convert.ToBase64String(SHA256.ComputeHash(SignatureBytes)));
return authHV;
}
private static string GetCanonicalizedHeaders(HttpRequestMessage httpRequestMessage)
{
var headers = from kvp in httpRequestMessage.Headers
where kvp.Key.StartsWith("x-ms-", StringComparison.OrdinalIgnoreCase)
orderby kvp.Key
select new { Key = kvp.Key.ToLowerInvariant(), kvp.Value };
StringBuilder sb = new StringBuilder();
// Create the string in the right format; this is what makes the headers "canonicalized" --
// it means put in a standard format. http://en.wikipedia.org/wiki/Canonicalization
foreach (var kvp in headers)
{
StringBuilder headerBuilder = new StringBuilder(kvp.Key);
char separator = ':';
// Get the value for each header, strip out \r\n if found, then append it with the key.
foreach (string headerValues in kvp.Value)
{
string trimmedValue = headerValues.TrimStart().Replace("\r\n", String.Empty);
headerBuilder.Append(separator).Append(trimmedValue);
// Set this to a comma; this will only be used
// if there are multiple values for one of the headers.
separator = ',';
}
sb.Append(headerBuilder.ToString()).Append("\n");
}
return sb.ToString();
}
private static string GetCanonicalizedResource(Uri address, string storageAccountName)
{
// The absolute path is "/" because for we're getting a list of containers.
StringBuilder sb = new StringBuilder("/").Append(storageAccountName).Append(address.AbsolutePath);
// Address.Query is the resource, such as "?comp=list".
// This ends up with a NameValueCollection with 1 entry having key=comp, value=list.
// It will have more entries if you have more query parameters.
NameValueCollection values = HttpUtility.ParseQueryString(address.Query);
foreach (var item in values.AllKeys.OrderBy(k => k))
{
sb.Append('\n').Append(item).Append(':').Append(values[item]);
}
return sb.ToString().ToLower();
}
}
Uplaod
FileInfo fileInfo = new FileInfo("D:\\sampleData\\readsample.jpg");
string blobName = fileInfo.Name;
string contentType = MimeMapping.GetMimeMapping(blobName);
DateTime now = DateTime.UtcNow;
string blobURI = string.Format("https://{0}.blob.core.windows.net/{1}/{2}", StorageAccountName, "test", blobName);
using (var httpRequestMessage = new HttpRequestMessage(HttpMethod.Put, blobURI))
{
httpRequestMessage.Headers.Add("x-ms-date", now.ToString("R", CultureInfo.InvariantCulture));
httpRequestMessage.Headers.Add("x-ms-version", "2020-06-12");
httpRequestMessage.Headers.Add("x-ms-blob-type", "BlockBlob");
httpRequestMessage.Headers.Add("x-ms-blob-content-type", contentType);
httpRequestMessage.Content = new StreamContent(fileInfo.OpenRead());
httpRequestMessage.Headers.Authorization = AzureStorageAuthenticationHelper.GetAuthorizationHeader(
StorageAccountName, StorageAccountKey, now, httpRequestMessage);
// Send the request.
using (HttpResponseMessage httpResponseMessage = await new HttpClient().SendAsync(httpRequestMessage))
{
// If successful (status code = 200),
// parse the XML response for the container names.
if (httpResponseMessage.StatusCode == HttpStatusCode.Created)
{
Console.WriteLine("OK");
}
}
}
Besides, Using Azure SDK to implement upload progress is a simple way. Regarding how to use azure sdk, please refer to here.

Bad device token apple push notification

I have stuck in apple push notification. Following the document in https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html#//apple_ref/doc/uid/TP40008194-CH11-SW1 I created the header and payload with the private key to generate token, but after call api: https://api.development.push.apple.com:443/3/device/, It told bad device token. Check in jwt.io it said invalid token.
Anyone know this problem or idea.
Thank you !
Here is the code .net core:
var header = JsonConvert.SerializeObject(new { alg = "ES256", kid = keyId });
var payload = JsonConvert.SerializeObject(new { iss = teamId, iat = ToEpoch(DateTime.UtcNow) });
var key = CngKey.Import(Convert.FromBase64String(p8privateKey), CngKeyBlobFormat.Pkcs8PrivateBlob);
using (ECDsaCng dsa = new ECDsaCng(key))
{
dsa.HashAlgorithm = CngAlgorithm.Sha256;
var headerBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(header));
var payloadBasae64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(payload));
var unsignedJwtData = System.Convert.ToBase64String(Encoding.UTF8.GetBytes(header)) + "." + System.Convert.ToBase64String(Encoding.UTF8.GetBytes(payload));
var unsignedJwtDataBytes = Encoding.UTF8.GetBytes(unsignedJwtData);
var signature =
dsa.SignData(unsignedJwtDataBytes);
return unsignedJwtData + "." + System.Convert.ToBase64String(signature);
}
}

Google API - Gmail. Downloading attachments

When calling this method please help by advising what values to pass through such as GmailService? Im guessing userID is the Gmail account and the messageID( i want to download all of them) .
How can i change this to download all the attachments in the inbox.
Thank you in advance and I hope someone can help me.
Method im using is below.
public static void GetAttachments(GmailService service, String userId, String messageId, String outputDir)
{
try
{
Message message = service.Users.Messages.Get(userId, messageId).Execute();
IList<MessagePart> parts = message.Payload.Parts;
foreach (MessagePart part in parts)
{
if (!String.IsNullOrEmpty(part.Filename))
{
String attId = part.Body.AttachmentId;
MessagePartBody attachPart = service.Users.Messages.Attachments.Get(userId, messageId, attId).Execute();
// Converting from RFC 4648 base64 to base64url encoding
// see http://en.wikipedia.org/wiki/Base64#Implementations_and_history
String attachData = attachPart.Data.Replace('-', '+');
attachData = attachData.Replace('_', '/');
byte[] data = Convert.FromBase64String(attachData);
File.WriteAllBytes(Path.Combine(outputDir, part.Filename), data);
}
}
}
catch (Exception e)
{
Console.WriteLine("An error occurred: " + e.Message);
}
}
I think the answer to your question can be solved using Get in PostMan
In your header in PostMan, use key as Authorization and pass your token generated to it as value
or goto Authorization and pick bearer token and pass your token.
Note that messageId is the Id in string of the message you are trying to fetch and {userId} =me or user according to google and I believe you can fetch Attachment Id by the method you use above.
https://www.googleapis.com/gmail/v1/users/me/messages/messageId/attachments/id
public async Task<TResult> GetGmailInboxAttachmentById<TResult>(string messageId, string token, string id, string attachId)
{
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var url = "https://www.googleapis.com/gmail/v1/users/me/messages/" + messageId + "/" + id + "/attachments" + "/" + attachId;
HttpResponseMessage response = await httpClient.GetAsync(url);
return await response.Content.ReadAsAsync<TResult>();
}
}
foreach (var part in inbox.Payload.Parts)
{
if (!String.IsNullOrEmpty(part.Filename))
{
var attachId = part.Body.AttachmentId;
var attach = _gmailService.GetGmailInboxAttachmentById<MessagePartBody>(id, token, part.PartId, attachId).Result;
// Converting from RFC 4648 base64 to base64url encoding
// see http://en.wikipedia.org/wiki/Base64#Implementations_and_history
string attachData = attach.Data.Replace('_', '+');
attachData = attachData.Replace('_', '/');
byte[] data = Convert.FromBase64String(attachData);
string file = Convert.ToBase64String(data);
GetAttach.Add(file);
}
}
Hope this solve it for you because I solved it with this method
I know, it's could be an off-topic, but using PostMan in case when programmer use Google API is little but contraproductive :) APIs also have attachments.Get Method, and you will not need to call HTTP request and allow non OAuth 2.0 authentication especially when you're using multi-phase authentication.
Here is example:
https://developers.google.com/gmail/api/v1/reference/users/messages/attachments/get
foreach (var part in m.Payload.Parts)
{
if (!string.IsNullOrEmpty(part.Filename))
{
var attachId = part.Body.AttachmentId;
MessagePartBody attachPart = service.Users.Messages.Attachments.Get(userId,
message.Id,
attachId).Execute();
byte[] data = GetBytesFromPart(attachPart.Data);
File.WriteAllBytes(Path.Combine(#"c:\teste\",
$"{DateTime.Now:yyyyMMddHHmmss}-{part.Filename}"), data);
}
}
private static string DecodedString(string messagePart)
{
try
{
var data = GetBytesFromPart(messagePart);
string decodedString = Encoding.UTF8.GetString(data);
return decodedString;
}
catch (System.Exception e)
{
// ignored
return string.Empty;
}
}
private static byte[] GetBytesFromPart(string messagePart)
{
var attachData = messagePart.Replace('-', '+');
attachData = attachData.Replace('_', '/');
byte[] data = Convert.FromBase64String(attachData);
return data;
}

Error: "response":"X006","description":"Invalid hash"

I made a call with Restsharp with the following code in Gridview row command in ASP.net C#:
const string task = "pay";
const string command_api_token = "D3Kc4vMatqQ7pQtU39D22j35aKqy8";
const string merchant_email_on_voguepay = "abc#wyz.com";
Random rnd = new Random();
string refl = DateTime.Now + rnd.Next(1, 9999999).ToString();
byte[] hash_target = Encoding.UTF8.GetBytes(command_api_token + task + merchant_email_on_voguepay + refl);
string hash = BitConverter.ToString(new SHA512CryptoServiceProvider().ComputeHash(hash_target)).Replace("-", string.Empty).ToUpper();
// //load all fields as json and serialize
var keyValues = new Dictionary<string, string>
{
{ "task", "pay"},
{ "merchant", "1234-4567"},
{ "ref",refl},
{ "hash",hash},
{ "remarks", "secure trade"},
{ "seller", "seller#website.com"},
{ "cur", "usd"},
{ "amount", "20"}
};
//serialization using Newtonsoft JSON
string json = JsonConvert.SerializeObject(keyValues);
//url encode the json
var postString = HttpUtility.UrlEncode(json);
//calling API with Restsharp
var client = new RestClient("https://voguepay.com/api/");
var request = new RestRequest(Method.POST);
request.AddParameter("json", json);
IRestResponse response = client.Execute(request);
the call returns the following error when I use the real parameters for email_on_voguepay and command_api_token.
this is what it returns:
{"response":"X006","description":"Invalid hash","values":"","salt":"58656ba1d162a","hash":"2638fb45f266d35bc78524490a3329a3fecf7b5189c473e82e45d7d6a22f07656743e2cb9ae28dfc81e77eef49eb24d46e0fd4a71cab16847a7afd225310feb5","username":"MyName"}
Please I need help here, what did I do wrong and how do I correct that?
Seems to be your request has a invalid hash (token). I suggest you to check the string refl = DateTime.Now + rnd.Next(1, 9999999).ToString();
The time function in php (according with voguePay API, returns the unix timestamp.
So in C# please consider using DateTimeOffset.ToUnixTimeSeconds or (DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds
For more info, please consider checking the generated token was following instructions:
Required Security code generated by concatenating the following and passing the result through a sha512 encryption
your command api token
task specified above
your voguepay email
ref specified above
ref Unique id for each request (Required) $field['ref'] = time().mt_rand(0,999999999);
https://voguepay.com/developers

Google OAuth2 Service Account Access Token Request gives 'Invalid Request' Response

I'm trying to communicate with my app's enabled BigQuery API via the server to server method.
I've ticked all the boxes on this Google guide for constructing my JWT as best I can in C#.
And I've Base64Url encoded everything that was necessary.
However, the only response I get from google is a 400 Bad Request
"error" : "invalid_request"
I've made sure of all of the following from these other SO questions:
The signature is properly encrypted using RSA and SHA256
I am using POST and using application/x-www-form-urlencoded content type
Escaped all the backslashes in the claim set
Tried various grant_type and assertion values in the POST data
I get the same result when I use Fiddler. The error message is frustratingly lacking in detail! What else can I try?! Here's my code:
class Program
{
static void Main(string[] args)
{
// certificate
var certificate = new X509Certificate2(#"<Path to my certificate>.p12", "notasecret");
// header
var header = new { typ = "JWT", alg = "RS256" };
// claimset
var times = GetExpiryAndIssueDate();
var claimset = new
{
iss = "<email address of the client id of my app>",
scope = "https://www.googleapis.com/auth/bigquery",
aud = "https://accounts.google.com/o/oauth2/token",
iat = times[0],
exp = times[1],
};
// encoded header
var headerSerialized = JsonConvert.SerializeObject(header);
var headerBytes = Encoding.UTF8.GetBytes(headerSerialized);
var headerEncoded = Base64UrlEncode(headerBytes);
// encoded claimset
var claimsetSerialized = JsonConvert.SerializeObject(claimset);
var claimsetBytes = Encoding.UTF8.GetBytes(claimsetSerialized);
var claimsetEncoded = Base64UrlEncode(claimsetBytes);
// input
var input = headerEncoded + "." + claimsetEncoded;
var inputBytes = Encoding.UTF8.GetBytes(input);
// signiture
var rsa = certificate.PrivateKey as RSACryptoServiceProvider;
var cspParam = new CspParameters
{
KeyContainerName = rsa.CspKeyContainerInfo.KeyContainerName,
KeyNumber = rsa.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2
};
var aescsp = new RSACryptoServiceProvider(cspParam) { PersistKeyInCsp = false };
var signatureBytes = aescsp.SignData(inputBytes, "SHA256");
var signatureEncoded = Base64UrlEncode(signatureBytes);
// jwt
var jwt = headerEncoded + "." + claimsetEncoded + "." + signatureEncoded;
Console.WriteLine(jwt);
var client = new HttpClient();
var uri = "https://accounts.google.com/o/oauth2/token";
var post = new Dictionary<string, string>
{
{"assertion", jwt},
{"grant_type", "urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer"}
};
var content = new FormUrlEncodedContent(post);
var result = client.PostAsync(uri, content).Result;
Console.WriteLine(result);
Console.WriteLine(result.Content.ReadAsStringAsync().Result);
Console.ReadLine();
}
private static int[] GetExpiryAndIssueDate()
{
var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var issueTime = DateTime.Now;
var iat = (int)issueTime.Subtract(utc0).TotalSeconds;
var exp = (int)issueTime.AddMinutes(55).Subtract(utc0).TotalSeconds;
return new[]{iat, exp};
}
private static string Base64UrlEncode(byte[] input)
{
var output = Convert.ToBase64String(input);
output = output.Split('=')[0]; // Remove any trailing '='s
output = output.Replace('+', '-'); // 62nd char of encoding
output = output.Replace('/', '_'); // 63rd char of encoding
return output;
}
}
Looks like my guess in the comment above was correct. I got your code working by changing:
"urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer"
to:
"urn:ietf:params:oauth:grant-type:jwt-bearer"
Looks like you were accidentally double-encoding it.
I now get a response which looks something like:
{
"access_token" : "1/_5pUwJZs9a545HSeXXXXXuNGITp1XtHhZXXxxyyaacqkbc",
"token_type" : "Bearer",
"expires_in" : 3600
}
Edited Note: please make sure to have the correct date/time/timezone/dst configuration on your server. Having the clock off by even a few seconds will result in an invalid_grant error. http://www.time.gov will give the official time from the US govt, including in UTC.
Be sure to use DateTime.UtcNow instead of DateTime.Now in the GetExpiryAndIssueDate method.

Categories

Resources