function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Jitendra RawatJitendra Rawat 

Google Service Account

How to authenticate google service account for google adword api. By using this document. 
https://developers.google.com/accounts/docs/OAuth2ServiceAccount
My code Is as following :
public class GoogleAdWordApi{
     public Static String getAccessToken(){
        String accesstoken = '';
        
        String headerStr64 = EncodingUtil.base64Encode(Blob.valueOf('{"alg":"RS256","typ":"JWT"}'));
        headerStr64 = headerStr64.split('=')[0];

        String str = '{"iss":"965360208690-a6qbrksf61b5dl7punv126ntb5aspu93@developer.gserviceaccount.com",';
        str += '"scope":"' + 'https://www.googleapis.com/auth/prediction' + '",';
        str += '"aud":"' + 'https://accounts.google.com/o/oauth2/token' + '",';
        str += '"exp":' + system.now().addminutes(30).gettime()/1000 + ',';
        str += '"iat":'+system.now().gettime()/1000;
        str += '}';
        
        System.debug('@@@ str==>'+str);
        
        String claim_set64 = EncodingUtil.base64Encode(Blob.valueOf(str));
        claim_set64 = claim_set64.split('=')[0];
        System.debug('@@ claim_set64==>'+claim_set64);
        
        //make signature
        String sigInputstr =   headerStr64 + '.' + claim_set64 ;
        Blob signInputByteData = Blob.valueOf(sigInputstr);
        Blob signByteData = System.Crypto.signWithCertificate('RSA-SHA256',signInputByteData,'privatekey');
        String sigStr64 = EncodingUtil.base64Encode(signByteData);
        System.debug('@@@ sigStr64==>'+sigStr64);
        sigStr64 = sigStr64.split('=')[0];
        System.debug('@@@ sigStr64==>'+sigStr64);
        
        String jwtStr = headerStr64 + '.' + claim_set64 + '.' + sigStr64;
        
        System.debug('@@@ jwtStr==>'+jwtStr);
        
        HttpRequest req = new HttpRequest();
        req.setEndpoint('https://accounts.google.com/o/oauth2/token');
        req.setMethod('POST');
        req.setHeader('Host','accounts.google.com');
        req.setHeader('Content-Type','application/x-www-form-urlencoded');
        String body = 'grant_type='+ EncodingUtil.urlEncode('urn:ietf:params:oauth:grant-type:jwt-bearer','UTF-8');
        body += '&assertion=' + jwtStr;
        req.setBody(body);
        System.debug('@@ body==>'+body);
        Http http = new Http();
        HTTPResponse res = http.send(req);
        System.debug(res.getBody());    
        
        return accesstoken;
    }
}
--------------------------
Please tell me what is going worng with it.
Best Answer chosen by Jitendra Rawat
jhennyjhenny
public with sharing class GoogleApiTest {

 // NOTE - DONT PUT THIS KEY HERE IN A PROD SCENARIO - SEE ARTICLE ON SECURE CODING STORING SECRETS
  private static final String CLIENT_KEY = 'YOUR_CLIENT_KEY';
  private static final String CLIENT_SCOPE = 'https://www.googleapis.com/auth/calendar';
  private static final String CLIENT_EMAIL = 'YOUR CLIENT EMAIL'';

  public GoogleApiTest(){

    String jwt = generateJWT(CLIENT_KEY, CLIENT_SCOPE, CLIENT_EMAIL);
    Token t = getToken(jwt);
    System.debug(t);
  }

  public Token getToken(String jwt) {

    Httprequest req = new HttpRequest();
    req.setEndpoint('https://www.googleapis.com/oauth2/v4/token');
    req.setMethod('POST');
    req.setHeader('Content-Type','application/x-www-form-urlencoded');

    String body = '';
    body = 'grant_type='+EncodingUtil.urlEncode('urn:ietf:params:oauth:grant-type:jwt-bearer','UTF-8');
    body += '&assertion='+jwt;
    req.setBody(body);

    Http h = new Http();
    HttpResponse res = h.send(req);
    return (Token)JSON.deserialize(res.getBody(), Token.Class);
	}

  private String generateJWT(String key, String scope, String clientEmail){

    String header = '{"alg":"RS256","typ":"JWT"}';
    String headerUrlBase64Encode = EncodingUtil.base64Encode(Blob.valueOf(header));
    String headerBase64UrlEncode = EncodingUtil.urlEncode(headerUrlBase64Encode, 'UTF-8');

    long currentTime = datetime.now().getTime()/1000;
    long expiryTime = datetime.now().addHours(1).getTime()/1000;

    ClaimSet claimSet = new ClaimSet();
    claimSet.iss = clientEmail;
    claimSet.scope = scope;
    claimSet.aud = 'https://www.googleapis.com/oauth2/v4/token';
    claimSet.exp = expiryTime;
    claimSet.iat = currentTime;

    String clailSetJson = JSON.serialize(claimSet);
    String claimSetBaseEncode = EncodingUtil.base64Encode(Blob.valueOf(clailSetJson));
    String claimSetBase64UrlEncode = EncodingUtil.urlEncode(claimSetBaseEncode, 'UTF-8');

    String algorithmName = 'RSA-SHA256';
    Blob privateKey = EncodingUtil.base64Decode(key);
    Blob input = Blob.valueOf(headerBase64UrlEncode+'.'+claimSetBase64UrlEncode);
    Blob signed = Crypto.sign(algorithmName, input, privateKey);
    String signedBase64Encode = EncodingUtil.base64Encode(signed);
    String signedStrBase64UrlEncode = EncodingUtil.urlEncode(signedBase64Encode, 'UTF-8');

    String jwt = headerBase64UrlEncode+'.'+claimSetBase64UrlEncode+'.'+signedStrBase64UrlEncode;
    return jwt;
  }

  private class ClaimSet{

    private String iss;
    private String scope;
    private String aud;
    private long exp;
    private long iat;
  }

  private class Token{

    private String access_token;
    private String token_type;
    private String expires_in;
  }

}

 

All Answers

SonamSonam (Salesforce Developers) 
Could you please provide the link to the document you are using, below one is not working for me:
https://developers.google.com/accounts/docs/OAuth2ServiceAccount

Also, are you getting any error messages with theabove code? 
Goutham RamireddyGoutham Ramireddy
This Worked for me

//Creating the header string and encoding it to Base64
        String Header = '{"alg":"RS256","typ":"JWT"}';
        String Header_Encode = EncodingUtil.base64Encode(blob.valueof(Header));        
        System.Debug('Header_Encode ###############################'+Header_Encode);
        
       //setting the assertion expiration time and Issuing time
        long currentTime = datetime.now().getTime()/1000;
        long expiryTime = datetime.now().addHours(1).getTime()/1000;
       
       //Creating the Claimset string and encoding it to Base64
        String claim_set = '{"iss":"SERVICE ACC MAIL EMAIL ID"';
        claim_set += ',"scope":"https://www.googleapis.com/auth/analytics.readonly"';//I made call for analytics api
        claim_set += ',"aud":"https://www.googleapis.com/oauth2/v3/token"';
        claim_set += ',"exp":'+expiryTime;
        claim_set += ',"iat":'+currentTime+'}';        
        System.Debug('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'+claim_set);              
        String claim_set_Encode = EncodingUtil.base64Encode(blob.valueof(claim_set));
        System.Debug('claim_set_Encode%%%%%%%%%%%%%%%%%%%%%%%%%'+claim_set_Encode);
        
        //Creating the input for Signing i.e HeaderEncode.CalimSetEncode
        String Singature_Encode = Header_Encode+'.'+claim_set_Encode;
        System.Debug('Singature_Encode@@@@@@@@@@@@@@@@'+Singature_Encode);
       
       //Download JSON format Key from google service account and take the Private key by removing all '\n' 
        String key = 'MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAM5HGuvhQEYBCuvDoYxlkU6bIaBIokCTIpkpVo6JSQt8e8p8yjzdGqI51DxYdKIPuHxG/S8wAUdf7gOHNQR3w+BcSzpu9Jr6bzTQSn/XLVVi4oc7LwlGsL0J0yJT8by4labAmf+qPYESboTKzMhMENU4A5TbjLZOPZ0D4nu9cZu9AgMBAAECgYAUkNZISi7kS9pQ4zJKEx5HngPePR+gHItIheyRTXKw6HpXF9X593leTzGvyonmVnboPROlDr4x5YiPZX2Nsnsex4IawQC2VoCVNI+2sqlisfNQSOeXjlJABnuWJNCW1afX29NNrkZ2rTSzzvZlaryCf9sp+eT9aMNLruq6kYCmwQJBAOd10jkRHaEWKKeLMO5fzLRiAKn7/5Uh4lKiTqiYM6q6T8IWFvMimwl+PsScazmS2kCQQDkJcsijgaCNyEdWWRJaqtrvVLDW4l7imfbYut5HOSp48SaDjWFCcUgUfCbIMBOEmuvGgnCUXoYcjTpnAdkLyc1AkEA3j7zrHgSyypLvxSX10uFc27m1FCe0aH8mtH1vMjqtXyeKvaGH59qxRkcy4A/FDJn5G5O6VZtU4ril7IOFMbMgQJASMOdBApUHRfEIf4utBOnVJdvXAtHz/UWpqvn+hqy+1Q/kfrSKvowutwnZvKNItlRKumdDzK5RC64nYE8AkHfuQJBAMI8VjZteXDvHuNvn989kNeFmlGPwdogKgfNelRDslKCikdAARTTu76HtPyHWl5inDo54I+O+e8uOtg7zpmf76k\u003d';
        
        //Decoding the Private Key to bytes
        blob privateKey = EncodingUtil.base64Decode(key);
        System.Debug('privateKey is &&&&&&&&&&&&&&&&'+Singature_Encode_Url);
        
        //Replacing '=' Value with blank value
        Singature_Encode = Singature_Encode.replaceAll('=','');
        
        //URL encoding the sign string before signing
        String Singature_Encode_Url = EncodingUtil.urlEncode(Singature_Encode,'UTF-8');
        System.Debug('Singature_Encode_Url ^^^^^^^^^^^^^'+Singature_Encode_Url);
        
        blob Signature_b =   blob.valueof(Singature_Encode_Url);
       
        String Sinatute_blob = EncodingUtil.base64Encode(Crypto.sign('RSA-SHA256', Signature_b , privateKey));
        System.Debug('Sinatute_blob Is &&&&&&&&&&&&&&&&&&&&&&&&& '+Sinatute_blob);        
        Sinatute_blob = Sinatute_blob.replaceAll('+', '-');
        Sinatute_blob = Sinatute_blob.replaceAll('/', '_');
        Sinatute_blob = Sinatute_blob.replaceAll('=', '');
        System.Debug('Final signature Is (((((((((((((((((((((((((('+Sinatute_blob);
        
        //Creating the JWT by appending Signature encode and Signature        
        String JWT = Singature_Encode+'.'+Signature;
        System.Debug('JWT IS ************************'+JWT);
       
          
        //Making the HTTP req          
            http h = new Http();
        Httprequest req = new HttpRequest();
        req.setEndpoint('https://www.googleapis.com/oauth2/v3/token');
        req.setMethod('POST');      
        req.setHeader('Content-Type','application/x-www-form-urlencoded');
        string bodyrequest = '';
        bodyrequest = 'grant_type='+EncodingUtil.urlEncode('urn:ietf:params:oauth:grant-type:jwt-bearer','UTF-8');
        bodyrequest += '&assertion='+EncodingUtil.urlEncode('JWT','UTF-8');        
        System.debug('%%%%%%%%%%'+bodyrequest);
        req.setBody(bodyrequest);
        HttpResponse res = h.send(req);
        system.debug('******' + res.getBody()) ;


 
c_r_carvalhoc_r_carvalho
Is this works?
jhennyjhenny
public with sharing class GoogleApiTest {

 // NOTE - DONT PUT THIS KEY HERE IN A PROD SCENARIO - SEE ARTICLE ON SECURE CODING STORING SECRETS
  private static final String CLIENT_KEY = 'YOUR_CLIENT_KEY';
  private static final String CLIENT_SCOPE = 'https://www.googleapis.com/auth/calendar';
  private static final String CLIENT_EMAIL = 'YOUR CLIENT EMAIL'';

  public GoogleApiTest(){

    String jwt = generateJWT(CLIENT_KEY, CLIENT_SCOPE, CLIENT_EMAIL);
    Token t = getToken(jwt);
    System.debug(t);
  }

  public Token getToken(String jwt) {

    Httprequest req = new HttpRequest();
    req.setEndpoint('https://www.googleapis.com/oauth2/v4/token');
    req.setMethod('POST');
    req.setHeader('Content-Type','application/x-www-form-urlencoded');

    String body = '';
    body = 'grant_type='+EncodingUtil.urlEncode('urn:ietf:params:oauth:grant-type:jwt-bearer','UTF-8');
    body += '&assertion='+jwt;
    req.setBody(body);

    Http h = new Http();
    HttpResponse res = h.send(req);
    return (Token)JSON.deserialize(res.getBody(), Token.Class);
	}

  private String generateJWT(String key, String scope, String clientEmail){

    String header = '{"alg":"RS256","typ":"JWT"}';
    String headerUrlBase64Encode = EncodingUtil.base64Encode(Blob.valueOf(header));
    String headerBase64UrlEncode = EncodingUtil.urlEncode(headerUrlBase64Encode, 'UTF-8');

    long currentTime = datetime.now().getTime()/1000;
    long expiryTime = datetime.now().addHours(1).getTime()/1000;

    ClaimSet claimSet = new ClaimSet();
    claimSet.iss = clientEmail;
    claimSet.scope = scope;
    claimSet.aud = 'https://www.googleapis.com/oauth2/v4/token';
    claimSet.exp = expiryTime;
    claimSet.iat = currentTime;

    String clailSetJson = JSON.serialize(claimSet);
    String claimSetBaseEncode = EncodingUtil.base64Encode(Blob.valueOf(clailSetJson));
    String claimSetBase64UrlEncode = EncodingUtil.urlEncode(claimSetBaseEncode, 'UTF-8');

    String algorithmName = 'RSA-SHA256';
    Blob privateKey = EncodingUtil.base64Decode(key);
    Blob input = Blob.valueOf(headerBase64UrlEncode+'.'+claimSetBase64UrlEncode);
    Blob signed = Crypto.sign(algorithmName, input, privateKey);
    String signedBase64Encode = EncodingUtil.base64Encode(signed);
    String signedStrBase64UrlEncode = EncodingUtil.urlEncode(signedBase64Encode, 'UTF-8');

    String jwt = headerBase64UrlEncode+'.'+claimSetBase64UrlEncode+'.'+signedStrBase64UrlEncode;
    return jwt;
  }

  private class ClaimSet{

    private String iss;
    private String scope;
    private String aud;
    private long exp;
    private long iat;
  }

  private class Token{

    private String access_token;
    private String token_type;
    private String expires_in;
  }

}

 
This was selected as the best answer
Scott Morrison 33Scott Morrison 33
This works, I couldn't get the standard jwt classes to work, it's not clear how to sign the request using those clases. The key thing is to remove all \n, -----BEGIN PRIVATE KEY-----, -----END PRIVATE KEY----- from the private key in the google generated json value 
Nicola SpreaficoNicola Spreafico
Hi,
this is my own implementation, started from @jhenny asnwer. I added two features:
1) You can load directly the json service account credentials instead of picking email/key from it
2) Access-Token is automatically renewed when 60 minutes are expired
 
/**
* Utility class to authenticate with Google Service Account
*/
public with sharing class GoogleServiceAccountCredential {
    private String email;
    private String key;
    private String scopes;
    
    private long expiration;
    private String accessToken;
    
	/**
	* Create a new Google Credential
	* @param jsonContent Content of a Service Account JSON Credential (generated from Cloud Console)
	* @param List of scopes to be used for authentication (space separated)
	*/
    public GoogleServiceAccountCredential(String jsonContent, String scopes) {
        ServiceAccountCredential sac = (ServiceAccountCredential)JSON.deserialize(jsonContent, ServiceAccountCredential.Class);
        
        this.email = sac.client_email;
        this.key = sac.private_key.replaceAll('-----END PRIVATE KEY-----', '').replaceAll('-----BEGIN PRIVATE KEY-----', '').replaceAll('\n', '');
        this.scopes = scopes;
    }
    
	/**
	* https://developers.google.com/identity/protocols/OAuth2ServiceAccount#authorizingrequests
	*/
    public String getAccessToken() {
        // Token already available and not expired
        if(this.accessToken != null && this.expiration > (datetime.now().getTime()/1000)) {
            return this.accessToken;
        }
        
        System.debug('Access-Token not available');
        System.debug('Email: ' + this.email);
        System.debug('Scopes: ' + this.scopes);
        
        // Header
        
        String header = getHeader();
        System.debug('Header: ' + header);
        
        String base64Header = EncodingUtil.base64Encode(Blob.valueOf(header));
        System.debug('BASE64 Header: ' + header);
        
        // Claim
        
        String claim = getClaim(this.email, this.scopes);
        System.debug('Claim: ' + claim);
        
        String base64Claim = EncodingUtil.base64Encode(Blob.valueOf(claim));
        System.debug('BASE64 Claim: ' + base64Claim);
        
        // Signature
        
        String inputSignature = base64Header + '.' + base64Claim;
        System.debug('Input Signature: ' + base64Claim);
        
        String signature = sign(this.key, inputSignature);
        System.debug('Signature: ' + signature);
        
        // Assertion
        
        String assertion = base64Header + '.' + base64Claim + '.' + signature;
        System.debug('Assertion: ' + assertion);
        
        String grantType = 'urn:ietf:params:oauth:grant-type:jwt-bearer';
        System.debug('Grant Type: ' + grantType);
        
        // Fetch
        
        Token token = fetchToken(grantType, assertion);
        System.debug('Token: ' + token);
        
        // Store
        this.expiration = (datetime.now().getTime()/1000) + token.expires_in;
        this.accessToken = token.access_token;
        
        // Return
        
        return this.accessToken;
    }
    
    private String getHeader() {
        Map<String, Object> obj = new Map<String, Object>();
        obj.put('alg', 'RS256');
        obj.put('typ', 'JWT');
        
        return JSON.Serialize(obj);
    }
    
    private String getClaim(String email, String scopes) {
        long currentTime = datetime.now().getTime()/1000;
        long expiryTime = datetime.now().addHours(1).getTime()/1000;
        
        Map<String, Object> obj = new Map<String, Object>();
        obj.put('iss', email);
        obj.put('scope', scopes);
        obj.put('aud', 'https://www.googleapis.com/oauth2/v4/token');
        obj.put('exp', expiryTime);
        obj.put('iat', currentTime);
        
        return JSON.Serialize(obj);
    }
    
    private String sign(String key, String inputSignature) {
        String algorithmName = 'RSA-SHA256';
        Blob privateKey = EncodingUtil.base64Decode(key);
        Blob input = Blob.valueOf(inputSignature);
        
        Blob signed = Crypto.sign(algorithmName, input, privateKey);    
        return EncodingUtil.base64Encode(signed);
    }
    
    private Token fetchToken(String grantType, String assertion) {
        Httprequest req = new HttpRequest();
        req.setEndpoint('https://www.googleapis.com/oauth2/v4/token');
        req.setMethod('POST');
        req.setHeader('Content-Type','application/x-www-form-urlencoded');
        
        String body = '';
        body += 'grant_type=' + EncodingUtil.urlEncode(grantType,'UTF-8');
        body += '&';
        body += 'assertion=' + EncodingUtil.urlEncode(assertion, 'UTF-8');
        
        req.setBody(body);
        req.setHeader('Content-Length', body.length()+'');
        
        Http h = new Http();
        System.debug('request: ' + req);
        
        HttpResponse res = h.send(req);
        
        if(res.getStatusCode() != 200) {
            System.debug('Error Code: ' + res.getStatusCode());
            System.debug('Error Response: ' + res.getBody());
			
            CalloutException ce = new CalloutException();
            ce.setMessage('Google Authentication Error: ' + res.getBody());
            throw ce;
        } else {
            return (Token)JSON.deserialize(res.getBody(), Token.Class);
        }
    }
    
    private class ServiceAccountCredential {
        private String type;
        private String project_id;
        private String private_key_id;
        private String private_key;
        private String client_email;
        private String client_id;
        private String auth_uri;
        private String token_uri;
        private String auth_provider_x509_cert_url;
        private String client_x509_cert_url;
    }
    
    private class Token{
        private String access_token;
        private String token_type;
        private Long expires_in;
    }
}

 
anna wang 1anna wang 1
Hi,

I faced an issue when using generated jwt to request access token. I got below error:
{
    "error": "invalid_grant",
    "error_description": "Invalid JWT Signature."
}

I used the sample code above and only changed the client email, private key, and scope.
I also  changed the aud to "https://oauth2.googleapis.com/token", which is from the .json file. As google document requires https://developers.google.com/identity/protocols/OAuth2ServiceAccount

Can anyone help?