Google's API is designed with serious
apps in mind. That's fine, but it can be obnoxiously cumbersome and heavyweight when all you want is an access token for your own account so you can write a little hobby script. I recently went through the process of figuring this out. Here's the rundown.
Make An App
First things first: you need to make an app. Head on over to the create a project page. It doesn't matter what you name it or what organization it's under. I just went with No Organization
.
Once the app is created you'll land back at the dashboard. Click on the OAuth Consent Screen link in the left navigation. Choose External
for the user type (it might be your only option anyway), then click Create
. Now there's a bunch of crap on the next form that doesn't matter. Give it a name to make it happy, then click Save
at the bottom.
Credentials
Now go to the Credentials page. Click + Create Credentials
on top and select OAuth client ID
. This next step really matters: choose Desktop App
from the selector for Application Type
. This makes sure we have the power to get a refresh token
, which we can use to fetch new access tokens
whenever we want without having to go back through the annoying web authentication flow. The Name
you give the client here doesn't matter.
Boom. Now you've got a popup with a Client ID
and a Client Secret
. These will be helpful. Keep a copy of them on hand for the future steps.
Authorization Scopes
You've got to figure out the authorization scope for whatever it is you want to do. I wanted to access my Google Photos, and I found the authorization scopes here. I chose https://www.googleapis.com/auth/photoslibrary
.
Web Auth Flow
Now we need to load a URL in our browser that will let us grant our newly created Google App the permissions it needs to access our Google Account data. Replace $CLIENT_ID
and $SCOPE
with the values we figured out in the previous two steps.
https://accounts.google.com/o/oauth2/v2/auth?client_id=$CLIENT_ID&redirect_uri=https://localhost&response_type=code&scope=$SCOPE
This app isn't verified!
Once you choose an account (and enter your password if necessary), you'll see a scary This app is not verified!
screen. Woopeedoo. Click the Advanced
link or whatever it's called (it's different in each browser), and click the scary link to go to the unsafe page. Click through a few more Allow
style Google forms and you'll ultimately land at a localhost
address, which will fail to load anything. This is fine. Look in the URL. It'll look like this:
https://localhost/?code=$CODE&scope=$SCOPE
Booyah. That $CODE
in the URL is what we're after. Copy that sucker out for safe keeping.
Curling for Tokens
Now we have to make a POST request to convert that $CODE
into a juicy refresh token
. This is the final boss. This will utilize all the variables we've gotten up until now:
curl -d "client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&code=$CODE&grant_type=authorization_code&redirect_uri=https://localhost" -X POST https://oauth2.googleapis.com/token
This will give you what you're really after. And like most good things in life, it'll be in JSON:
{
"access_token": $ACCESS_TOKEN,
"expires_in": 3599,
"refresh_token": $REFRESH_TOKEN,
"scope": $SCOPE,
"token_type": "Bearer"
}
Access Token
That $ACCESS_TOKEN
is what will let you make successful API requests. The exact form of that request is going to depend on what Google API you're trying to access. But the headers in the POST command will always take the same form:
{
"Content-type": "application/json",
"Authorization": `Bearer ${ACCESS_TOKEN}`
}
Refresh Token
As you can see from that sad expires_in
attribute: your access_token
is only going to last you an hour. But that's ok, the refresh_token
is our saviour. Whenever you need a new access_token
: you can make another POST
request using the refresh_token
. In my Scriptable script, which is mostly javascript, it looks like this:
const refreshRequest = new Request("https://oauth2.googleapis.com/token");
refreshRequest.method = "POST";
refreshRequest.body = [
`client_id=${this.ClientID}`,
`client_secret=${this.ClientSecret}`,
"grant_type=refresh_token",
`refresh_token=${this.RefreshToken}`
].join("&");
const tokenInfo = await refreshRequest.loadJSON();
The response will be a JSON
object with another access_token
in it. Rinse and repeat every hour. 😃
What do you think? I'd love to hear from you. Here is a link to the source for this article on github. Please open an issue, send me a pull-request, or just email me at jubi@jubishop.com