My AIY box stopped working.
Google stopped talking to me.
For a short while I could regenerate a temporary token but the token lasted a mere seven days.
So, inevitably, when I shouted at the box to set a timer or something equally trivial, it didn’t respond.
Regularly resetting the token was a faff and frequently forgotten, so the box fell into disuse.
Getting the box running properly again was added to my list of things to do.
The Past
Back in the distant past authorising Google AIY was a simple-ish process:
- Go to the Google cloud console
- Create a Project
- Enable the Google Assistant API
- Create some API credentials
- Download a JSON file and save it to the Pi as
assistant.json
- Launch the python
aiy
script in a Terminal on the Pi (and wait) - The Terminal will prompt visiting a URL
- Go to the URL, agree and wait for the Authorisation response code
- Paste said code in the waiting Terminal
- Go through the Authorisation acceptance steps
(OK, not that simple but a fairly straightforward set of steps.)
The above process is known as OOB or ‘Out of Band’ .
It has been deprecated in favour of a more secure OAuth flow.
(I think it has something to do with an access token being part of the response URL but I could be wrong about that.)
The Present
Google has stopped using OOB completely (even temporary tokens are unavailable) and they now enforce OAuth.
The authorisation process is similar but somewhat more involved:
- Go to the Google cloud console
- Create a Project
- Add
tld.domain
to ‘Branding | Authorised domains’
- Add
- Enable the Google Assistant API
- Create some API credentials:
- In Clients
- Create Client (Create OAuth client ID)
- Application Type: Desktop app
- Name: SomeName
- Click ‘Create’
- In Clients
- Copy the Client ID from the pop-up dialogue
- Copy the Client Secret from the pop-up dialogue
- Download a JSON file and save it to the Pi as
assistant.json
- Create a
tld.domain/auth
folder on a web server - Create a
tld.domain/auth/index.php
file from the OAuth code below - Copy:
- Client ID
- Client Secret
- Into
tld.domain/auth/index.php
- Launch the python
aiy
script in a Terminal on the Pi (and wait) - The Terminal will prompt visiting a URL
- Go to the URL, agree and wait for the Authorisation response code
- Paste said code in the waiting Terminal
- Go through the Authorisation acceptance steps
The Code
As I was reading about how to configure OAuth I came across this site .
The whole site is well worth a read as it gives an in depth overview of how OAuth works.
Embedded within that site is a link to this repo .
The following code is a direct copy of the Google OAuth example from the that repo and is duplicated here for posterity purposes.
Google OAuth
<?php
// Fill these out with the values you got from Google
$googleClientID = '';
$googleClientSecret = '';
// This is the URL we'll send the user to first to get their authorization
$authorizationEndpoint = 'https://accounts.google.com/o/oauth2/v2/auth';
// This is Google's OpenID Connect token endpoint
$tokenEndpoint = 'https://www.googleapis.com/oauth2/v4/token';
// The URL for this script, used as the redirect URL
// If PHP isn't setting these right you can put the full URL here manually
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
$redirectURL = $protocol . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
// Start a session so we have a place to store things between redirects
session_start();
// Start the login process by sending the user
// to Google's authorization page
if(isset($_GET['action']) && $_GET['action'] == 'login') {
unset($_SESSION['user_id']);
// Generate a random hash and store in the session
$_SESSION['state'] = bin2hex(random_bytes(16));
$params = array(
'response_type' => 'code',
'client_id' => $googleClientID,
'redirect_uri' => $redirectURL,
'scope' => 'openid email',
'state' => $_SESSION['state']
);
// Redirect the user to Google's authorization page
header('Location: ' . $authorizationEndpoint . '?' . http_build_query($params));
die();
}
if(isset($_GET['action']) && $_GET['action'] == 'logout') {
unset($_SESSION['user_id']);
header('Location: '.$redirectURL);
die();
}
// When Google redirects the user back here, there will be a "code" and "state"
// parameter in the query string
if(isset($_GET['code'])) {
// Verify the state matches our stored state
if(!isset($_GET['state']) || $_SESSION['state'] != $_GET['state']) {
header('Location: ' . $redirectURL . '?error=invalid_state');
die();
}
// Exchange the auth code for a token
$ch = curl_init($tokenEndpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
'grant_type' => 'authorization_code',
'client_id' => $googleClientID,
'client_secret' => $googleClientSecret,
'redirect_uri' => $redirectURL,
'code' => $_GET['code']
]));
$response = curl_exec($ch);
$data = json_decode($response, true);
// Note: You'd probably want to use a real JWT library
// but this will do in a pinch. This is only safe to do
// because the ID token came from the https connection
// from Google rather than an untrusted browser redirect
// Split the JWT string into three parts
$jwt = explode('.', $data['id_token']);
// Extract the middle part, base64 decode it, then json_decode it
$userinfo = json_decode(base64_decode($jwt[1]), true);
$_SESSION['user_id'] = $userinfo['sub'];
$_SESSION['email'] = $userinfo['email'];
// While we're at it, let's store the access token and id token
// so we can use them later
$_SESSION['access_token'] = $data['access_token'];
$_SESSION['id_token'] = $data['id_token'];
$_SESSION['userinfo'] = $userinfo;
header('Location: ' . $redirectURL);
die();
}
// If there is a user ID in the session
// the user is already logged in
if(!isset($_GET['action'])) {
if(!empty($_SESSION['user_id'])) {
echo '<h3>Logged In</h3>';
echo '<p>User ID: '.$_SESSION['user_id'].'</p>';
echo '<p>Email: '.$_SESSION['email'].'</p>';
echo '<p><a href="?action=logout">Log Out</a></p>';
echo '<h3>ID Token</h3>';
echo '<pre>';
print_r($_SESSION['userinfo']);
echo '</pre>';
echo '<h3>User Info</h3>';
echo '<pre>';
$ch = curl_init('https://www.googleapis.com/oauth2/v3/userinfo');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer '.$_SESSION['access_token']
]);
curl_exec($ch);
echo '</pre>';
} else {
echo '<h3>Not logged in</h3>';
echo '<p><a href="?action=login">Log In</a></p>';
}
die();
}
Yes, it’s more complicated.
Yes, there are more steps.
But my Google box is back to shouting at me again.
The Future
Google are known for changing stuff.
I’m sure this authorisation process will change too.
Finally
You might also like to read an illustrated guide to OAuth , though by now you are probably fed up with the whole thing.