Google oAuth 2.0 (JWT token request) for Service Application

15,767

The solution was that in request code all slashes have to be backslashed

WRONG:

"scope":"https://www.googleapis.com/auth/prediction",
"aud":"https://accounts.google.com/o/oauth2/token",

CORRECT:

"scope":"https:\\/\\/www.googleapis.com\\/auth\\/prediction",
"aud":"https:\\/\\/accounts.google.com\\/o\\/oauth2\\/token",
Share:
15,767
soulburner
Author by

soulburner

Updated on June 05, 2022

Comments

  • soulburner
    soulburner almost 2 years

    I'm trying to implement Google oAuth 2 for service accounts described here: https://developers.google.com/accounts/docs/OAuth2ServiceAccount on UnityScript (or C# - that doesn't matter because they both use the same Mono .NET classes).

    I've found similar topic here: Is there a JSON Web Token (JWT) example in C#? web-token-jwt-example-in-c but I still don't have a success.

    Fist of all, I have generated header and claimset (that are just like in google documentation)

    var header: String = GetJWTHeader();
    var claimset: String = GetJWTClaimSet();
    

    The result is (separated with new lines for clarity):

    {"alg":"RS256","typ":"JWT"}

    {"iss":"425466719070-1dg2rebp0a8fn9l02k9ntr6u5o4a8lp2.apps.googleusercontent.com",

    "scope":"https://www.googleapis.com/auth/prediction",

    "aud":"https://accounts.google.com/o/oauth2/token",

    "exp":1340222315,

    "iat":1340218715}

    Base-64 encoding methods:

    public static function Base64Encode(b: byte[]): String {
        var s: String = Convert.ToBase64String(b);
        s = s.Replace("+", "-");
        s = s.Replace("/", "_");
        s = s.Split("="[0])[0]; // Remove any trailing '='s
        return s;
    }
    
    public static function Base64Encode(s: String): String {    
        return Base64Encode(Encoding.UTF8.GetBytes(s));
    }
    

    Then I'm making a signature.

    var to_sign: byte[] = 
         Encoding.UTF8.GetBytes(Base64Encode(header) + "." + Base64Encode(claimset));
    var cert: X509Certificate2 = 
         new X509Certificate2(google_pvt_key.ToArray(), "notasecret");
    var rsa: RSACryptoServiceProvider = cert.PrivateKey;
    var sgn: String = Base64Encode(rsa.SignData(to_sign, "SHA256"));
    
    var jwt: String = Base64Encode(header) + "." + Base64Encode(claimset) + 
                         "." + sgn;
    

    And then forming the request:

    var url: String = "https://accounts.google.com/o/oauth2/token";
    var form: WWWForm = new WWWForm();
    form.AddField("grant_type", "assertion");
    form.AddField("assertion_type", "http://oauth.net/grant_type/jwt/1.0/bearer");
    form.AddField("assertion", jwt);
    var headers: Hashtable = form.headers;
    headers["Content-Type"] = "application/x-www-form-urlencoded";
    
    var www: WWW = new WWW(url, form.data, headers);
    

    And all I get is "Error 400: Bad request".

    The encoded data looks like (line breaks added for clarity):

    eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.

    eyJpc3MiOiI0MjU0NjY3MTkwNzAtMWRnMnJlYnAwYThmbjlsMDJrOW50cjZ1NW80YThscDIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTM0MDIyMjMxNSwiaWF0IjoxMzQwMjE4NzE1fQ.

    lIFg7-Og_BcC5qpICLt7USwGIHUOz-vV4ADNq0AWhuRtsvFrbZn5mxk4n9r5qU66q4reTVVAtuW06DeGsdcBMNgEdIMvN6VuYQybs64p9mqrfECBYxO1FWHbUG-2On1IpowybEsRRUjZfp0jFuEY7SLE3XRaXan0k5zmejcvLQo

    I've spent two days trying to figure out what is wrong but I can't see.

    Also, I couldn't find any suitable documentation and examples.

    I'm trying just to recieve a token.

    1. Am I signing the bytes the right way?
    2. What should "scope" parameter in claimset look like? I've tried "https://www.googleapis.com/auth/devstorage.readonly" and "https://www.googleapis.com/auth/prediction".
    3. What "iss" parameter should be equal to? Client-id or e-mail address? (tried both)
    4. What are the ways to find out my mistake?
    5. Are there any C# libraries for Service Application (not for installed apps or client login)?

    I'm getting crazy... It has to work, but it doesn't... :-/