Authenticating with Google Service Account in C# (JWT)

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 };
    }
}
Advertisements

90 thoughts on “Authenticating with Google Service Account in C# (JWT)

    • 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.

  1. 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.

  2. 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?

      • 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.

      • 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.

      • 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 =)

  3. 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…

      • 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 ?

  4. 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.

  5. 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 🙂

  6. I have no idea how to create a email in Google Apps with Google Appi and C# | Questions and Answers Resource

  7. 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.

  8. 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 };
    }

    }

  9. 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’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!

      • 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();
        };

  10. 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!

  11. 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?

  12. 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?

      • 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

      • 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 “.

  13. 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??

  14. Using Google Services in UWP C# Apps – Part 2 - Falafel Software Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s