Alex Harbers
Alex Harbers

Reputation: 31

Google API OAuth2.0 JWT returns 400 - Bad Request

I copied the code from post:

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

and tried to get my own OAuth2.0 / JWT authentication to work. But no matter what I try to do I keep getting the exception

System.Exception: The remote server returned an error: (400) Bad Request.

Here is my code:

public void Authenticate()
{
    string clientId = "...apps.googleusercontent.com";
    string clientSecret = "...";
    string emailAddress = "...@developer.gserviceaccount.com";
    string publicKeyFingerprints = "...";

    string certificateFilename = "Google Analytics - OAuth 2.0 - ...-privatekey.p12";

    // certificate 
    var certificate = new X509Certificate2(certificateFilename, clientSecret);

    // header 
    var header = new { typ = "JWT", alg = "RS256" };

    // claimset 
    var times = GetExpiryAndIssueDate();
    var claimset = new
    {
        iss = emailAddress,
        scope = "https://www.googleapis.com/auth/analytics.readonly",
        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;

    _logger.DebugFormat("JWT: {0}", jwt);

    var r = WebRequest.Create("https://accounts.google.com/o/oauth2/token") as HttpWebRequest;
    r.Method = "POST";
    r.ContentType = "application/x-www-form-urlencoded";

    var post = string.Format("{0}={1}&{2}={3}",
        "grant_type", HttpUtility.UrlEncode("urn:ietf:params:oauth:grant-type:jwt-bearer", Encoding.UTF8),
        "assertion" , jwt);

    var result = SendHttpRequest(r, post);

    _logger.Debug(result);
}

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

private string SendHttpRequest(HttpWebRequest request, string json)
{
    string result = string.Empty;
    try
    {
            _logger.DebugFormat("HttpRequest: {0} {1}", request.Method, request.RequestUri);
            foreach (string header in request.Headers)
            {
                _logger.DebugFormat("Header[{0}]: {1}", header, request.Headers[header]);
            }
            _logger.DebugFormat("Body: {0}", json);

            byte[] body = Encoding.UTF8.GetBytes(json);
            request.ContentLength = body.Length;

            using (Stream requestStream = request.GetRequestStream())
            {
                requestStream.Write(body, 0, body.Length);
            }

            HttpWebResponse response = request.GetResponse() as HttpWebResponse;
            using (Stream receiveStream = response.GetResponseStream())
            {
                using (StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8))
                {
                    result = readStream.ReadToEnd();
                }
            }
            _logger.DebugFormat("...done, result={0}", result);
    }
    catch (Exception ex)
    {
        throw new Exception(ex.Message);
    }
    return result;
}

Does anyone have an idea of what's wrong with this code?

Upvotes: 0

Views: 4356

Answers (3)

jayasimha sai
jayasimha sai

Reputation: 1

I have faced the same problem

return 400 - Bad request

error : invalid_grant

error description : Bad request

the main reason behind this was incorrect timezone or date and time in the server which is requesting the token change it according to your timezone it will work fine

It worked for me!!

Upvotes: 0

Alex Harbers
Alex Harbers

Reputation: 31

I got the code working:

var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var issueTime = DateTime.Now.ToUniversalTime();

I added .ToUniversalTime() and now I got my access token.

Upvotes: 3

Ryan Boyd
Ryan Boyd

Reputation: 3018

Please look at the body of the response. That typically has more detail on the reason behind the 400 error.

If you copied the code from that post, did you verify that the date/time on your server is correct? This is the most common cause of errors when doing signatures as part of the JWT. Even a few seconds count -- verify your clock is synced to NTP and/or matches time.gov. Also make sure timezone is correct.

Upvotes: 1

Related Questions