13 changed files with 306 additions and 39 deletions
@ -0,0 +1,43 @@ |
|||
# Authorized HTTPS Endpoint |
|||
|
|||
This samples shows how to restrict an HTTPS Function to only the Firebase users of your app. |
|||
|
|||
Only users who pass a valid Firebase ID token as a Bearer token in the Authorization header of the HTTP request are authorized to use the function. |
|||
|
|||
Checking the ID token is done with an ExpressJs middleware that also passes the decoded ID token in the Express request object. |
|||
|
|||
Once authrorized the function respond with 'Hello <username>'. |
|||
|
|||
This sample comes with a simple web-based UI which code is in [public](public) directory that lets you sign-in Firebase and initiates an authorized XHR to the Function. |
|||
|
|||
|
|||
## Setting up the sample |
|||
|
|||
1. Create a Firebase Project using the [Firebase Console](https://console.firebase.google.com). |
|||
1. Enable the **Google** Provider in the **Auth** section. |
|||
1. Clone or download this repo and open the `authenticated-https-endpoint` directory. |
|||
1. Paste the Web initialization snippet from: **Firebase Console > Overview > Add Firebase to your web app** in the `public/index.html` where the `TODO` is located. |
|||
1. Download a Service Account credentials from: **Firebase Console > ⚙ > Project Settings > SERVICE ACCOUNTS > GENERATE NEW PRIVATE KEY** and save the file as `functions/service-account.json`. |
|||
1. Configure the CLI locally by using `firebase use --add` and select your project in the list. |
|||
1. You must have the Firebase CLI installed. If you don't have it install it with `npm install -g firebase-tools` and then configure it with `firebase login`. |
|||
1. Configure the CLI locally by using `firebase use --add` and select your project in the list. |
|||
1. Install dependencies locally by running: `cd functions; npm install; cd -` |
|||
|
|||
|
|||
## Deploy and test |
|||
|
|||
This sample comes with a web-based UI for testing the function. To test it out: |
|||
|
|||
1. Deploy your project using `firebase deploy` |
|||
1. Open the app using `firebase open hosting:site`, this will open a browser. |
|||
1. Sign in the web app in the browser using Google Sign-In and delete your account using the button on the web app. You should receive email confirmations for each actions. |
|||
|
|||
|
|||
## Contributing |
|||
|
|||
We'd love that you contribute to the project. Before doing so please read our [Contributor guide](../CONTRIBUTING.md). |
|||
|
|||
|
|||
## License |
|||
|
|||
© Google, 2017. Licensed under an [Apache-2](../LICENSE) license. |
@ -0,0 +1,5 @@ |
|||
{ |
|||
"hosting": { |
|||
"public": "public" |
|||
} |
|||
} |
@ -0,0 +1,63 @@ |
|||
/** |
|||
* Copyright 2016 Google Inc. All Rights Reserved. |
|||
* |
|||
* 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 functions = require('firebase-functions'); |
|||
const admin = require('firebase-admin'); |
|||
const credentials = require('./service-account.json'); |
|||
admin.initializeApp({ |
|||
credential: admin.credential.cert(credentials), |
|||
databaseURL: `https://${credentials.project_id}.firebaseio.com` |
|||
}); |
|||
const express = require('express'); |
|||
const cors = require('cors')({origin: true}); |
|||
const app = express(); |
|||
|
|||
// Express middleware that validates Firebase ID Tokens passed in the Authorization HTTP header.
|
|||
// The Firebase ID token needs to be passed as a Bearer token in the Authorization HTTP header like this:
|
|||
// `Authorization: Bearer <Firebase ID Token>`.
|
|||
// when decoded successfully, the ID Token content will be added as `req.user`.
|
|||
const validateFirebaseIdToken = (req, res, next) => { |
|||
console.log('Check if request is authorized with Firebase ID token'); |
|||
|
|||
if (!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) { |
|||
console.error('No Firebase ID token was passed as a Bearer token in the Authorization header.', |
|||
'Make sure you authorize your request by providing the following HTTP header:', |
|||
'Authorization: Bearer <Firebase ID Token>'); |
|||
res.status(403).send('Unauthorized'); |
|||
return; |
|||
} |
|||
const idToken = req.headers.authorization.split('Bearer ')[1]; |
|||
admin.auth().verifyIdToken(idToken).then(decodedIdToken => { |
|||
console.log('ID Token correctly decoded', decodedIdToken); |
|||
req.user = decodedIdToken; |
|||
next(); |
|||
}).catch(error => { |
|||
console.error('Error while verifying Firebase ID token:', error); |
|||
res.status(403).send('Unauthorized'); |
|||
}); |
|||
}; |
|||
|
|||
app.use(cors); |
|||
app.use(validateFirebaseIdToken); |
|||
app.get('*', (req, res) => { |
|||
res.send(`Hello ${req.user.name}`); |
|||
}); |
|||
|
|||
// This HTTPS endpoint can only be accessed by your Firebase Users.
|
|||
// Requests need to be authorized by providing an `Authorization` HTTP header
|
|||
// with value `Bearer <Firebase ID Token>`.
|
|||
exports.authorizedHello = functions.https.onRequest(app); |
@ -0,0 +1,10 @@ |
|||
{ |
|||
"name": "time-server-functions", |
|||
"description": "A simple time server using HTTPS Cloud Function", |
|||
"dependencies": { |
|||
"cors": "^2.8.1", |
|||
"express": "^4.14.1", |
|||
"firebase-admin": "^4.1.1", |
|||
"firebase-functions": "https://storage.googleapis.com/firebase-preview-drop/node/firebase-functions/firebase-functions-preview.latest.tar.gz" |
|||
} |
|||
} |
After Width: | Height: | Size: 3.5 KiB |
@ -0,0 +1,77 @@ |
|||
<!doctype html> |
|||
<!-- |
|||
Copyright 2016 Google Inc. All rights reserved. |
|||
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 |
|||
https://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 |
|||
--> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
|||
<meta name="description" content="Demonstrates how to send a welcome email using Firebase Functions."> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<title>Welcome email demo</title> |
|||
|
|||
<!-- Material Design Lite --> |
|||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"> |
|||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"> |
|||
<link rel="stylesheet" href="https://code.getmdl.io/1.1.3/material.blue_grey-orange.min.css"> |
|||
<script defer src="https://code.getmdl.io/1.1.3/material.min.js"></script> |
|||
|
|||
<link rel="stylesheet" href="main.css"> |
|||
</head> |
|||
<body> |
|||
<div class="demo-layout mdl-layout mdl-js-layout mdl-layout--fixed-header"> |
|||
|
|||
<!-- Header section containing title --> |
|||
<header class="mdl-layout__header mdl-color-text--white mdl-color--light-blue-700"> |
|||
<div class="mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-grid"> |
|||
<div class="mdl-layout__header-row mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-cell--8-col-desktop"> |
|||
<h3>Authorized HTTP Function demo</h3> |
|||
</div> |
|||
</div> |
|||
</header> |
|||
<main class="mdl-layout__content mdl-color--grey-100"> |
|||
<div class="mdl-cell--12-col mdl-cell--12-col-tablet mdl-grid"> |
|||
|
|||
<!-- Card containing the sign-in UI --> |
|||
<div id="demo-signed-out-card" class="mdl-card mdl-shadow--2dp mdl-cell"> |
|||
<div class="mdl-card__supporting-text mdl-color-text--grey-600"> |
|||
<p> |
|||
This web application demonstrates how you can send an authorized XHR Request to an HTTPS Function. |
|||
<strong>Now sign in!</strong> |
|||
</p> |
|||
<button id="demo-sign-in-button" class="mdl-color-text--grey-700 mdl-button--raised mdl-button mdl-js-button mdl-js-ripple-effect"><i class="material-icons">account_circle</i> Sign in with Google</button> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Card containing the signed-in UI --> |
|||
<div id="demo-signed-in-card" class="mdl-card mdl-shadow--2dp mdl-cell"> |
|||
<div class="mdl-card__supporting-text mdl-color-text--grey-600"> |
|||
<p> |
|||
Sending Authorized XHR to:<br> |
|||
<span id="demo-url"></span><br><br> |
|||
Response: <span id="demo-response">...</span> |
|||
</p> |
|||
<button id="demo-sign-out-button" class="mdl-color-text--grey-700 mdl-button--raised mdl-button mdl-js-button mdl-js-ripple-effect">Sign out</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</main> |
|||
</div> |
|||
|
|||
<!-- Firebase --> |
|||
<!-- *********************************************************************************************************************** |
|||
* TODO(DEVELOPER): Paste the initialization snippet from: Firebase Console > Overview > Add Firebase to your web app. * |
|||
*********************************************************************************************************************** --> |
|||
|
|||
<script src="main.js"></script> |
|||
</body> |
|||
</html> |
@ -0,0 +1,78 @@ |
|||
/** |
|||
* Copyright 2016 Google Inc. All Rights Reserved. |
|||
* |
|||
* 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'; |
|||
|
|||
// Initializes the Demo.
|
|||
function Demo() { |
|||
document.addEventListener('DOMContentLoaded', function() { |
|||
// Shortcuts to DOM Elements.
|
|||
this.signInButton = document.getElementById('demo-sign-in-button'); |
|||
this.signOutButton = document.getElementById('demo-sign-out-button'); |
|||
this.responseContainer = document.getElementById('demo-response'); |
|||
this.urlContainer = document.getElementById('demo-url'); |
|||
this.helloFunctionUrl = 'https://us-central1-' + config.authDomain.split('.')[0] + '.cloudfunctions.net/authorizedHello/'; |
|||
this.signedOutCard = document.getElementById('demo-signed-out-card'); |
|||
this.signedInCard = document.getElementById('demo-signed-in-card'); |
|||
|
|||
// Bind events.
|
|||
this.signInButton.addEventListener('click', this.signIn.bind(this)); |
|||
this.signOutButton.addEventListener('click', this.signOut.bind(this)); |
|||
firebase.auth().onAuthStateChanged(this.onAuthStateChanged.bind(this)); |
|||
}.bind(this)); |
|||
} |
|||
|
|||
// Triggered on Firebase auth state change.
|
|||
Demo.prototype.onAuthStateChanged = function(user) { |
|||
if (user) { |
|||
this.urlContainer.textContent = this.helloFunctionUrl; |
|||
this.signedOutCard.style.display = 'none'; |
|||
this.signedInCard.style.display = 'block'; |
|||
this.startFunctionsRequest(); |
|||
} else { |
|||
this.signedOutCard.style.display = 'block'; |
|||
this.signedInCard.style.display = 'none'; |
|||
} |
|||
}; |
|||
|
|||
// Initiates the sign-in flow using LinkedIn sign in in a popup.
|
|||
Demo.prototype.signIn = function() { |
|||
firebase.auth().signInWithPopup(new firebase.auth.GoogleAuthProvider()); |
|||
}; |
|||
|
|||
// Signs-out of Firebase.
|
|||
Demo.prototype.signOut = function() { |
|||
firebase.auth().signOut(); |
|||
}; |
|||
|
|||
// Does an authenticated request to a Firebase Functions endpoint.
|
|||
Demo.prototype.startFunctionsRequest = function() { |
|||
firebase.auth().currentUser.getToken().then(function(token) { |
|||
console.log('Sending request to', this.helloFunctionUrl, 'with ID token', token); |
|||
var req = new XMLHttpRequest(); |
|||
req.onload = function() { |
|||
this.responseContainer.innerText = req.responseText; |
|||
}.bind(this); |
|||
req.onerror = function() { |
|||
this.responseContainer.innerText = 'There was an error'; |
|||
}.bind(this); |
|||
req.open('GET', this.helloFunctionUrl, true); |
|||
req.setRequestHeader('Authorization', 'Bearer ' + token); |
|||
req.send(); |
|||
}.bind(this)); |
|||
}; |
|||
|
|||
// Load the demo.
|
|||
window.demo = new Demo(); |
@ -1,10 +0,0 @@ |
|||
{ |
|||
"rules": { |
|||
"users": { |
|||
"$uid": { |
|||
".read": "auth.uid === $uid", |
|||
".write": "auth.uid === $uid" |
|||
} |
|||
} |
|||
} |
|||
} |
Loading…
Reference in new issue