Nicolas Garnier
8 years ago
10 changed files with 268 additions and 0 deletions
@ -0,0 +1,38 @@ |
|||
# Server-side generated pages w/ Handlebars templating and user sessions |
|||
|
|||
This samples shows how to serve server-side generated HTML pages using the [HandlebarsJs](http://handlebarsjs.com/) templating system. |
|||
|
|||
It also shows how to serve user specific content by passing the Firebase ID token of the signed-in user in a `__session` cookie. |
|||
|
|||
Checking and decoding the ID token passed in the `__session` cookie is done with an ExpressJs middleware. |
|||
|
|||
Some custom scripts in [functions/views/layouts/main.handlebars] maintain the Firebase ID token in the `__session` cookie. |
|||
|
|||
|
|||
## 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 `template-handlebars` directory. |
|||
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 some user information will be displayed on a server-side generated page. |
|||
|
|||
|
|||
## 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,11 @@ |
|||
{ |
|||
"hosting": { |
|||
"public": "public", |
|||
"rewrites": [ |
|||
{ |
|||
"source":"**", |
|||
"function":"app" |
|||
} |
|||
] |
|||
} |
|||
} |
@ -0,0 +1,74 @@ |
|||
/** |
|||
* 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 admin = require('firebase-admin'); |
|||
const cookieParser = require('cookie-parser')(); |
|||
|
|||
// Express middleware that checks if a Firebase ID Tokens is passed in the `Authorization` HTTP
|
|||
// header or the `__session` cookie and decodes it.
|
|||
// 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'); |
|||
|
|||
getIdTokenFromRequest(req, res).then(idToken => { |
|||
if (idToken) { |
|||
addDecodedIdTokenToRequest(idToken, req).then(() => { |
|||
next(); |
|||
}); |
|||
} else { |
|||
next(); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
/** |
|||
* Returns a Promise with the Firebase ID Token if found in the Authorization or the __session cookie. |
|||
*/ |
|||
function getIdTokenFromRequest(req, res) { |
|||
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) { |
|||
console.log('Found "Authorization" header'); |
|||
// Read the ID Token from the Authorization header.
|
|||
return Promise.resolve(req.headers.authorization.split('Bearer ')[1]); |
|||
} |
|||
return new Promise(function(resolve) { |
|||
cookieParser(req, res, () => { |
|||
if (req.cookies && req.cookies.__session) { |
|||
console.log('Found "__session" cookie'); |
|||
// Read the ID Token from cookie.
|
|||
resolve(req.cookies.__session); |
|||
} else { |
|||
resolve(); |
|||
} |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Returns a Promise with the Decoded ID Token and adds it to req.user. |
|||
*/ |
|||
function addDecodedIdTokenToRequest(idToken, req) { |
|||
return admin.auth().verifyIdToken(idToken).then(decodedIdToken => { |
|||
console.log('ID Token correctly decoded', decodedIdToken); |
|||
req.user = decodedIdToken; |
|||
}).catch(error => { |
|||
console.error('Error while verifying Firebase ID token:', error); |
|||
}); |
|||
} |
|||
|
|||
exports.validateFirebaseIdToken = validateFirebaseIdToken; |
@ -0,0 +1,40 @@ |
|||
/** |
|||
* 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'); |
|||
admin.initializeApp(functions.config().firebase); |
|||
const express = require('express'); |
|||
const exphbs = require('express-handlebars'); |
|||
const app = express(); |
|||
const firebaseUser = require('./firebaseUser'); |
|||
|
|||
app.engine('handlebars', exphbs({defaultLayout: 'main'})); |
|||
app.set('view engine', 'handlebars'); |
|||
app.use(firebaseUser.validateFirebaseIdToken); |
|||
|
|||
app.get('/', (req, res) => { |
|||
console.log('Signed-in user:', req.user); |
|||
res.render('user', { |
|||
user: req.user |
|||
}); |
|||
}); |
|||
|
|||
// 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.app = functions.https.onRequest(app); |
@ -0,0 +1,13 @@ |
|||
{ |
|||
"name": "template-handlebars-functions", |
|||
"description": "Use handlebars templating engine on Firebase hosting with Cloud functions", |
|||
"dependencies": { |
|||
"cookie-parser": "^1.4.3", |
|||
"cors": "^2.8.1", |
|||
"express": "^4.14.1", |
|||
"express-handlebars": "^3.0.0", |
|||
"firebase-admin": "^4.1.2", |
|||
"firebase-functions": "^0.5.1", |
|||
"handlebars": "^4.0.8" |
|||
} |
|||
} |
@ -0,0 +1,58 @@ |
|||
<!DOCTYPE html> |
|||
<html> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<title>Example App</title> |
|||
|
|||
<!-- Import and configure the Firebase SDK --> |
|||
<!-- These scripts are made available when the app is served or deployed on Firebase Hosting --> |
|||
<!-- If you do not serve/host your project using Firebase Hosting see https://firebase.google.com/docs/web/setup --> |
|||
<script defer src="/__/firebase/3.9.0/firebase-app.js"></script> |
|||
<script defer src="/__/firebase/3.9.0/firebase-auth.js"></script> |
|||
<script defer src="/__/firebase/init.js"></script> |
|||
|
|||
<script> |
|||
function checkCookie() { |
|||
// Checks if it's likely that there is a signed-in Firebase user and the session cookie expired. |
|||
// In that case we'll hide the body of the page until it will be reloaded after the cookie has been set. |
|||
var hasSessionCookie = document.cookie.indexOf('__session=') !== -1; |
|||
var isProbablySignedInFirebase = typeof Object.keys(localStorage).find(function (key) { |
|||
return key.startsWith('firebase:authUser') |
|||
}) !== 'undefined'; |
|||
if (!hasSessionCookie && isProbablySignedInFirebase) { |
|||
var style = document.createElement('style'); |
|||
style.id = '__bodyHider'; |
|||
style.appendChild(document.createTextNode('body{display:none}')); |
|||
document.head.appendChild(style); |
|||
} |
|||
} |
|||
checkCookie(); |
|||
|
|||
document.addEventListener('DOMContentLoaded', function() { |
|||
// Make sure the Firebase ID Token is always passed as a cookie. |
|||
firebase.auth().addAuthTokenListener(function (idToken) { |
|||
var hadSessionCookie = document.cookie.indexOf('__session=') !== -1; |
|||
document.cookie = '__session=' + idToken + ';max-age=' + (idToken ? 3600 : 0); |
|||
// If there is a change in the auth state compared to what's in the session cookie we'll reload after setting the cookie. |
|||
if ((!hadSessionCookie && idToken) || (hadSessionCookie && !idToken)) { |
|||
window.location.reload(true); |
|||
} else { |
|||
// In the rare case where there was a user but it could not be signed in (for instance the account has been deleted). |
|||
// We un-hide the page body. |
|||
var style = document.getElementById('__bodyHider'); |
|||
if (style) { |
|||
document.head.removeChild(style); |
|||
} |
|||
} |
|||
}); |
|||
}); |
|||
</script> |
|||
|
|||
<link rel="stylesheet" href="/main.css"> |
|||
</head> |
|||
<body> |
|||
|
|||
{{{body}}} |
|||
|
|||
</body> |
|||
</html> |
@ -0,0 +1,11 @@ |
|||
{{#if user}} |
|||
<h1>Hello {{user.name}}!</h1> |
|||
Here is your UID: {{user.uid}} |
|||
<br><br> |
|||
<button onclick="firebase.auth().signOut()">Sign-out</button> |
|||
{{^}} |
|||
<h1>Hey there!</h1> |
|||
Please sign-in! |
|||
<br><br> |
|||
<button onclick="firebase.auth().signInWithPopup(new firebase.auth.GoogleAuthProvider())">Sign-in with Google</button> |
|||
{{/if}} |
After Width: | Height: | Size: 3.5 KiB |
@ -0,0 +1,19 @@ |
|||
/** |
|||
* Copyright 2017 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. |
|||
*/ |
|||
|
|||
html, body { |
|||
font-family: 'Roboto', 'Helvetica', sans-serif; |
|||
} |
Loading…
Reference in new issue