To support scenarios where an unattended application accesses Google data, Google introduced the concept of Service Accounts which allows for unattended log in using JWT (JSON Web Token).
Having fought with the somewhat incomplete documentation and code samples, I decided to summarize and explain the working code here for the benefit of all.
To use the sample code provided here you will need:
- A Google Account
- A Google Service Account client e-mail address which is obtained from the Google API Console. The client e-mail address looks like:
XXX@developer.gserviceaccount.com
- A Google Service Account private key file (privatekey.p12) which is also obtained from the Google API Console
- A C# compiler
For simplicity, I created a .NET class called GoogleJsonWebToken with a public static method GetAccessToken which performs the authentication:
public static dynamic GetAccessToken(string clientIdEMail, string keyFilePath, string scope)
The method returns a dictionary with the Google access token accessible by the key “access_token”:
var auth = GoogleJsonWebToken.GetAccessToken( clientIdEmail, keyFilePath, GoogleJsonWebToken.SCOPE_ANALYTICS_READONLY); Console.WriteLine("Google access token: {0}", auth["access_token"]);
This token can later be returned as part of the standard Google OAuth2 API GetAuthentication delegate result IAuthorizationState:
AuthorizationState result = new AuthorizationState(); result.AccessToken = auth["access_token"]; result.AccessTokenExpirationUtc = DateTime.UtcNow.AddMinutes(30); return result;
For your convenience I include the full source code of the GoogleJsonWebToken C# class:
using System; using System.Text; using System.Collections.Generic; using System.Collections.Specialized; using System.Web.Script.Serialization; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Net; public class GoogleJsonWebToken { public const string SCOPE_ANALYTICS_READONLY = "https://www.googleapis.com/auth/analytics.readonly"; public static dynamic GetAccessToken(string clientIdEMail, string keyFilePath, string scope) { // certificate var certificate = new X509Certificate2(keyFilePath, "notasecret"); // header var header = new { typ = "JWT", alg = "RS256" }; // claimset var times = GetExpiryAndIssueDate(); var claimset = new { iss = clientIdEMail, scope = scope, aud = "https://accounts.google.com/o/oauth2/token", iat = times[0], exp = times[1], }; JavaScriptSerializer ser = new JavaScriptSerializer(); // encoded header var headerSerialized = ser.Serialize(header); var headerBytes = Encoding.UTF8.GetBytes(headerSerialized); var headerEncoded = Convert.ToBase64String(headerBytes); // encoded claimset var claimsetSerialized = ser.Serialize(claimset); var claimsetBytes = Encoding.UTF8.GetBytes(claimsetSerialized); var claimsetEncoded = Convert.ToBase64String(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 = Convert.ToBase64String(signatureBytes); // jwt var jwt = headerEncoded + "." + claimsetEncoded + "." + signatureEncoded; var client = new WebClient(); client.Encoding = Encoding.UTF8; var uri = "https://accounts.google.com/o/oauth2/token"; var content = new NameValueCollection(); content["assertion"] = jwt; content["grant_type"] = "urn:ietf:params:oauth:grant-type:jwt-bearer"; string response = Encoding.UTF8.GetString(client.UploadValues(uri, "POST", content)); var result = ser.Deserialize<dynamic>(response); return result; } private static int[] GetExpiryAndIssueDate() { var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); var issueTime = DateTime.UtcNow; var iat = (int)issueTime.Subtract(utc0).TotalSeconds; var exp = (int)issueTime.AddMinutes(55).Subtract(utc0).TotalSeconds; return new[] { iat, exp }; } }
Very helpful, thanks.
The pleasure is mine, thanks for the feedback!
I just test it and it works perfect!!.. you saved me many headaches
Thanks a lot
Thanks! It’s great to know that my work is useful!
Thanks a bunch !!! After weeks of trying to make GAN work using service account, this worked perfectly.
Thank you for your feedback!
Do you have something for .net 2.? (The rest of this project is in .net 2)
Hi, I haven’t prepared anything specifically for .NET Framework 2, however – that should be fairly easy.
To make the source code .NET 2 compatible, one would have to change “var” declarations to their corresponding types, and use explicit .NET classes instead of dynamic declarations.
You would also have to use a 3rd party JSON serializer/deserializer.
I just stumbled upon this and I think this will be very useful to me. I’m trying to get it to work in my situation which is, through a SSIS package (C# script component) retrieve data from YouTube API, YouTube Analytics API and Google Analytics API and store the data in a database. My problem is that I am working with framework 3.5 not 4.0. What can I do about the dynamic keywords on line 21 and other one on line 80?
Hi, thank you for your feedback. The dynamic keywords could be replaced with actual strong-typed C# classes in your case, and you would also probably have to use a 3rd party JSON serialization/deserialization library.
Thank you very much for this code. Have been struggling off and on for 3 days trying to figure out how to access the google analytic stuff. Quick question – what type of value goes in scope? Is that a Google value?
Just began answering this and noticed that you’ve figured the values yourself. =)
Thanks to a link on Stack Overflow by Vineet2982, I was able to come up with a list of scopes. I’ve inlcuded the link here for anyone else that needs it.
https://developers.google.com/gdata/faq#AuthScopes
Thanks, this is wonderful! The more complete information we’ll have here – the more chance it will help someone!
I meant to say Vineet1982.
And the code works very well, so thank you so much for posting it.
It’s great to know my work is appreciated, thank you!
Do you have any code samples of how to then use the token to request the data from Google apis. I’m trying to access the Google webmaster tools and I’m getting a 401 unauthorized message.
I’ve implemented your code above and get the token I need but when I send that to Google in the HTTP header it’s not accepting it.
Thanks.
I haven’t tried accessing the Google Webmaster Tools specifically, but since you get an access token from the API, I would check either the authentication SCOPE value, or the Google webmaster tools API docs to make sure you perform the call correctly.
It also helps using a library for those tasks.
Yeah, I’ve looked into the library and at this point I’m going in circles trying to understand how to access the data I need. The webmasters tools instructions say you need an oauth token but it’s a Data API and there’s verbiage that makes me think that it needs accessed in a different way.
I’m on the .net Client library website, I’ll see if someone there has an example or can point me in the right direction.
Thanks.
The .NET client library (libraries?) usually has a GetAccessToken method implementation somewhere in the examples which is responsible for supplying the client library with the actual access token.
If you can’t figure out where to supply the token – that would be the first thing I would look for.
I suppose I could try and help you dissect a source sample.
By the way, you might be looking at the old version of the API (and the API client library) which is no longer working.
Well, that could be an issue. I just wish there was some very straight forward explanation on how to use this. Like I said I have gone round and round and I keep ending up on the same pages without a better understanding of how this works.
I don’t mean to bug you but I have no one else that has answered any questions. I’ve created the HTTP web request and I’m adding a header “authorization”, “GoogleLogin auth = “ + oauthToken. Is this the correct way to do this?
Thanks,
Carolyn
I’m in no way an authority on the way the Google API works, however:
1. Having to add an HTTP request header seems contradicting the RESTfulness of the Google API philosophy. I would expect either a query string parameter (for example: ?auth=oauthToken) or a JSON request (having the oauth token as one of the values)
2. You could use the Fiddler Web Debugger (http://www.fiddler2.com/fiddler2/) to try and see what a working application does (fiddler is great for analyzing HTTP traffic). The only trouble with this method is to find a working application.
And finally – 3. I’ve just checked the list of Google API services at the Google APIs Console (you can find the link in the original post), and there’s nothing which resembles “webmaster tools”, which leads me to the conclusion that you might be trying to use an API which no longer exists.
Have all the Google data apis been replaced? This is listed as a google data api (https://developers.google.com/gdata/docs/directory) which does say that some have been replaced. You would think they would tell you if it’s been replaced and replaced with what.
Thanks, I’ll keep searching for what I need.
Carolyn
I know for sure that GData APIs have been replaced for Google Analytics (the service I was writing the original class for), so it seems logical to assume that they brought all of their services to a common baseline over time.
Just curious: what is it that you are trying to do in terms of functionality (with no relation to any particular service or API)?
I’m trying to get a list of crawl errors. My supervisor has said to use the google webmaster tools to get this information. We are putting together a dashboard of web analytics for our clients and want to list issues with their sites so they can be fixed.
Is that available in the Google Analytics? My supervisor seemed to think we had to use the webmaster tools to get this data.
Thanks,
Carolyn
There’s a CSV export option in the webmaster tools – I believe it’s intended for manual use, however, there’s an interesting part in the URL – the [security_token] query string parameter.
https://www.google.com/webmasters/tools/crawl-errors-new-dl?hl=en&siteUrl=https://zavitax.wordpress.com/&authuser=0&security_token=%5BAUTH_TOKEN%5D&format=csv
The first thing I would do, is to assume the security token is the OAuth provided security token, and try to go ahead and use that (just replace siteUrl with your own website URL).
However, there’s a chance that Google Webmaster Tools feeds require the older ClientLogin or AuthSub systems to generate that security token.
There’s some code (I believe this is either Python or Ruby) which you can rely on when building your authentication client here:
https://github.com/rngtng/webmaster_tools/blob/master/lib/webmaster_tools.rb
It really seems overwhelming at first, but after a couple of minutes – comprehensible enough to translate this to C#.
You can also find documentation regarding authentication here:
https://developers.google.com/accounts/docs/GettingStarted
http://code.google.com/apis/gdata/docs/auth/overview.html
If the authentication token you get from OAuth2 does not work, then the latter is definitely the way to go, and I would love to hear more from you later on if you do succeed in automating this.
If you don’t – there’s always the “let the secretary do it” option, however, it does not make any sense for me to let a human do the computer’s job.
Good luck!
Thank you for the information. Got pulled into a meeting so haven’t had a chance to check out the reference but trying to do it with the url link gives me a 404 error not found.
The way I’m trying to do it gives a 401 not authorized.
I’ll take this up with my supervisor. I’ve got to get this done and there not being good documentation is a pain.
But I do appreciate the help.
Carolyn
Yes, I understand your frustration. Actually I’ve been able to produce an authentication token using documented means, and a security token using some undocumented hacking.
The security token does not seem to do any good (or maybe I am too lazy to think of more options), but you might be able to use this code as a boilerplate for further testing:
static void Main(string[] args)
{
string auth = Login(“google_account@gmail.com”, “your-password”);
if (auth != null)
{
string csv = DownloadCSV(auth, “https://zavitax.wordpress.com/”);
}
}
public static string DownloadCSV(string auth, string siteUrl)
{
string result = null;
string urlWithoutToken = string.Format(
“https://www.google.com/webmasters/tools/crawl-errors?hl=en&siteUrl={0}&format=csv”,
HttpUtility.UrlEncode(siteUrl)
);
WebClient client = new WebClient();
client.Encoding = Encoding.UTF8;
client.Headers.Add(“Authorization”, “GoogleLogin auth=” + auth);
client.Headers.Add(“GData-Version”, “2”);
string authResponse = client.DownloadString(urlWithoutToken);
Regex authParseTokenRegEx = new Regex(“wmt.common.globals.setHelpServiceApiaryKey\\s*\\(\\s*\”(.+?)\”\\s*\\)”, RegexOptions.Singleline);
if (authParseTokenRegEx.IsMatch(authResponse))
{
Match m = authParseTokenRegEx.Match(authResponse);
string security_token = m.Groups[1].Value;
Console.WriteLine(“{0}”, security_token);
string urlWithToken = string.Format(
“https://www.google.com/webmasters/tools/feeds/{0}/crawlissues/”,
HttpUtility.UrlEncode(siteUrl),
security_token
);
Console.Write(“{0}”, urlWithToken);
client = new WebClient();
client.Encoding = Encoding.UTF8;
client.Headers.Add(“Authorization”, “GoogleLogin auth=” + auth);
client.Headers.Add(“GData-Version”, “2”);
string content = client.DownloadString(urlWithToken);
Console.WriteLine(“{0}”, content);
//client.Headers.Add(“”)
}
return result;
}
public static string Login(string username, string password)
{
string result = null;
WebClient client = new WebClient();
client.Encoding = Encoding.UTF8;
NameValueCollection form = new NameValueCollection();
form[“accountType”] = “HOSTED_OR_GOOGLE”;
form[“service”] = “sitemaps”;
form[“Email”] = username;
form[“Passwd”] = password;
form[“source”] = “Google-WMTdownloadscript-0.1-php”;
byte[] data = client.UploadValues(“https://www.google.com/accounts/ClientLogin”, form);
string response = Encoding.UTF8.GetString(data);
string[] lines = response.Split(new char[] { ‘\r’, ‘\n’ }, StringSplitOptions.RemoveEmptyEntries);
foreach (string line in lines)
{
if (line.StartsWith(“Auth=”))
{
string[] kv = line.Split(new char[] { ‘=’ });
result = kv[1];
}
}
Console.WriteLine(“{0}”, response);
Console.WriteLine(“{0}”, result);
return result;
}
}
Thank you for taking the time to do this. I’m on contract and suppose to get hired next month. I think part of my frustration is I feel like if I don’t figure this stuff out quick enough they won’t hire me, so that puts extra pressure on me. And at this point with Google, I would just like to throw it out the window. ☺
I really do appreciate the help. It’s developers like you that keep some of us from going insane with frustration. I’ve bookmarked your blog and will be keeping up with the other snippets you put out. And thank you is not enough to repay for the help today, but I do thank you very much.
Carolyn
I’m glad I was able to help. Actually it’s a pretty tough subject, and unless your new job involves cryptography or some advanced algorithms – I wouldn’t be so pressured about it.
Having that in mind – you now have the value to pass in the “Authorization: …” HTTP request header, which puts you with a solution to the initial problem you’ve asked help with at the beginning of our conversation today.
So – tomorrow is a new day, and some fresh thoughts will help you figure this out. If you do – I would appreciate a code snippet to share with the rest of us.
Definitely. I keep meaning to write some articles for a couple of forums I “lurk” around on but I never seem to get it done. Plus I would like to pay forward what you help me with today.
I really do appreciate it.
Carolyn
There’s no need for an “article”, a couple of lines of code which do the job are just more than enough – I’ll figure them out =)
This is the best example I have come across. Very clear, concise and most easily adaptable. Thank you very much.
Thank you, it’s always great to know my work is useful to others 🙂
Thanks you so much. Fantastic post. Much appreciated.
Thank you for your feedback 🙂
Once i have access_token. I am using RestSharp for passing that access_token for picasa web.
public string GetPicasaAlbum(string authorization, string userName)
{
var request = new RestRequest(“data/feed/api/user/{user-name}”, Method.GET);
request.AddUrlSegment(“user-name”, userName);
request.AddUrlSegment(“access_token”, authorization);
request.AddHeader(“GData-Version”, “2”);
request.AddHeader(“Content-Type”, “application/xml”);
IRestResponse response = _googleApi.ExecuteRequest(request, “https://picasaweb.google.com”);
}
Response I do get result but only public photos for that user id not private.
Could you please tell me what i am doing wrong here?
Hi, I’m sorry but the subject of this article is far from dissecting the Picasa-specific service API.
It seems that you’re past the authentication step though, which is good.
You’ll have to figure out the Picasa-specific details by yourself…
Fantastic post! Thank you very much!
You’re welcome!
I’m getting a 400 error
(The remote server returned an error: (400) Bad Request.)
Does anyone has an idea as of why this is happening ?
Double check the daylight saving time, you may need to subtracte an hour from the IssueDate
Hi Yahia,
Have you run into any issues with this?
Thanks!
Hi Zavitax,
Thanks for your reply. Actually we are at GMT+1 (since a couple of weeks), and GetAccessToken returns a correct token only if I change the time back to GMT+0. Otherwise it returns error 400 Bad Request.
Any ideas, thank in advance ?
Hi Yahia,
I actually happen to have the same code running in production for quite a while now, and it survived DST changes both to winter time and back to summer time so it seems the problem is not in the access token generation code.
The access token expiration time calculation code uses DateTime.UtcNow which represents the Universal Coordinated Time (previously known as GMT) as it’s base, which means that unless there’s some local timezone issue with the test machine – it should work both when DST is on and off.
In your case, I would start by checking the timezone settings of the machine you’re testing the code on. There are tools out there which could help you uncover the rules used to determine when DST should kick in and out like TZEdit, which you can find here:
http://support.microsoft.com/kb/914387
Let me know how if that helps!
Thanks Zavitax. I believe the difference between our 2 situations, is that in my country the DST has been put in place recently (couple of years), and is still not stable in its application date (still a political decision). Therefore the NTP servers still do not account for local DST.
So I resolved the issue by tweaking GetExpiryAndIssueDate by replacing :
var issueTime = DateTime.UtcNow;
by
var issueTime = DateUtility.GetNetworkTime().ToUniversalTime();
Where the GetNetworkTime function gets the UTC time from MS NTP server instead of using the local machine time
Thanks
Our DST kick in/out dates are also influenced by politics and are not very stable, so I can relate to your situation.
I guess that your solution is as good as any other, although personally – I would prefer to attack the root cause and have the machine’s timezone settings corrected to avoid extra calls over the network and remove dependencies on an external time server.
Oh, very helpful, you save a lot of my time. Thanks
You welcome!
STILL VERY HELPFUL! I work at a university – we have google domains for education, and we let students opt to have us put their class schedule data into their university google calendar accounts. Google Calendar API v2 was turned off on 11/17/2014, and even though it’s late 2014, the documentation and examples in google-land are very lacking. I’m building a .NET application registered as a google service account, and your class does everything I need to create and sign a JWT, use it to request a token from google, and receive and do stuff with the token response! It was a very frustrating road to get to this point, and your blog entry saved me, so very big thank you.
I’d like to contribute to this thread, and so I have a few minor remarks that might be of assistance to future adopters of your class:
1) if your visual studio compiler complains about the dynamic method be sure to add microsoft.csharp to your project references (most people probably know this)
2) When you call GoogleJsonWebToken.GetAccessToken(…) be sure to use the long xxxxxx@developer.gserviceaccount.com email address that matches your pkcs12 key file that you got when you created your client ID with application type of service account… Your instructions at the top detail this, but I was in a hurry and in the code saw “clientEmail” and stupidly entered our test student account’s email…oops!
Anyway, thanks again
Hi Adam, I’m glad to hear this post helped you!
I can totally relate to your frustration – getting this pieced together was a bumpy experience indeed.
Thank you for your contribution as well – it’s good to know people like you are out there 🙂
I have no idea how to create a email in Google Apps with Google Appi and C# | Questions and Answers Resource
HEY HEY!!! This example works!!
FWIW: I spent a bit of time trying to make a Google-acceptable JWT using MS’s JsonWebTokenHandler ( a library, with which, for other projects, has worked quite well ).
I seemed to be very close – all my claims were were correct, per Google documentation – but I could not get MS to make the signature.
MS’s library refuses to make the RSA-style signature based on the service account certificate generated for me by Google.
The error:
Jwt10530: The ‘System.IdentityModel.Tokens.X509AsymmetricSecurityKey’ for signing cannot be smaller than ‘2048’ bits.
or
IDX10630: The ‘System.IdentityModel.Tokens.X509AsymmetricSecurityKey’ for signing cannot be smaller than ‘2048’ bits.
Maybe this note will save a few others some time.
Thank you for your feedback and the additional information for the community!
there is adaptation to latest api
using System;
using System.Text;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Web.Script.Serialization;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Net;
using System.IO;
using Google.Apis.Auth.OAuth2.Requests;
using Google.Apis.Requests.Parameters;
using Google.Apis.Auth;
using Google.Apis.AndroidPublisher.v2;
public class GoogleJsonWebToken
{
public const string SCOPE_ANALYTICS_READONLY = “https://www.googleapis.com/auth/analytics.readonly”;
public static dynamic GetAccessToken(string clientIdEMail, string keyFilePath, string scope)
{
// certificate
var certificate = new X509Certificate2(keyFilePath, “notasecret”);
// header
var header = new { typ = “JWT”, alg = “RS256” };
// claimset
var times = GetExpiryAndIssueDate();
var claimset = new
{
iss = clientIdEMail,
scope = scope,
aud = “https://www.googleapis.com/oauth2/v3/token”,
iat = times[0],
exp = times[1],
};
JavaScriptSerializer ser = new JavaScriptSerializer();
// encoded header
var headerSerialized = ser.Serialize(header);
var headerBytes = Encoding.UTF8.GetBytes(headerSerialized);
var headerEncoded = Convert.ToBase64String(headerBytes);
// encoded claimset
var claimsetSerialized = ser.Serialize(claimset);
var claimsetBytes = Encoding.UTF8.GetBytes(claimsetSerialized);
var claimsetEncoded = Convert.ToBase64String(claimsetBytes);
// input
var input = headerEncoded + “.” + claimsetEncoded;
var inputBytes = Encoding.UTF8.GetBytes(input);
// signiture
RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)certificate.PrivateKey;
byte[] privateKeyBlob = rsa.ExportCspBlob(true);
var key = new RSACryptoServiceProvider();
key.ImportCspBlob(privateKeyBlob);
var signatureBytes = key.SignData(inputBytes, “SHA256”);
var signatureEncoded = Convert.ToBase64String(signatureBytes);
// jwt
var jwt = headerEncoded + “.” + claimsetEncoded + “.” + signatureEncoded;
var client = new WebClient();
client.Encoding = Encoding.UTF8;
var uri = “https://www.googleapis.com/oauth2/v3/token”;
var content = new NameValueCollection();
content[“assertion”] = jwt;
content[“grant_type”] = “urn:ietf:params:oauth:grant-type:jwt-bearer”;
string response = Encoding.UTF8.GetString(client.UploadValues(uri, “POST”, content));
var result = ser.Deserialize(response);
return result;
}
private static int[] GetExpiryAndIssueDate()
{
var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var issueTime = DateTime.UtcNow;
var iat = (int)issueTime.Subtract(utc0).TotalSeconds;
var exp = (int)issueTime.AddMinutes(55).Subtract(utc0).TotalSeconds;
return new[] { iat, exp };
}
}
Thanks for sharing!
It’s works perfect. Thank you SO MUCH!
Thank you for your feedback!
Really helpful . Thanks a lot. perfect code to create the access_token,
Thank you!
zavitax, thank you for this code. It would have taken me forever to get my console app working without your help. Keep up the good work.
Glad it helped! Thanks!
Thank you! Works perfectly and you saved me a great deal of time.
Thank you for the feedback!
Thanks. I’ve found this article very useful even though I’m using node.js. I’m able to construct the jwt but have failed to obtain an access token from “https://accounts.google.com/o/oauth2/token” so my web application would send email. I’m desperately looking for help.
I don’t have much experience with nodejs, but have you looked at this article?
https://dannysu.com/2014/01/16/google-api-service-account/
Thanks! I’m still trying to make sense out of the cow dung.
I’ve constructed a jwt using:
var njwt = require(‘/usr/local/lib/node_modules/njwt’);
var jwt = njwt.create(jwtClaimSet, signingKey);
where:
var jwtClaimSet = {
“iss”: account,
“scope”: “https://www.googleapis.com/auth/gmail.send”,
“aud”: “https://accounts.google.com/o/oauth2/token”,
“exp”: expiration,
“iat”: iatTime
};
where:
var account = client_email // for service account
var iatTime = Date.now() * 1000;
and
var expiration = iatTime + 3600;
var signingKey = private_key; // for service account
Now that I have the jwt, I request for an access token to send email as follows:
var request = require(‘/usr/local/lib/node_modules/request’);
request(options, function (error, response, body) {..});
where:
var options = {
method: “POST”,
host: “www.googleapis.com”,
content_type: “application/x-www-form-urlencoded”,
uri: “https://accounts.google.com/o/oauth2/token”,
grant_type: “urn:ietf:params:oauth:grant-type:jwt-bearer”,
assertion: jwt
};
At this point I get the following error:
response status code: 400
and
body: {
“error” : “invalid_request”,
“error_description” : “Required parameter is missing: grant_type”
}
It looks like I’ve specified grant_type wrongly!
How does one specify it correctly?
I need help on this and will appreciate greatly.
Looking forward to some responses from you who have succeeded.
Thanks.
I vaguely remember getting grant_type errors as well back when I messed with this, just to realize that its irrelevant altogether.
I would look for the problem elsewhere. If I had to guess, I’d place my bet on the “exp” and “iat” values being incorrectly computed.
Thanks alot. You Sir have been very helpful and indeed you are right!
I’ve gone another way:
var google = require(‘/usr/local/lib/node_modules/googleapis’);
var key = require(pathToJson); // path to where my service account json is
var scope = “https://www.googleapis.com/auth/gmail.send”;
var jwtClient = new google.auth.JWT(key.client_email, null, key.private_key, scope, null);
jwtClient.authorize(function (err, tokens) {…});
And now at last, I have the tokens!
Glad I could help, and thanks for sharing the solution with the community!
I need help. I’m drowning!!
Well, I did get the access tokens to send email using the Gmail API.
Incidentally, because I have a service account, I get:
{ access_token: ‘blah blah blah’,
token_type: ‘Bearer’,
expiry_date: 1452803724000,
refresh_token: ‘jwt-placeholder’ }
Question 1: On expiry_date; is this time in ms since Jan 1, 1970?
Question 2: On ‘jwt-placeholder’; does this mean I don’t have a refresh_token?
Question 3: Is a refresh_token needed for a service account or will I just request for a new token when expiry_date has elapsed?
Anyway, my web application attempts to send email immediately the access token is received.
I’d appreciate your comments on how I attempt to send an email:
var encodedMail = new Buffer(
“From: sender@gmail.com\n” +
“To: recipient@gmail.com\n” +
“Subject: blah blah blah\n\n” +
“The Message”
).toString(“base64”).replace(/\+/g, ‘-‘).replace(/\//g, ‘_’);
var http = require(‘https’);
var postOptions = {
hostname: ‘www.googleapis.com’,
port: ‘443’,
path: ‘/gmail/v1/users/me/messages/send’,
method: ‘POST’,
headers: {
“Authorization”: ” ‘Bearer ‘ ” + access_token,
“Content-Type”: “application/json”
}
};
var postRequest = http.request(postOptions, function (response) {
response.setEncoding(‘utf8’);
….
});
I GET THE FOLLOWING ERROR and have so far failed to correct it!
‘{\n “error”: {\n “errors”: [\n {\n “domain”: “global”,\n “reason”: “authError”,\n “message”: “Invalid Credentials”,\n “locationType”: “header”,\n “location”: “Authorization”\n }\n ],\n “code”: 401,\n “message”: “Invalid Credentials”\n }\n}\n’
I’d appreciate your kind help.
While using the authorization token to send email via gmail API I get the following error:
{
“error”: {
“errors”: [
{
“domain”: “global”,
“reason”: “authError”,
“message”: “Invalid Credentials”,
“locationType”: “header”,
“location”: “Authorization”,
}
],
“code”: 401,
“message”: “Invalid Credentials”
}
}
I’ve not succeeded to correct this error! I’d appreciate kind guidance from those of you who have succeeded in sending email via gmail API!
Here is what I’m doing:
var sendMessage = require(‘./models/sendEmail’);
sendMessage.byEmail(email, theSubject, theMessage, accessToken, function (theParams) {…});
where:
var exports = module.exports = {};
exports.byEmail = function (email, theSubject, theMessage, accessToken, callback) {
var someParams = {// Prepare to send parameters
someResponse: “”,
someChunk: “”
};
var encodedMail = new Buffer(
“From: my@gmail.com\n” +
“To: ” + email + “\n” +
“Subject: ” + theSubject + “\n\n” +
theMessage
).toString(“base64”).replace(/\+/g, ‘-‘).replace(/\//g, ‘_’);
var http = require(‘https’);
var postOptions = {
hostname: ‘www.googleapis.com’,
port: ‘443’,
path: ‘/gmail/v1/users/me/messages/send’,
method: ‘POST’,
headers: {
“Authorization”: ” ‘Bearer ‘ ” + accessToken,
“Content-Type”: “application/json”
}
};
var postRequest = http.request(postOptions, function (response) {
response.setEncoding(‘utf8’);
someParams.someResponse = response;
response.on(‘data’, function (chunk) {
someParams.someChunk = chunk;
callback(someParams);
});
});
postRequest.write(JSON.stringify({“raw”: encodedMail}));
postRequest.end();
};
I’ve seen this, Suggested action: Refresh the access token using the long-lived refresh token.
But I wonder whether I have a refresh_token.
THIS IS HOW MY TOKENS LOOK LIKE:
{ access_token: ‘blah blah blah’,
token_type: ‘Bearer’,
expiry_date: 1452803724000,
refresh_token: ‘jwt-placeholder’ }
where ‘blah blah blah’ represents my actual access_token.
PLEASE HELP. I’M DROWNING!
I think this is more related to the scenario when an access token is obtained in a browser session. Service tokens don’t need this.
I concur! Service token doesn’t need a refresh_token. Still, the error “Invalid Credentials” persists, even when I execute the application afresh, to get a new token and use it to send email! Moreover, I attempt to send email right after the token has been obtained – excludes the possibility of expiry!
So, I’m flabbergasted. I’ve no idea what causes this error.
You might want to check if you’re authenticating with the correct SCOPE, and also, check if your service account has access to your gmail box.
Having said all that, why ain’t you using traditional methods for sending out emails like good old SMTP?
Hi,
thanks for your code, I tested your code and does not work for me. The problem is a object keyFilePath. I put the location of Json file but it dosn’t work, what is keyFilePath?
Hello! Thanks for your feedback. The code comes with a fair amount of text explaining what you need to do. Please read that text.
I created a project name to obtain client id and client email, (all informations are contains in file JSON) … and I have a private key in this file. So, in c# code, you have a string to name keyFilePath. Is this the same of private key
Sure, keyFilePath should contain the path to the privatekey.p12 file which corresponds to the Google Service Account e-mail.
Thanks for your answer.
I created a project name to obtain client id and client email, (all informations are contains in file JSON) … and I have a private key in this file. So, in c# code, you have a string to name keyFilePath. Is this the same of private key.
But, when I replace keyFilePath by private key, I have an error. Can you explain me : what does it have to put in place ” keyFilePath “.
Thanks for this answer and your code. I was wrong, I have create a JSON file instead to choose a extension .p12.
Thanks for your answer. I founded where was my problem. I had created a JSON file. So i created a file with extension .p12 and your code works.
Thanks for this code.
But the token which has created by your code, he doesn’t work. He return me a error 403 forbidden.
When i create the access token with tools of google analytics, the request with the new access token, he works.
Have you an idea, why the access token created by your code, he doesn’t work??
Using Google Services in UWP C# Apps – Part 2 - Falafel Software Blog
thank you for sharing this! I was able to use this example to create a similar sample for UWP: https://blog.falafel.com/using-google-services-uwp-c-apps-part-2/
appreciate your work and sharing it, hope this is also helpful to someone
Thank you. I’ve read your article. Good job! \o/
It’s good to know my work is of use.
Great work!!! This is very helpful to me! Thank a lot~~~
Thumbs up!
You really helped me!! Huge thanks to you 🙂
My pleasure!
Thank you so much. Your work has saved me. I was struggling to much the Google documentation and then I stumbled on your post, I followed your steps and it worked first time. You are genius
Using Google Services in UWP C# Apps — Part 2 – SelArom Dot Net
Thanks! Good to know this is still helpful after so many years!