12

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:

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; } } 
2
  • 1
    I'm not finding anything blatantly obvious, and I've stepped through each line of your code. One thing might be that you're encoding the grant type in your dictionary, and FormUrlEncodededContent may end up double-encoding it. So, I would try "urn:ietf:params:oauth:grant-type:jwt-bearer" instead. Commented Aug 13, 2012 at 19:14
  • 1
    Looks like HttpClient is from a very recent .NET framework release, so I'm installing that, and trying out the code directly as well. I've also reached out internally to a few folks who might be able to help. Commented Aug 13, 2012 at 19:15

2 Answers 2

12

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.

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

Comments

6

Be sure to use DateTime.UtcNow instead of DateTime.Now in the GetExpiryAndIssueDate method.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.