Skip to content

Commit

Permalink
Getting Started: Authenticate Users (#207)
Browse files Browse the repository at this point in the history
  • Loading branch information
bshaffer committed Sep 25, 2019
1 parent 391f0de commit 361e540
Show file tree
Hide file tree
Showing 10 changed files with 350 additions and 4 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

[![Build Status][travis-badge]][travis-link]

This repository contains the complete sample code for the PHP Getting Started on
Google Cloud Platform tutorials. Please refer to the tutorials for instructions
on configuring, running, and deploying these samples.
This repository contains the code for getting started with PHP on Google Cloud
Platform. Please follow the tutorials for instructions on using these samples.

* [Getting started on PHP][getting-started]
* [Getting started with PHP][getting-started]
* [Authenticating users on PHP][authenticate-users]
* [Session handling with PHP][sessions]

## Contributing changes
Expand All @@ -20,4 +20,5 @@ on configuring, running, and deploying these samples.
[travis-badge]: https://travis-ci.org/GoogleCloudPlatform/getting-started-php.svg?branch=master
[travis-link]: https://travis-ci.org/GoogleCloudPlatform/getting-started-php
[getting-started]: http://cloud.google.com/php/getting-started
[authenticate-users]: http://cloud.google.com/php/getting-started/authenticate-users
[sessions]: http://cloud.google.com/php/getting-started/sessions
2 changes: 2 additions & 0 deletions authenticate-users/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
vendor
composer.lock
8 changes: 8 additions & 0 deletions authenticate-users/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Authenticating users with PHP on Google Cloud Platform

This directory contains the complete sample code for authenticating users with
PHP on Google Cloud Platform. Follow the tutorial to run the code:

* [Authenticating users on PHP][authenticate-users]

[authenticate-users]: http://cloud.google.com/php/getting-started/authenticate-users
1 change: 1 addition & 0 deletions authenticate-users/app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
runtime: php72
8 changes: 8 additions & 0 deletions authenticate-users/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"require": {
"php": ">=7.1",
"google/cloud-core": "^1.32",
"kelvinmo/simplejwt": "^0.2.5",
"ralouphie/getallheaders": "^3.0"
}
}
103 changes: 103 additions & 0 deletions authenticate-users/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

# [START getting_started_auth_all]
require_once __DIR__ . '/vendor/autoload.php';

# [START getting_started_auth_certs]
/**
* Returns a dictionary of current Google public key certificates for
* validating Google-signed JWTs.
*/
function certs() : string
{
$client = new GuzzleHttp\Client();
$response = $client->get(
'https://www.gstatic.com/iap/verify/public_key-jwk'
);
return $response->getBody();
}
# [END getting_started_auth_certs]

# [START getting_started_auth_audience]
/**
* Returns the audience value (the JWT 'aud' property) for the current
* running instance. Since this involves a metadata lookup, the result is
* cached when first requested for faster future responses.
*/
function audience() : string
{
$metadata = new Google\Cloud\Core\Compute\Metadata();
$projectNumber = $metadata->getNumericProjectId();
$projectId = $metadata->getProjectId();
$audience = sprintf('/projects/%s/apps/%s', $projectNumber, $projectId);
return $audience;
}
# [END getting_started_auth_audience]

# [START getting_started_auth_validate_assertion]
/**
* Checks that the JWT assertion is valid (properly signed, for the
* correct audience) and if so, returns strings for the requesting user's
* email and a persistent user ID. If not valid, returns null for each field.
*
* @param string $assertion The JWT string to assert.
* @param string $certs The certificates to use for the assertion validation.
* @param string $audience The required audience of the JWT.
* @return array containing [$email, $id], or [null, null] on failed validation.
*/
function validate_assertion(string $assertion, string $certs, string $audience) : array
{
$jwkset = new SimpleJWT\Keys\KeySet();
$jwkset->load($certs);
try {
$info = SimpleJWT\JWT::decode(
$assertion,
$jwkset,
'ES256'
);
if ($info->getClaim('aud') != $audience) {
throw new Exception('Audience did not match');
}
return [$info->getClaim('email'), $info->getClaim('sub')];
} catch (Exception $e) {
printf('Failed to validate assertion: %s', $e->getMessage());
return [null, null];
}
}
# [END getting_started_auth_validate_assertion]

# [START getting_started_auth_front_controller]
/**
* This is an example of a front controller for a flat file PHP site. Using a
* static list provides security against URL injection by default.
*/
switch (@parse_url($_SERVER['REQUEST_URI'])['path']) {
case '/':
$assertion = getallheaders()['X-Goog-Iap-Jwt-Assertion'] ?? '';
list($email, $id) = validate_assertion($assertion, certs(), audience());
if ($email) {
printf("<h1>Hello %s</h1>", $email);
}
break;
case ''; break; // Nothing to do, we're running our tests
default:
http_response_code(404);
exit('Not Found');
}
# [END getting_started_auth_front_controller]
# [END getting_started_auth_all]
23 changes: 23 additions & 0 deletions authenticate-users/phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2019 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<phpunit bootstrap="vendor/autoload.php" convertWarningsToExceptions="false">
<testsuites>
<testsuite name="PHP Authenticate Users Test Suite">
<directory>test</directory>
</testsuite>
</testsuites>
</phpunit>
96 changes: 96 additions & 0 deletions authenticate-users/test/FunctionsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

use PHPUnit\Framework\TestCase;

/**
* Test for the application controller.
*/
class ControllersTest extends TestCase
{
/**
* Test private/public keys generated from http://jwt.io
*
* Private Key:
* -----BEGIN PUBLIC KEY-----
* MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
* q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==
* -----END PUBLIC KEY-----
*
* Public Key:
* -----BEGIN PRIVATE KEY-----
* MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2
* OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r
* 1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G
* -----END PRIVATE KEY-----
*/
private static $testCert = [
'kty' => 'EC',
'crv' => 'P-256',
'x' => 'EVs_o5-uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf84',
'y' => 'kGe5DgSIycKp8w9aJmoHhB1sB3QTugfnRWm5nU_TzsY',
'kid' => '19J8y7Z',
];

public static function setUpBeforeClass() : void
{
require_once __DIR__ . '/../index.php';
}

public function testInvalidJwt()
{
validate_assertion('fake_jwt', '{"keys":[]}', '');
$this->expectOutputRegex('/Failed to validate assertion: Cannot decode compact serialisation/');
}

public function testInvalidAudience()
{
$testAssertion = 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImF1ZCI6ImZvbyJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwiYXVkIjoiZm9vIiwiZW1haWwiOiJmb29AZ29vZ2xlLmNvbSJ9.rKr6N3u3inkeTIlVaJ24iIb_8C-x-WKcDw65cwaoxb27ZclFSFQktFCGLW1ochruuL0OD8-GViv1vOSyKpXb_g';
$testAudience = 'invalidaudience';

list($email, $id) = validate_assertion(
$testAssertion,
json_encode(['keys' => [self::$testCert]]),
$testAudience
);

$this->expectOutputRegex('/Failed to validate assertion: Audience did not match/');
}

public function testValidAssertion()
{
$testAssertion = 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImF1ZCI6ImZvbyJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwiYXVkIjoiZm9vIiwiZW1haWwiOiJmb29AZ29vZ2xlLmNvbSJ9.rKr6N3u3inkeTIlVaJ24iIb_8C-x-WKcDw65cwaoxb27ZclFSFQktFCGLW1ochruuL0OD8-GViv1vOSyKpXb_g';
$testAudience = 'foo';

list($email, $id) = validate_assertion(
$testAssertion,
json_encode(['keys' => [self::$testCert]]),
$testAudience
);

$this->assertEquals('[email protected]', $email);
$this->assertEquals('1234567890', $id);
$this->expectOutputRegex('//');
}

public function testCerts()
{
$certs = certs();
$this->assertTrue(false !== $json = json_decode($certs, true));
$this->assertArrayHasKey('keys', $json);
}
}
96 changes: 96 additions & 0 deletions authenticate-users/test/indexTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

use PHPUnit\Framework\TestCase;

/**
* Test for application controllers
*/
class indexTest extends TestCase
{
/**
* Test private/public keys generated from http://jwt.io
*
* Private Key:
* -----BEGIN PUBLIC KEY-----
* MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
* q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==
* -----END PUBLIC KEY-----
*
* Public Key:
* -----BEGIN PRIVATE KEY-----
* MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2
* OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r
* 1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G
* -----END PRIVATE KEY-----
*/
private static $testCert = [
'kty' => 'EC',
'crv' => 'P-256',
'x' => 'EVs_o5-uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf84',
'y' => 'kGe5DgSIycKp8w9aJmoHhB1sB3QTugfnRWm5nU_TzsY',
'kid' => '19J8y7Z',
];

public static function setUpBeforeClass() : void
{
require_once __DIR__ . '/../index.php';
}

public function testInvalidJwt()
{
validate_assertion('fake_jwt', '{"keys":[]}', '');
$this->expectOutputRegex('/Failed to validate assertion: Cannot decode compact serialisation/');
}

public function testInvalidAudience()
{
$testAssertion = 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImF1ZCI6ImZvbyJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwiYXVkIjoiZm9vIiwiZW1haWwiOiJmb29AZ29vZ2xlLmNvbSJ9.rKr6N3u3inkeTIlVaJ24iIb_8C-x-WKcDw65cwaoxb27ZclFSFQktFCGLW1ochruuL0OD8-GViv1vOSyKpXb_g';
$testAudience = 'invalidaudience';

list($email, $id) = validate_assertion(
$testAssertion,
json_encode(['keys' => [self::$testCert]]),
$testAudience
);

$this->expectOutputRegex('/Failed to validate assertion: Audience did not match/');
}

public function testValidAssertion()
{
$testAssertion = 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImF1ZCI6ImZvbyJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwiYXVkIjoiZm9vIiwiZW1haWwiOiJmb29AZ29vZ2xlLmNvbSJ9.rKr6N3u3inkeTIlVaJ24iIb_8C-x-WKcDw65cwaoxb27ZclFSFQktFCGLW1ochruuL0OD8-GViv1vOSyKpXb_g';
$testAudience = 'foo';

list($email, $id) = validate_assertion(
$testAssertion,
json_encode(['keys' => [self::$testCert]]),
$testAudience
);

$this->assertEquals('[email protected]', $email);
$this->assertEquals('1234567890', $id);
$this->expectOutputRegex('//');
}

public function testCerts()
{
$certs = certs();
$this->assertTrue(false !== $json = json_decode($certs, true));
$this->assertArrayHasKey('keys', $json);
}
}
8 changes: 8 additions & 0 deletions bookshelf/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Getting Started with PHP on Google Cloud Platform

This directory contains the code for deploying a basic PHP app to Google Cloud
Platform. Follow the tutorial to use this sample.

* [Getting started with PHP][getting-started]

[getting-started]: http://cloud.google.com/php/getting-started

0 comments on commit 361e540

Please sign in to comment.