I am trying to use this API through a service account.
https://securetoken.googleapis.com/v1/token?key=[API_KEY]
Reference: https://cloud.google.com/identity-platform/docs/use-rest-api#section-refresh-token
The problem is that I keep having the 403 FORBIDDEN
error when trying to call it with credentials generated from my service account.
{"error"=>{"code"=>403, "message"=>"Request had insufficient authentication scopes.", "status"=>"PERMISSION_DENIED", "details"=>[{"@type"=>"type.googleapis.com/google.rpc.ErrorInfo", "reason"=>"ACCESS_TOKEN_SCOPE_INSUFFICIENT", "domain"=>"googleapis.com", "metadata"=>{"method"=>"google.identity.securetoken.v1.SecureToken.GrantToken", "service"=>"securetoken.googleapis.com"}}]}}
Here is what I checked.
Token Service API
is enabled on my GCP project.owner
role.Here is the code that I am trying to make it work.
SCOPE = %w[
https://www.googleapis.com/auth/firebase
https://www.googleapis.com/auth/identitytoolkit
]
def initialize
decoded_service_account_json = Base64.decode64(ENV.fetch("GCP_JSON_KEYFILE_BASE64"))
io = StringIO.new(decoded_service_account_json)
@credentials = Google::Auth::DefaultCredentials.make_creds(scope: SCOPE, json_key_io: io)
end
# Exchange a refresh token for an ID token
# References:
# - https://cloud.google.com/identity-platform/docs/reference/rest/v1/accounts/accounts/getIdpAuthentication
def exchange_refresh_token(refresh_token)
uri = URI.parse("https://securetoken.googleapis.com/v1/token")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path, { 'Content-Type' => 'application/x-www-form-urlencoded' })
request.body = URI.encode_www_form(
'grant_type' => 'refresh_token',
'refresh_token' => refresh_token
)
request.add_field('Authorization', @credentials.apply({})[:authorization])
response = http.request(request)
response_data = JSON.parse(response.body)
if response.code.to_i == 200
{
id_token: response_data['id_token'],
refresh_token: response_data['refresh_token'],
expires_at: Time.now + response_data['expires_in'].to_i
}
else
raise TokenExchangeError.new("Token exchange failed: #{response.code} - #{response_data['error_description']}")
end
end
Do you have any idea why it does not work? Or perhaps Token Service API
cannot work with a service account?
Thank you in advance.
I found the solution by playing around and making guesses as the documentation was not providing any useful information.
I needed to add the https://www.googleapis.com/auth/securetoken
scope in my SCOPE
in order to be able to use https://securetoken.googleapis.com/v1/token
.
SCOPE = %w[
https://www.googleapis.com/auth/firebase
https://www.googleapis.com/auth/identitytoolkit
https://www.googleapis.com/auth/securetoken
]
def initialize
decoded_service_account_json = Base64.decode64(ENV.fetch("GCP_JSON_KEYFILE_BASE64"))
io = StringIO.new(decoded_service_account_json)
@credentials = Google::Auth::DefaultCredentials.make_creds(scope: SCOPE, json_key_io: io)
end
That was completely a random guess... I checked all the possible scopes here: https://developers.google.com/identity/protocols/oauth2/scopes
But https://www.googleapis.com/auth/securetoken
is not one of them. Even Google Search does not return any relevant information for the https://www.googleapis.com/auth/securetoken
scope.
This is quite frustrating. Could someone tell me if there is a way I could have found the solution without guessing? Where does https://www.googleapis.com/auth/securetoken
come from?