Skip to content

Commit

Permalink
Sample for user auth with IAP (#334)
Browse files Browse the repository at this point in the history
  • Loading branch information
engelke authored and fhinkel committed Sep 1, 2019
1 parent 90a4cdb commit 674dec2
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 0 deletions.
5 changes: 5 additions & 0 deletions authenticating-users/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Authenticating users

This folder contains the sample code for the *Authenticating users*
tutorial. Please refer to the tutorial for instructions on configuring, running,
and deploying this sample.
108 changes: 108 additions & 0 deletions authenticating-users/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* 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 strict';

const express = require('express');
const got = require('got');
const jwt = require('jsonwebtoken');

const app = express();

// Cache externally fetched information for future invocations
let certs;
let aud;

async function certificates() {
if (!certs) {
let response = await got('https://www.gstatic.com/iap/verify/public_key');
certs = JSON.parse(response.body);
}

return certs;
}

async function get_metadata(itemName) {
const endpoint = 'http://metadata.google.internal';
const path = '/computeMetadata/v1/project/';
const url = endpoint + path + itemName;

let response = await got(url, {
headers: {'Metadata-Flavor': 'Google'}
});
return response.body;
}

async function audience() {
if (!aud) {
let project_number = await get_metadata('numeric-project-id');
let project_id = await get_metadata('project-id');

aud = '/projects/' + project_number + '/apps/' + project_id;
}

return aud;
}

async function validate_assertion(assertion) {
// Decode the header to determine which certificate signed the assertion
const encodedHeader = assertion.split('.')[0];
const decodedHeader = Buffer.from(encodedHeader, 'base64').toString('utf8');
const header = JSON.parse(decodedHeader);
const keyId = header.kid;

// Fetch the current certificates and verify the signature on the assertion
const certs = await certificates();
const payload = jwt.verify(assertion, certs[keyId]);

// Check that the assertion's audience matches ours
const aud = await audience();
if (payload.aud !== aud) {
throw new Error('Audience mismatch. {$payload.aud} should be {$aud}.');
}

// Return the two relevant pieces of information
return {
'email': payload.email,
'sub': payload.sub,
};

}

app.get('/', (req, res) => {
const assertion = req.header('X-Goog-IAP-JWT-Assertion');
validate_assertion(assertion).then((info) => {
res
.status(200)
.send('Hello ' + info.email)
.end();
}).catch((error) => {
console.log(error);

res
.status(200)
.send('Hello None')
.end();
});
});

// Start the server
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`App listening on port ${PORT}`);
console.log('Press Ctrl+C to quit.');
});

module.exports = app;
14 changes: 14 additions & 0 deletions authenticating-users/app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# 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.

runtime: nodejs10
41 changes: 41 additions & 0 deletions authenticating-users/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "iap-authentication",
"description": "Minimal app to use authentication information from IAP.",
"version": "0.0.1",
"private": true,
"license": "Apache-2.0",
"author": "Google LLC",
"repository": {
"type": "git",
"url": "https://github.com/GoogleCloudPlatform/getting-started-nodejs.git"
},
"engines": {
"node": ">=10.0.0"
},
"scripts": {
"deploy": "gcloud app deploy",
"start": "node app.js",
"system-test": "mocha --exit test/*.test.js",
"test": "npm run system-test",
"e2e-test": "repo-tools test deploy"
},
"dependencies": {
"express": "^4.17.1",
"got": "^9.6.0",
"jsonwebtoken": "^8.5.1"
},
"devDependencies": {
"@google-cloud/nodejs-repo-tools": "^3.3.0",
"mocha": "^6.1.4",
"supertest": "^4.0.2"
},
"cloud-repo-tools": {
"test": {
"app": {
"msg": "Hello, world!"
}
},
"requiresKeyFile": true,
"requiresProjectId": true
}
}
16 changes: 16 additions & 0 deletions authenticating-users/test/app.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const app = require('../app');

const request = require('supertest');

describe('GET /', () => {
it('should get 200', done => {
request(app)
.get('/')
.expect(200, done);
}),
it('should get Hello None', done => {
request(app)
.get('/')
.expect('Hello None', done);
});
});

0 comments on commit 674dec2

Please sign in to comment.