Skip to content

Commit e21b4f1

Browse files
author
Nicolas Garnier
committed
Add Authorized HTTPS endpoint sample
Change-Id: I6d65e424119869c14dd56b9568ba280b9f373eaf
1 parent 5c6b2b7 commit e21b4f1

File tree

13 files changed

+306
-39
lines changed

13 files changed

+306
-39
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,20 @@ This quickstart sample demonstrates using **Cloud Functions** triggered by **Fir
3232

3333
This quickstart sample demonstrates using **Cloud Functions** triggered by **PubSub events**. The functions log the PubSub payload in a Hello world message.
3434

35+
### [Authorized HTTP Endpoint](/authorized-https-endpoint)
36+
37+
This samples shows how to restrict an HTTPS Function to only the Firebase users of your app.
38+
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.
39+
Checking the ID token is done with an ExpressJs middleware that also passes the decoded ID token in the Express request object.
40+
41+
Uses an HTTP trigger.
42+
3543
### [Send FCM notifications](fcm-notifications)
3644

3745
This sample demonstrates how to send a Firebase Cloud Messaging (FCM) notification from a Realtime Database triggered Function when users get new followers. The sample also features a Web UI to experience the FCM notification.
3846

47+
Uses a Realtime Database trigger.
48+
3949
### [Authorize with LinkedIn](/linkedin-auth)
4050

4151
Demonstrates how to authorize with a 3rd party sign-in mechanism (LinkedIn in this case), create a Firebase custom auth token, update the user's profile and authorize Firebase.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Authorized HTTPS Endpoint
2+
3+
This samples shows how to restrict an HTTPS Function to only the Firebase users of your app.
4+
5+
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.
6+
7+
Checking the ID token is done with an ExpressJs middleware that also passes the decoded ID token in the Express request object.
8+
9+
Once authrorized the function respond with 'Hello <username>'.
10+
11+
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.
12+
13+
14+
## Setting up the sample
15+
16+
1. Create a Firebase Project using the [Firebase Console](https://console.firebase.google.com).
17+
1. Enable the **Google** Provider in the **Auth** section.
18+
1. Clone or download this repo and open the `authenticated-https-endpoint` directory.
19+
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.
20+
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`.
21+
1. Configure the CLI locally by using `firebase use --add` and select your project in the list.
22+
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`.
23+
1. Configure the CLI locally by using `firebase use --add` and select your project in the list.
24+
1. Install dependencies locally by running: `cd functions; npm install; cd -`
25+
26+
27+
## Deploy and test
28+
29+
This sample comes with a web-based UI for testing the function. To test it out:
30+
31+
1. Deploy your project using `firebase deploy`
32+
1. Open the app using `firebase open hosting:site`, this will open a browser.
33+
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.
34+
35+
36+
## Contributing
37+
38+
We'd love that you contribute to the project. Before doing so please read our [Contributor guide](../CONTRIBUTING.md).
39+
40+
41+
## License
42+
43+
© Google, 2017. Licensed under an [Apache-2](../LICENSE) license.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"hosting": {
3+
"public": "public"
4+
}
5+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* Copyright 2016 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
'use strict';
17+
18+
const functions = require('firebase-functions');
19+
const admin = require('firebase-admin');
20+
const credentials = require('./service-account.json');
21+
admin.initializeApp({
22+
credential: admin.credential.cert(credentials),
23+
databaseURL: `https://${credentials.project_id}.firebaseio.com`
24+
});
25+
const express = require('express');
26+
const cors = require('cors')({origin: true});
27+
const app = express();
28+
29+
// Express middleware that validates Firebase ID Tokens passed in the Authorization HTTP header.
30+
// The Firebase ID token needs to be passed as a Bearer token in the Authorization HTTP header like this:
31+
// `Authorization: Bearer <Firebase ID Token>`.
32+
// when decoded successfully, the ID Token content will be added as `req.user`.
33+
const validateFirebaseIdToken = (req, res, next) => {
34+
console.log('Check if request is authorized with Firebase ID token');
35+
36+
if (!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) {
37+
console.error('No Firebase ID token was passed as a Bearer token in the Authorization header.',
38+
'Make sure you authorize your request by providing the following HTTP header:',
39+
'Authorization: Bearer <Firebase ID Token>');
40+
res.status(403).send('Unauthorized');
41+
return;
42+
}
43+
const idToken = req.headers.authorization.split('Bearer ')[1];
44+
admin.auth().verifyIdToken(idToken).then(decodedIdToken => {
45+
console.log('ID Token correctly decoded', decodedIdToken);
46+
req.user = decodedIdToken;
47+
next();
48+
}).catch(error => {
49+
console.error('Error while verifying Firebase ID token:', error);
50+
res.status(403).send('Unauthorized');
51+
});
52+
};
53+
54+
app.use(cors);
55+
app.use(validateFirebaseIdToken);
56+
app.get('*', (req, res) => {
57+
res.send(`Hello ${req.user.name}`);
58+
});
59+
60+
// This HTTPS endpoint can only be accessed by your Firebase Users.
61+
// Requests need to be authorized by providing an `Authorization` HTTP header
62+
// with value `Bearer <Firebase ID Token>`.
63+
exports.authorizedHello = functions.https.onRequest(app);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "time-server-functions",
3+
"description": "A simple time server using HTTPS Cloud Function",
4+
"dependencies": {
5+
"cors": "^2.8.1",
6+
"express": "^4.14.1",
7+
"firebase-admin": "^4.1.1",
8+
"firebase-functions": "https://storage.googleapis.com/firebase-preview-drop/node/firebase-functions/firebase-functions-preview.latest.tar.gz"
9+
}
10+
}
3.46 KB
Loading
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<!doctype html>
2+
<!--
3+
Copyright 2016 Google Inc. All rights reserved.
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
https://www.apache.org/licenses/LICENSE-2.0
8+
Unless required by applicable law or agreed to in writing, software
9+
distributed under the License is distributed on an "AS IS" BASIS,
10+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
See the License for the specific language governing permissions and
12+
limitations under the License
13+
-->
14+
<html lang="en">
15+
<head>
16+
<meta charset="utf-8">
17+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
18+
<meta name="description" content="Demonstrates how to send a welcome email using Firebase Functions.">
19+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
20+
<title>Welcome email demo</title>
21+
22+
<!-- Material Design Lite -->
23+
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
24+
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
25+
<link rel="stylesheet" href="https://code.getmdl.io/1.1.3/material.blue_grey-orange.min.css">
26+
<script defer src="https://code.getmdl.io/1.1.3/material.min.js"></script>
27+
28+
<link rel="stylesheet" href="main.css">
29+
</head>
30+
<body>
31+
<div class="demo-layout mdl-layout mdl-js-layout mdl-layout--fixed-header">
32+
33+
<!-- Header section containing title -->
34+
<header class="mdl-layout__header mdl-color-text--white mdl-color--light-blue-700">
35+
<div class="mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-grid">
36+
<div class="mdl-layout__header-row mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-cell--8-col-desktop">
37+
<h3>Authorized HTTP Function demo</h3>
38+
</div>
39+
</div>
40+
</header>
41+
<main class="mdl-layout__content mdl-color--grey-100">
42+
<div class="mdl-cell--12-col mdl-cell--12-col-tablet mdl-grid">
43+
44+
<!-- Card containing the sign-in UI -->
45+
<div id="demo-signed-out-card" class="mdl-card mdl-shadow--2dp mdl-cell">
46+
<div class="mdl-card__supporting-text mdl-color-text--grey-600">
47+
<p>
48+
This web application demonstrates how you can send an authorized XHR Request to an HTTPS Function.
49+
<strong>Now sign in!</strong>
50+
</p>
51+
<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>
52+
</div>
53+
</div>
54+
55+
<!-- Card containing the signed-in UI -->
56+
<div id="demo-signed-in-card" class="mdl-card mdl-shadow--2dp mdl-cell">
57+
<div class="mdl-card__supporting-text mdl-color-text--grey-600">
58+
<p>
59+
Sending Authorized XHR to:<br>
60+
<span id="demo-url"></span><br><br>
61+
Response: <span id="demo-response">...</span>
62+
</p>
63+
<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>
64+
</div>
65+
</div>
66+
</div>
67+
</main>
68+
</div>
69+
70+
<!-- Firebase -->
71+
<!-- ***********************************************************************************************************************
72+
* TODO(DEVELOPER): Paste the initialization snippet from: Firebase Console > Overview > Add Firebase to your web app. *
73+
*********************************************************************************************************************** -->
74+
75+
<script src="main.js"></script>
76+
</body>
77+
</html>
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2015 Google Inc. All Rights Reserved.
2+
* Copyright 2016 Google Inc. All Rights Reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,35 +28,19 @@ html, body {
2828
.mdl-layout__header-row {
2929
padding: 0;
3030
}
31-
#message-form {
32-
display: flex;
33-
flex-direction: column;
34-
}
35-
#message-form button {
36-
max-width: 300px;
37-
}
38-
#message-list {
39-
padding: 0;
40-
width: 100%;
41-
}
42-
#message-list > div {
43-
padding: 15px;
44-
border-bottom: 1px #f1f1f1 solid;
45-
}
4631
h3 {
4732
background: url('firebase-logo.png') no-repeat;
4833
background-size: 40px;
4934
padding-left: 50px;
5035
}
5136
#demo-signed-out-card,
52-
#demo-signed-in-card,
53-
#demo-subscribe-button,
54-
#demo-unsubscribe-button,
55-
#demo-subscribed-text-container,
56-
#demo-unsubscribed-text-container {
37+
#demo-signed-in-card {
5738
display: none;
5839
}
59-
#demo-subscribe-button,
60-
#demo-unsubscribe-button {
61-
margin-right: 20px;
40+
#demo-url,
41+
#demo-response {
42+
font-weight: bold;
43+
}
44+
#demo-signed-in-card {
45+
width: 600px;
6246
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
* Copyright 2016 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
'use strict';
17+
18+
// Initializes the Demo.
19+
function Demo() {
20+
document.addEventListener('DOMContentLoaded', function() {
21+
// Shortcuts to DOM Elements.
22+
this.signInButton = document.getElementById('demo-sign-in-button');
23+
this.signOutButton = document.getElementById('demo-sign-out-button');
24+
this.responseContainer = document.getElementById('demo-response');
25+
this.urlContainer = document.getElementById('demo-url');
26+
this.helloFunctionUrl = 'https://us-central1-' + config.authDomain.split('.')[0] + '.cloudfunctions.net/authorizedHello/';
27+
this.signedOutCard = document.getElementById('demo-signed-out-card');
28+
this.signedInCard = document.getElementById('demo-signed-in-card');
29+
30+
// Bind events.
31+
this.signInButton.addEventListener('click', this.signIn.bind(this));
32+
this.signOutButton.addEventListener('click', this.signOut.bind(this));
33+
firebase.auth().onAuthStateChanged(this.onAuthStateChanged.bind(this));
34+
}.bind(this));
35+
}
36+
37+
// Triggered on Firebase auth state change.
38+
Demo.prototype.onAuthStateChanged = function(user) {
39+
if (user) {
40+
this.urlContainer.textContent = this.helloFunctionUrl;
41+
this.signedOutCard.style.display = 'none';
42+
this.signedInCard.style.display = 'block';
43+
this.startFunctionsRequest();
44+
} else {
45+
this.signedOutCard.style.display = 'block';
46+
this.signedInCard.style.display = 'none';
47+
}
48+
};
49+
50+
// Initiates the sign-in flow using LinkedIn sign in in a popup.
51+
Demo.prototype.signIn = function() {
52+
firebase.auth().signInWithPopup(new firebase.auth.GoogleAuthProvider());
53+
};
54+
55+
// Signs-out of Firebase.
56+
Demo.prototype.signOut = function() {
57+
firebase.auth().signOut();
58+
};
59+
60+
// Does an authenticated request to a Firebase Functions endpoint.
61+
Demo.prototype.startFunctionsRequest = function() {
62+
firebase.auth().currentUser.getToken().then(function(token) {
63+
console.log('Sending request to', this.helloFunctionUrl, 'with ID token', token);
64+
var req = new XMLHttpRequest();
65+
req.onload = function() {
66+
this.responseContainer.innerText = req.responseText;
67+
}.bind(this);
68+
req.onerror = function() {
69+
this.responseContainer.innerText = 'There was an error';
70+
}.bind(this);
71+
req.open('GET', this.helloFunctionUrl, true);
72+
req.setRequestHeader('Authorization', 'Bearer ' + token);
73+
req.send();
74+
}.bind(this));
75+
};
76+
77+
// Load the demo.
78+
window.demo = new Demo();

quickstarts/email-users/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,13 @@ This sample comes with a web-based UI for testing the function. To test it out:
4444
1. Deploy your project using `firebase deploy`
4545
1. Open the app using `firebase open hosting:site`, this will open a browser.
4646
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.
47+
48+
49+
## Contributing
50+
51+
We'd love that you contribute to the project. Before doing so please read our [Contributor guide](../../CONTRIBUTING.md).
52+
53+
54+
## License
55+
56+
© Google, 2016. Licensed under an [Apache-2](../../LICENSE) license.

0 commit comments

Comments
 (0)