Browse Source
Try it here: https://titanium-firebase-linkedin.firebaseapp.com/ Change-Id: I59575e26633ed6d9d4b07a79de6db6e78b44f609ryanpbrewster-patch-1
Nicolas Garnier
8 years ago
24 changed files with 1188 additions and 4 deletions
@ -0,0 +1,63 @@ |
|||
# Use LinkedIn Sign In with Firebase |
|||
|
|||
This sample shows how to authenticate using LinkedIn Sign-In on Firebase. In this sample we use OAuth 2.0 based |
|||
authentication to get LinkedIn user information then create a Firebase Custom Token (using the LinkedIn user ID). |
|||
|
|||
|
|||
## Setup the sample |
|||
|
|||
Create and setup the Firebase project: |
|||
1. Create a Firebase project using the [Firebase Developer Console](https://console.firebase.google.com). |
|||
1. Enable Billing on your Firebase the project by switching to the **Blaze** plan, this is currently needed for |
|||
Firebase Functions. |
|||
1. Copy the Web initialisation snippet from **Firebase Console > Overview > Add Firebase to your web app** and paste it |
|||
in `public/index.html` and `public/popup.html` in lieu of the placeholders (where the `TODO(DEVELOPER)` |
|||
are located). |
|||
1. From Firebse initialization snippet copy the `apiKey` value and paste it in `env.json` for the attribute |
|||
`firebaseConfig.apiKey` in lieu of the placeholder. |
|||
|
|||
Create and provide a Service Account's keys: |
|||
1. Create a Service Accounts file as described in the [Server SDK setup instructions](https://firebase.google.com/docs/server/setup#add_firebase_to_your_app). |
|||
1. Save the Service Account credential file as `./functions/service-account.json` |
|||
|
|||
|
|||
Create and setup your LinkedIn app: |
|||
1. Create a LinkedIn app in the [LinkedIn Developers website](https://www.linkedin.com/developer/apps/). |
|||
1. Add the URL `https://<application-id>.firebaseapp.com/popup.html` to the |
|||
**OAuth 2.0** > **Authorized Redirect URLs** of your LinkedIn app. |
|||
1. Copy the **Client ID** and **Client Secret** of your LinkedIn app and paste them in `env.json` for the attribute |
|||
`linkedIn.clientId` and `linkedIn.secret` in lieu of the placeholders. |
|||
|
|||
> Make sure the LinkedIn Client Secret is always kept secret. For instance do not save this in your version control system. |
|||
|
|||
Deploy your project: |
|||
1. Run `firebase use --add` and choose your Firebase project. This will configure the Firebase CLI to use the correct |
|||
project locally. |
|||
1. Run `firebase deploy` to effectively deploy the sample. The first time the Functions are deployed the process can |
|||
take several minutes. |
|||
|
|||
|
|||
## Run the sample |
|||
|
|||
Open the sample's website by using `firebase open hosting:site` or directly accessing `https://<project-id>.firebaseapp.com/`. |
|||
|
|||
Click on the **Sign in with LinkedIn** button and a popup window will appear that will show the Linked In authentication consent screen. Sign In and/or authorize the authentication request. |
|||
|
|||
The website should display your name, email and profile pic from Linked In. At this point you are authenticated in Firebase and can use the database/hosting etc... |
|||
|
|||
## Workflow and design |
|||
|
|||
When Clicking the **Sign in with LinkedIn** button a popup is shown which redirects users to the `redirect` Function URL. |
|||
|
|||
The `redirect` Function then redirects the user to the LinkedIn OAuth 2.0 consent screen where (the first time only) the user will have to grant approval. Also the `state` cookie is set on the client with the value of the `state` URL query parameter to check against later on. |
|||
|
|||
After the user has granted approval he is redirected back to the `./popup.html` page along with an OAuth 2.0 Auth Code as a URL parameter. This Auth code is then sent to the `token` Function using a JSONP Request. The `token` function then: |
|||
- Checks that the value of the `state` URL query parameter is the same as the one in the `state` cookie. |
|||
- Exchanges the auth code for an access token using the LinkedIn app credentials. |
|||
- Use the Access Token to query the LinkedIn API to get user's information such as ID, name, email and profile pic URL. |
|||
- Mints a Custom Auth token (which is why we need Service Accounts Credentials). |
|||
- Use the Custom Auth token to authorize as the user and updates the email and/or profile information on Firebase if needed. |
|||
- Returns the Custom Auth Token to the `./popup.html` page. |
|||
|
|||
The `./popup.html` receives the Custom Auth Token back from the AJAX request to the `token` Function and uses it to authenticate the user in Firebase. Then close the popup. |
|||
At this point the main page will detect the sign-in through the Firebase Auth State observer and display the signed-In user information. |
@ -0,0 +1,9 @@ |
|||
{ |
|||
"instagram": { |
|||
"clientId": "YOUR_INSTAGRAM_APP_CLIENT_ID", |
|||
"clientSecret": "YOUR_INSTAGRAM_APP_CLIENT_SECRET" |
|||
}, |
|||
"firebaseConfig": { |
|||
"apiKey": "YOUR_FIREBASE_PROJECT_API_KEY" |
|||
} |
|||
} |
@ -0,0 +1,5 @@ |
|||
{ |
|||
"hosting": { |
|||
"public": "public" |
|||
} |
|||
} |
@ -0,0 +1,131 @@ |
|||
/** |
|||
* 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 t`he specific language governing permissions and
|
|||
* limitations under the License. |
|||
*/ |
|||
'use strict'; |
|||
|
|||
const functions = require('firebase-functions'); |
|||
const cookieParser = require('cookie-parser'); |
|||
const crypto = require('crypto'); |
|||
const firebase = require('firebase'); |
|||
firebase.initializeApp({ |
|||
serviceAccount: require('./service-account.json'), |
|||
databaseURL: 'https://' + process.env.GCLOUD_PROJECT + '.firebaseio.com' |
|||
}); |
|||
const oauth2 = require('simple-oauth2')({ |
|||
clientID: functions.env.get('instagram.clientId'), |
|||
clientSecret: functions.env.get('instagram.clientSecret'), |
|||
site: 'https://api.instagram.com', |
|||
tokenPath: '/oauth/access_token', |
|||
authorizationPath: '/oauth/authorize' |
|||
}); |
|||
|
|||
const OAUTH_REDIRECT_URI = 'https://' + process.env.GCLOUD_PROJECT + '.firebaseapp.com/popup.html'; |
|||
const OAUTH_SCOPES = 'basic'; |
|||
|
|||
/** |
|||
* Redirects the User to the Instagram authentication consent screen. Also the 'state' cookie is set for later state |
|||
* verification. |
|||
*/ |
|||
exports.redirect = functions.cloud.http().on('request', (req, res) => { |
|||
cookieParser()(req, res, () => { |
|||
try { |
|||
const state = req.cookies.state || crypto.randomBytes(20).toString('hex'); |
|||
console.log('Setting verification state:', state); |
|||
res.cookie('state', state.toString(), {maxAge: 3600000, secure: true, httpOnly: true}); |
|||
const redirectUri = oauth2.authCode.authorizeURL({ |
|||
redirect_uri: OAUTH_REDIRECT_URI, |
|||
scope: OAUTH_SCOPES, |
|||
state: state |
|||
}); |
|||
console.log('Redirecting to:', redirectUri); |
|||
res.redirect(redirectUri); |
|||
} catch (e) { |
|||
res.status(500).send(e.toString()); |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
/** |
|||
* Exchanges a given Instagram auth code passed in the 'code' URL query parameter for a Firebase auth token. |
|||
* The request also needs to specify a 'state' query parameter which will be checked against the 'state' cookie. |
|||
* The Firebase custom auth token is sent back in a JSONP callback function with function name defined by the |
|||
* 'callback' query parameter. |
|||
*/ |
|||
exports.token = functions.cloud.http().on('request', (req, res) => { |
|||
try { |
|||
cookieParser()(req, res, () => { |
|||
console.log('Received verification state:', req.cookies.state); |
|||
console.log('Received state:', req.query.state); |
|||
if (!req.cookies.state) { |
|||
throw new Error('State cookie not set or expired. Maybe you took too long to authorize. Please try again.'); |
|||
} else if (req.cookies.state !== req.query.state) { |
|||
throw new Error('State validation failed'); |
|||
} |
|||
console.log('Received auth code:', req.query.code); |
|||
oauth2.authCode.getToken({ |
|||
code: req.query.code, |
|||
redirect_uri: OAUTH_REDIRECT_URI |
|||
}).then(results => { |
|||
console.log('Auth code exchange result received:', results); |
|||
const firebaseAccount = createFirebaseToken(results.user.id); |
|||
return updateAccount(firebaseAccount.token, firebaseAccount.uid, |
|||
results.user.full_name, results.user.profile_picture) |
|||
.then(() => res.jsonp({token: firebaseAccount.token})); |
|||
}); |
|||
}); |
|||
} catch (error) { |
|||
return res.jsonp({error: error.toString}); |
|||
} |
|||
}); |
|||
|
|||
/** |
|||
* Creates a Firebase custom auth token for the given Instagram user ID. |
|||
* |
|||
* @returns {Object} The Firebase custom auth token and the uid. |
|||
*/ |
|||
function createFirebaseToken(digitsUID) { |
|||
// The UID we'll assign to the user.
|
|||
const uid = `instagram:${digitsUID}`; |
|||
|
|||
// Create the custom token.
|
|||
const token = firebase.app().auth().createCustomToken(uid); |
|||
console.log('Created Custom token for UID "', uid, '" Token:', token); |
|||
return {token: token, uid: uid}; |
|||
} |
|||
|
|||
/** |
|||
* Updates the user with the given displayName and photoURL. Updates the Firebase user profile with the |
|||
* displayName if needed. |
|||
* |
|||
* @returns {Promise} Promise that completes when all the updates have been completed. |
|||
*/ |
|||
function updateAccount(token, uid, displayName, photoURL) { |
|||
// Create a Firebase app we'll use to authenticate as the user.
|
|||
const userApp = firebase.initializeApp({ |
|||
apiKey: functions.env.get('firebaseConfig.apiKey') |
|||
}, uid); |
|||
|
|||
// Authenticate as the user, updates the email and profile Pic.
|
|||
return userApp.auth().signInWithCustomToken(token).then(user => { |
|||
if (displayName !== user.displayName || photoURL !== user.photoURL) { |
|||
console.log('Updating profile of user', uid, 'with', {displayName: displayName, photoURL: photoURL}); |
|||
return user.updateProfile({displayName: displayName, photoURL: photoURL}).then(() => userApp.delete()); |
|||
} |
|||
return userApp.delete(); |
|||
}).catch(e => { |
|||
userApp.delete(); |
|||
throw e; |
|||
}); |
|||
} |
@ -0,0 +1,13 @@ |
|||
{ |
|||
"name": "functions", |
|||
"description": "Firebase Functions", |
|||
"dependencies": { |
|||
"cookie-parser": "^1.4.3", |
|||
"crypto": "0.0.3", |
|||
"firebase": "^3.3.0", |
|||
"firebase-functions": "https://storage.googleapis.com/firebase-preview-drop/node/firebase-functions/firebase-functions-preview.latest.tar.gz", |
|||
"request": "^2.74.0", |
|||
"request-promise-native": "^1.0.3", |
|||
"simple-oauth2": "^0.8.0" |
|||
} |
|||
} |
@ -0,0 +1,62 @@ |
|||
/** |
|||
* Copyright 2015 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; |
|||
} |
|||
.mdl-grid { |
|||
max-width: 1024px; |
|||
margin: auto; |
|||
} |
|||
.mdl-card { |
|||
min-height: 0; |
|||
padding-bottom: 5px; |
|||
} |
|||
.mdl-layout__header-row { |
|||
padding: 0; |
|||
} |
|||
#message-form { |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
#message-form button { |
|||
max-width: 300px; |
|||
} |
|||
#message-list { |
|||
padding: 0; |
|||
width: 100%; |
|||
} |
|||
#message-list > div { |
|||
padding: 15px; |
|||
border-bottom: 1px #f1f1f1 solid; |
|||
} |
|||
h3 { |
|||
background: url('firebase-logo.png') no-repeat; |
|||
background-size: 40px; |
|||
padding-left: 50px; |
|||
} |
|||
#demo-signed-out-card, |
|||
#demo-signed-in-card, |
|||
#demo-subscribe-button, |
|||
#demo-unsubscribe-button, |
|||
#demo-subscribed-text-container, |
|||
#demo-unsubscribed-text-container { |
|||
display: none; |
|||
} |
|||
#demo-subscribe-button, |
|||
#demo-unsubscribe-button { |
|||
margin-right: 20px; |
|||
} |
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 of to authorize Firebase with Instagram Auth using Firebase Functions"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<title>Firebase Functions demo to Sign In with Instagram</title> |
|||
|
|||
<!-- Material Design Lite --> |
|||
<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>Sign in with Instagram 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 Sign In with Instagram to Firebase Authentication. |
|||
<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 Instagram</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> |
|||
Welcome <span id="demo-name-container"></span><br> |
|||
Your Firebase User ID is: <span id="demo-uid-container"></span><br> |
|||
Your profile picture: <img id="demo-profile-pic"> |
|||
</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> |
|||
<button id="demo-delete-button" class="mdl-color-text--grey-700 mdl-button--raised mdl-button mdl-js-button mdl-js-ripple-effect">Delete account</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,54 @@ |
|||
/** |
|||
* 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. |
|||
*/ |
|||
|
|||
html, body { |
|||
font-family: 'Roboto', 'Helvetica', sans-serif; |
|||
} |
|||
.mdl-grid { |
|||
max-width: 1024px; |
|||
margin: auto; |
|||
} |
|||
.mdl-card { |
|||
min-height: 0; |
|||
padding-bottom: 5px; |
|||
} |
|||
.mdl-layout__header-row { |
|||
padding: 0; |
|||
} |
|||
h3 { |
|||
background: url('firebase-logo.png') no-repeat; |
|||
background-size: 40px; |
|||
padding-left: 50px; |
|||
} |
|||
#demo-signed-out-card, |
|||
#demo-signed-in-card { |
|||
display: none; |
|||
} |
|||
#demo-profile-pic { |
|||
height: 60px; |
|||
width: 60px; |
|||
border-radius: 30px; |
|||
margin-left: calc(50% - 30px); |
|||
margin-top: 10px; |
|||
} |
|||
#demo-name-container, |
|||
#demo-email-container, |
|||
#demo-uid-container { |
|||
font-weight: bold; |
|||
} |
|||
#demo-delete-button { |
|||
margin-left: 20px; |
|||
} |
@ -0,0 +1,85 @@ |
|||
/** |
|||
* 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.nameContainer = document.getElementById('demo-name-container'); |
|||
this.uidContainer = document.getElementById('demo-uid-container'); |
|||
this.deleteButton = document.getElementById('demo-delete-button'); |
|||
this.profilePic = document.getElementById('demo-profile-pic'); |
|||
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)); |
|||
this.deleteButton.addEventListener('click', this.deleteAccount.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.nameContainer.innerText = user.displayName; |
|||
this.uidContainer.innerText = user.uid; |
|||
this.profilePic.src = user.photoURL; |
|||
this.signedOutCard.style.display = 'none'; |
|||
this.signedInCard.style.display = 'block'; |
|||
} 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() { |
|||
// This is the URL to the HTTP triggered 'redirect' Firebase Function.
|
|||
// See https://firebase.google.com/preview/functions/gcp-events#handle_a_cloud_http_firebase_function_event.
|
|||
var redirectFunctionURL = 'https://us-central1-' + Demo.getFirebaseProjectId() + '.cloudfunctions.net/redirect'; |
|||
// Open the Functions URL as a popup.
|
|||
window.open(redirectFunctionURL, 'name', 'height=585,width=400'); |
|||
}; |
|||
|
|||
// Signs-out of Firebase.
|
|||
Demo.prototype.signOut = function() { |
|||
firebase.auth().signOut(); |
|||
}; |
|||
|
|||
// Deletes the user's account.
|
|||
Demo.prototype.deleteAccount = function() { |
|||
firebase.auth().currentUser.delete().then(function() { |
|||
window.alert('Account deleted'); |
|||
}).catch(function(error) { |
|||
if (error.code === 'auth/requires-recent-login') { |
|||
window.alert('You need to have recently signed-in to delete your account. Please sign-in and try again.'); |
|||
firebase.auth().signOut(); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
// Returns the Firebase project ID of the default Firebase app.
|
|||
Demo.getFirebaseProjectId = function() { |
|||
return firebase.app().options.authDomain.split('.')[0]; |
|||
}; |
|||
|
|||
// Load the demo.
|
|||
new Demo(); |
@ -0,0 +1,84 @@ |
|||
<!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 authorize Firebase with Instagram auth using Firebase Functions"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<title>Authenticate with Instagram</title> |
|||
</head> |
|||
<body> |
|||
|
|||
Please wait... |
|||
|
|||
<!-- Firebase --> |
|||
<!-- *********************************************************************************************************************** |
|||
* TODO(DEVELOPER): Paste the initialization snippet from: Firebase Console > Overview > Add Firebase to your web app. * |
|||
*********************************************************************************************************************** --> |
|||
|
|||
<script> |
|||
/** |
|||
* Returns the value of the given URL query parameter. |
|||
*/ |
|||
function getURLParameter(name) { |
|||
return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search) || |
|||
[null, ''])[1].replace(/\+/g, '%20')) || null; |
|||
} |
|||
|
|||
/** |
|||
* Returns the ID of the Firebase project. |
|||
*/ |
|||
function getFirebaseProjectId() { |
|||
return firebase.app().options.authDomain.split('.')[0]; |
|||
} |
|||
|
|||
/** |
|||
* This callback is called by the JSONP callback of the 'token' Firebase Function with the Firebase auth token. |
|||
*/ |
|||
function tokenReceived(data) { |
|||
if (data.token) { |
|||
firebase.auth().signInWithCustomToken(data.token).then(function() { |
|||
window.close(); |
|||
}).catch(function(error) { |
|||
console.error(error); |
|||
document.body.innerText = 'Error While using the Firebase Auth token to sign in: ' + error.message; |
|||
}) |
|||
} else { |
|||
console.error(error); |
|||
document.body.innerText = 'Error in the token Function: ' + data.error; |
|||
} |
|||
} |
|||
|
|||
var code = getURLParameter('code'); |
|||
var state = getURLParameter('state'); |
|||
var error = getURLParameter('error'); |
|||
if (!code) { |
|||
document.body.innerText = 'Error back from the Instagram auth page: ' + error; |
|||
} else { |
|||
// Use JSONP to load the 'token' Firebase Function to exchange the auth code against a Firebase custom token. |
|||
const script = document.createElement('script'); |
|||
script.type = 'text/javascript'; |
|||
// This is the URL to the HTTP triggered 'token' Firebase Function. |
|||
// See https://firebase.google.com/preview/functions/gcp-events#handle_a_cloud_http_firebase_function_event. |
|||
var tokenFunctionURL = 'https://us-central1-' + getFirebaseProjectId() + '.cloudfunctions.net/token'; |
|||
script.src = tokenFunctionURL + |
|||
'?code=' + encodeURIComponent(code) + |
|||
'&state=' + encodeURIComponent(state) + |
|||
'&callback=' + tokenReceived.name; |
|||
document.head.appendChild(script); |
|||
} |
|||
</script> |
|||
</body> |
|||
</html> |
@ -0,0 +1,63 @@ |
|||
# Use LinkedIn Sign In with Firebase |
|||
|
|||
This sample shows how to authenticate using LinkedIn Sign-In on Firebase. In this sample we use OAuth 2.0 based |
|||
authentication to get LinkedIn user information then create a Firebase Custom Token (using the LinkedIn user ID). |
|||
|
|||
|
|||
## Setup the sample |
|||
|
|||
Create and setup the Firebase project: |
|||
1. Create a Firebase project using the [Firebase Developer Console](https://console.firebase.google.com). |
|||
1. Enable Billing on your Firebase the project by switching to the **Blaze** plan, this is currently needed for |
|||
Firebase Functions. |
|||
1. Copy the Web initialisation snippet from **Firebase Console > Overview > Add Firebase to your web app** and paste it |
|||
in `public/index.html` and `public/popup.html` in lieu of the placeholders (where the `TODO(DEVELOPER)` |
|||
are located). |
|||
1. From Firebse initialization snippet copy the `apiKey` value and paste it in `env.json` for the attribute |
|||
`firebaseConfig.apiKey` in lieu of the placeholder. |
|||
|
|||
Create and provide a Service Account's keys: |
|||
1. Create a Service Accounts file as described in the [Server SDK setup instructions](https://firebase.google.com/docs/server/setup#add_firebase_to_your_app). |
|||
1. Save the Service Account credential file as `./functions/service-account.json` |
|||
|
|||
|
|||
Create and setup your LinkedIn app: |
|||
1. Create a LinkedIn app in the [LinkedIn Developers website](https://www.linkedin.com/developer/apps/). |
|||
1. Add the URL `https://<application-id>.firebaseapp.com/popup.html` to the |
|||
**OAuth 2.0** > **Authorized Redirect URLs** of your LinkedIn app. |
|||
1. Copy the **Client ID** and **Client Secret** of your LinkedIn app and paste them in `env.json` for the attribute |
|||
`linkedIn.clientId` and `linkedIn.secret` in lieu of the placeholders. |
|||
|
|||
> Make sure the LinkedIn Client Secret is always kept secret. For instance do not save this in your version control system. |
|||
|
|||
Deploy your project: |
|||
1. Run `firebase use --add` and choose your Firebase project. This will configure the Firebase CLI to use the correct |
|||
project locally. |
|||
1. Run `firebase deploy` to effectively deploy the sample. The first time the Functions are deployed the process can |
|||
take several minutes. |
|||
|
|||
|
|||
## Run the sample |
|||
|
|||
Open the sample's website by using `firebase open hosting:site` or directly accessing `https://<project-id>.firebaseapp.com/`. |
|||
|
|||
Click on the **Sign in with LinkedIn** button and a popup window will appear that will show the Linked In authentication consent screen. Sign In and/or authorize the authentication request. |
|||
|
|||
The website should display your name, email and profile pic from Linked In. At this point you are authenticated in Firebase and can use the database/hosting etc... |
|||
|
|||
## Workflow and design |
|||
|
|||
When Clicking the **Sign in with LinkedIn** button a popup is shown which redirects users to the `redirect` Function URL. |
|||
|
|||
The `redirect` Function then redirects the user to the LinkedIn OAuth 2.0 consent screen where (the first time only) the user will have to grant approval. Also the `state` cookie is set on the client with the value of the `state` URL query parameter to check against later on. |
|||
|
|||
After the user has granted approval he is redirected back to the `./popup.html` page along with an OAuth 2.0 Auth Code as a URL parameter. This Auth code is then sent to the `token` Function using a JSONP Request. The `token` function then: |
|||
- Checks that the value of the `state` URL query parameter is the same as the one in the `state` cookie. |
|||
- Exchanges the auth code for an access token using the LinkedIn app credentials. |
|||
- Use the Access Token to query the LinkedIn API to get user's information such as ID, name, email and profile pic URL. |
|||
- Mints a Custom Auth token (which is why we need Service Accounts Credentials). |
|||
- Use the Custom Auth token to authorize as the user and updates the email and/or profile information on Firebase if needed. |
|||
- Returns the Custom Auth Token to the `./popup.html` page. |
|||
|
|||
The `./popup.html` receives the Custom Auth Token back from the AJAX request to the `token` Function and uses it to authenticate the user in Firebase. Then close the popup. |
|||
At this point the main page will detect the sign-in through the Firebase Auth State observer and display the signed-In user information. |
@ -0,0 +1,9 @@ |
|||
{ |
|||
"linkedIn": { |
|||
"clientId": "YOUR_LINKEDIN_APP_CLIENT_ID", |
|||
"clientSecret": "YOUR_LINKEDIN_APP_CLIENT_SECRET" |
|||
}, |
|||
"firebaseConfig": { |
|||
"apiKey": "YOUR_FIREBASE_PROJECT_API_KEY" |
|||
} |
|||
} |
@ -0,0 +1,5 @@ |
|||
{ |
|||
"hosting": { |
|||
"public": "public" |
|||
} |
|||
} |
@ -0,0 +1,138 @@ |
|||
/** |
|||
* 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 t`he specific language governing permissions and
|
|||
* limitations under the License. |
|||
*/ |
|||
'use strict'; |
|||
|
|||
const functions = require('firebase-functions'); |
|||
const cookieParser = require('cookie-parser'); |
|||
const crypto = require('crypto'); |
|||
const firebase = require('firebase'); |
|||
firebase.initializeApp({ |
|||
serviceAccount: require('./service-account.json'), |
|||
databaseURL: 'https://' + process.env.GCLOUD_PROJECT + '.firebaseio.com' |
|||
}); |
|||
const Linkedin = require('node-linkedin')( |
|||
functions.env.get('linkedIn.clientId'), |
|||
functions.env.get('linkedIn.clientSecret'), |
|||
'https://' + process.env.GCLOUD_PROJECT + '.firebaseapp.com/popup.html'); |
|||
|
|||
const OAUTH_SCOPES = ['r_basicprofile', 'r_emailaddress']; |
|||
|
|||
/** |
|||
* Redirects the User to the LinkedIn authentication consent screen. ALso the 'state' cookie is set for later state |
|||
* verification. |
|||
*/ |
|||
exports.redirect = functions.cloud.http().on('request', (req, res) => { |
|||
cookieParser()(req, res, () => { |
|||
try { |
|||
const state = req.cookies.state || crypto.randomBytes(20).toString('hex'); |
|||
console.log('Setting verification state:', state); |
|||
res.cookie('state', state.toString(), {maxAge: 3600000, secure: true, httpOnly: true}); |
|||
Linkedin.auth.authorize(res, OAUTH_SCOPES, state.toString()); |
|||
} catch (e) { |
|||
res.status(500).send(e.toString()); |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
/** |
|||
* Exchanges a given LinkedIn auth code passed in the 'code' URL query parameter for a Firebase auth token. |
|||
* The request also needs to specify a 'state' query parameter which will be checked against the 'state' cookie. |
|||
* The Firebase custom auth token is sent back in a JSONP callback function with function name defined by the |
|||
* 'callback' query parameter. |
|||
*/ |
|||
exports.token = functions.cloud.http().on('request', (req, res) => { |
|||
try { |
|||
cookieParser()(req, res, () => { |
|||
if (!req.cookies.state) { |
|||
throw new Error('State cookie not set or expired. Maybe you took too long to authorize. Please try again.'); |
|||
} |
|||
console.log('Received verification state:', req.cookies.state); |
|||
Linkedin.auth.authorize(OAUTH_SCOPES, req.cookies.state); // Makes sure the state parameter is set
|
|||
console.log('Received auth code:', req.query.code); |
|||
console.log('Received state:', req.query.state); |
|||
Linkedin.auth.getAccessToken(res, req.query.code, req.query.state, (error, results) => { |
|||
if (error) { |
|||
throw error; |
|||
} |
|||
console.log('Received Access Token:', results.access_token); |
|||
const linkedin = Linkedin.init(results.access_token); |
|||
linkedin.people.me((error, results) => { |
|||
if (error) { |
|||
throw error; |
|||
} |
|||
console.log('Auth code exchange result received:', results); |
|||
const firebaseAccount = createFirebaseToken(results.id); |
|||
return updateAccount(firebaseAccount.token, firebaseAccount.uid, |
|||
results.emailAddress, results.formattedName, results.pictureUrl) |
|||
.then(() => res.jsonp({token: firebaseAccount.token})); |
|||
}); |
|||
}); |
|||
}); |
|||
} catch (e) { |
|||
return res.jsonp({error: e.toString}); |
|||
} |
|||
}); |
|||
|
|||
/** |
|||
* Creates a Firebase custom auth token for the given Instagram user ID. |
|||
* |
|||
* @returns {Object} The Firebase custom auth token and the uid. |
|||
*/ |
|||
function createFirebaseToken(digitsUID) { |
|||
// The UID we'll assign to the user.
|
|||
const uid = `linkedin:${digitsUID}`; |
|||
|
|||
// Create the custom token.
|
|||
const token = firebase.app().auth().createCustomToken(uid); |
|||
console.log('Created Custom token for UID "', uid, '" Token:', token); |
|||
return {token: token, uid: uid}; |
|||
} |
|||
|
|||
/** |
|||
* Updates the user with the given displayName and photoURL. Updates the Firebase user profile with the |
|||
* displayName if needed. |
|||
* |
|||
* @returns {Promise} Promise that completes when all the updates have been completed. |
|||
*/ |
|||
function updateAccount(token, uid, email, displayName, photoURL) { |
|||
// Create a Firebase app we'll use to authenticate as the user.
|
|||
const userApp = firebase.initializeApp({ |
|||
apiKey: functions.env.get('firebaseConfig.apiKey') |
|||
}, uid); |
|||
|
|||
// Update the profile of the user if needed.
|
|||
const updateUserProfile = user => { |
|||
if (displayName !== user.displayName || photoURL !== user.photoURL) { |
|||
console.log('Updating profile of user', uid, 'with', {displayName: displayName, photoURL: photoURL}); |
|||
return user.updateProfile({displayName: displayName, photoURL: photoURL}).then(() => userApp.delete()); |
|||
} |
|||
return userApp.delete(); |
|||
}; |
|||
|
|||
// Authenticate as the user and updates the email, displayName and profilePic.
|
|||
return userApp.auth().signInWithCustomToken(token).then(user => { |
|||
if (email !== user.email) { |
|||
console.log('Updating email of user', uid, 'with', email); |
|||
return user.updateEmail(email).then(() => updateUserProfile(user)); |
|||
} |
|||
return updateUserProfile(user); |
|||
}).catch(e => { |
|||
userApp.delete(); |
|||
throw e; |
|||
}); |
|||
} |
|||
|
|||
|
@ -0,0 +1,11 @@ |
|||
{ |
|||
"name": "functions", |
|||
"description": "Firebase Functions", |
|||
"dependencies": { |
|||
"cookie-parser": "^1.4.3", |
|||
"crypto": "0.0.3", |
|||
"firebase": "^3.3.0", |
|||
"firebase-functions": "https://storage.googleapis.com/firebase-preview-drop/node/firebase-functions/firebase-functions-preview.latest.tar.gz", |
|||
"node-linkedin": "^0.5.4" |
|||
} |
|||
} |
@ -0,0 +1,62 @@ |
|||
/** |
|||
* Copyright 2015 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; |
|||
} |
|||
.mdl-grid { |
|||
max-width: 1024px; |
|||
margin: auto; |
|||
} |
|||
.mdl-card { |
|||
min-height: 0; |
|||
padding-bottom: 5px; |
|||
} |
|||
.mdl-layout__header-row { |
|||
padding: 0; |
|||
} |
|||
#message-form { |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
#message-form button { |
|||
max-width: 300px; |
|||
} |
|||
#message-list { |
|||
padding: 0; |
|||
width: 100%; |
|||
} |
|||
#message-list > div { |
|||
padding: 15px; |
|||
border-bottom: 1px #f1f1f1 solid; |
|||
} |
|||
h3 { |
|||
background: url('firebase-logo.png') no-repeat; |
|||
background-size: 40px; |
|||
padding-left: 50px; |
|||
} |
|||
#demo-signed-out-card, |
|||
#demo-signed-in-card, |
|||
#demo-subscribe-button, |
|||
#demo-unsubscribe-button, |
|||
#demo-subscribed-text-container, |
|||
#demo-unsubscribed-text-container { |
|||
display: none; |
|||
} |
|||
#demo-subscribe-button, |
|||
#demo-unsubscribe-button { |
|||
margin-right: 20px; |
|||
} |
After Width: | Height: | Size: 3.5 KiB |
@ -0,0 +1,78 @@ |
|||
<!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 of to authorize Firebase with LinkedIn auth using Firebase Functions"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<title>Firebase Functions demo to Sign In with LinkedIn</title> |
|||
|
|||
<!-- Material Design Lite --> |
|||
<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>Sign in with LinkedIn 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 Sign In with LinkedIn to Firebase Authentication. |
|||
<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"></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> |
|||
Welcome <span id="demo-name-container"></span><br> |
|||
Your email is: <span id="demo-email-container"></span><br> |
|||
Your Firebase User ID is: <span id="demo-uid-container"></span><br> |
|||
Your profile picture: <img id="demo-profile-pic"> |
|||
</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> |
|||
<button id="demo-delete-button" class="mdl-color-text--grey-700 mdl-button--raised mdl-button mdl-js-button mdl-js-ripple-effect">Delete account</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> |
After Width: | Height: | Size: 3.9 KiB |
@ -0,0 +1,60 @@ |
|||
/** |
|||
* 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. |
|||
*/ |
|||
|
|||
html, body { |
|||
font-family: 'Roboto', 'Helvetica', sans-serif; |
|||
} |
|||
.mdl-grid { |
|||
max-width: 1024px; |
|||
margin: auto; |
|||
} |
|||
.mdl-card { |
|||
min-height: 0; |
|||
padding-bottom: 5px; |
|||
} |
|||
.mdl-layout__header-row { |
|||
padding: 0; |
|||
} |
|||
h3 { |
|||
background: url('firebase-logo.png') no-repeat; |
|||
background-size: 40px; |
|||
padding-left: 50px; |
|||
} |
|||
#demo-signed-out-card, |
|||
#demo-signed-in-card { |
|||
display: none; |
|||
} |
|||
#demo-profile-pic { |
|||
height: 60px; |
|||
width: 60px; |
|||
border-radius: 30px; |
|||
margin-left: calc(50% - 30px); |
|||
margin-top: 10px; |
|||
} |
|||
#demo-name-container, |
|||
#demo-email-container, |
|||
#demo-uid-container { |
|||
font-weight: bold; |
|||
} |
|||
#demo-sign-in-button { |
|||
background-image: url('linkedIn-button.png'); |
|||
background-size: 100% 100%; |
|||
width: 197px; |
|||
height: 27px; |
|||
} |
|||
#demo-delete-button { |
|||
margin-left: 20px; |
|||
} |
@ -0,0 +1,87 @@ |
|||
/** |
|||
* 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.emailContainer = document.getElementById('demo-email-container'); |
|||
this.nameContainer = document.getElementById('demo-name-container'); |
|||
this.deleteButton = document.getElementById('demo-delete-button'); |
|||
this.uidContainer = document.getElementById('demo-uid-container'); |
|||
this.profilePic = document.getElementById('demo-profile-pic'); |
|||
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)); |
|||
this.deleteButton.addEventListener('click', this.deleteAccount.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.nameContainer.innerText = user.displayName; |
|||
this.emailContainer.innerText = user.email; |
|||
this.uidContainer.innerText = user.uid; |
|||
this.profilePic.src = user.photoURL; |
|||
this.signedOutCard.style.display = 'none'; |
|||
this.signedInCard.style.display = 'block'; |
|||
} 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() { |
|||
// This is the URL to the HTTP triggered 'redirect' Firebase Function.
|
|||
// See https://firebase.google.com/preview/functions/gcp-events#handle_a_cloud_http_firebase_function_event.
|
|||
var redirectFunctionURL = 'https://us-central1-' + Demo.getFirebaseProjectId() + '.cloudfunctions.net/redirect'; |
|||
// Open the Functions URL as a popup.
|
|||
window.open(redirectFunctionURL, 'name', 'height=585,width=400'); |
|||
}; |
|||
|
|||
// Signs-out of Firebase.
|
|||
Demo.prototype.signOut = function() { |
|||
firebase.auth().signOut(); |
|||
}; |
|||
|
|||
// Deletes the user's account.
|
|||
Demo.prototype.deleteAccount = function() { |
|||
firebase.auth().currentUser.delete().then(function() { |
|||
window.alert('Account deleted'); |
|||
}).catch(function(error) { |
|||
if (error.code === 'auth/requires-recent-login') { |
|||
window.alert('You need to have recently signed-in to delete your account. Please sign-in and try again.'); |
|||
firebase.auth().signOut(); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
// Returns the Firebase project ID of the default Firebase app.
|
|||
Demo.getFirebaseProjectId = function() { |
|||
return firebase.app().options.authDomain.split('.')[0]; |
|||
}; |
|||
|
|||
// Load the demo.
|
|||
new Demo(); |
@ -0,0 +1,84 @@ |
|||
<!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 authenticate with LinkedIn on Firebase using Functions"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<title>Authenticate With LinkedIn</title> |
|||
</head> |
|||
<body> |
|||
|
|||
Please wait... |
|||
|
|||
<!-- Firebase --> |
|||
<!-- *********************************************************************************************************************** |
|||
* TODO(DEVELOPER): Paste the initialization snippet from: Firebase Console > Overview > Add Firebase to your web app. * |
|||
*********************************************************************************************************************** --> |
|||
|
|||
<script> |
|||
/** |
|||
* Returns the value of the given URL query parameter. |
|||
*/ |
|||
function getURLParameter(name) { |
|||
return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search) || |
|||
[null, ''])[1].replace(/\+/g, '%20')) || null; |
|||
} |
|||
|
|||
/** |
|||
* Returns the ID of the Firebase project. |
|||
*/ |
|||
function getFirebaseProjectId() { |
|||
return firebase.app().options.authDomain.split('.')[0]; |
|||
} |
|||
|
|||
/** |
|||
* This callback is called by the JSONP callback of the 'token' Firebase Function with the Firebase auth token. |
|||
*/ |
|||
function tokenReceived(data) { |
|||
if (data.token) { |
|||
firebase.auth().signInWithCustomToken(data.token).then(function() { |
|||
window.close(); |
|||
}).catch(function(error) { |
|||
console.error(error); |
|||
document.body.innerText = 'Error While using the Firebase Auth token to sign in: ' + error.message; |
|||
}) |
|||
} else { |
|||
console.error(error); |
|||
document.body.innerText = 'Error in the token Function: ' + data.error; |
|||
} |
|||
} |
|||
|
|||
var code = getURLParameter('code'); |
|||
var state = getURLParameter('state'); |
|||
var error = getURLParameter('error'); |
|||
if (!code) { |
|||
document.body.innerText = 'Error back from the LinkedIn auth page: ' + error; |
|||
} else { |
|||
// Use JSONP to load the 'token' Firebase Function to exchange the auth code against a Firebase custom token. |
|||
const script = document.createElement('script'); |
|||
script.type = 'text/javascript'; |
|||
// This is the URL to the HTTP triggered 'token' Firebase Function. |
|||
// See https://firebase.google.com/preview/functions/gcp-events#handle_a_cloud_http_firebase_function_event. |
|||
var tokenFunctionURL = 'https://us-central1-' + getFirebaseProjectId() + '.cloudfunctions.net/token'; |
|||
script.src = tokenFunctionURL + |
|||
'?code=' + encodeURIComponent(code) + |
|||
'&state=' + encodeURIComponent(state) + |
|||
'&callback=' + tokenReceived.name; |
|||
document.head.appendChild(script); |
|||
} |
|||
</script> |
|||
</body> |
|||
</html> |
Loading…
Reference in new issue