add firebase example to use with jwt mode (close #648) (#649)

This commit is contained in:
Anon Ray 2018-10-05 09:51:45 +00:00 committed by Shahidh K Muhammed
parent 80a5f00ae4
commit e460ec0e62
7 changed files with 4098 additions and 0 deletions

View File

@ -0,0 +1,4 @@
firebase.json
.firebaserc
node_modules
config.js

View File

@ -0,0 +1,64 @@
# Firebase Auth + Hasura JWT
Barebones example to show how to have Firebase Auth integrated with Hasura JWT mode.
## Firebase Auth
Firebase has few ways of implementing custom JWT claims in Firebase Auth:
1. Have firebase generate the JWTs, then customize them from your backend using
Firebase Admin SDK [[docs]](https://firebase.google.com/docs/auth/admin/custom-claims#defining_roles_via_an_http_request)
2. Use Firebase cloud functions, and listen to user creation events to add
custom claims to generated JWT [[docs]](https://firebase.google.com/docs/auth/admin/custom-claims#defining_roles_via_firebase_functions_on_user_creation)
3. Have your own backend server, which generates custom tokens [[docs]](https://firebase.google.com/docs/auth/admin/create-custom-tokens)
4. Have your own backend scripts (not initiated by the client) to update user custom claims [[docs]](https://firebase.google.com/docs/auth/admin/custom-claims#defining_roles_via_backend_script)
## Add custom claims in Firebase
In this example, we are choosing the option 2 from above. But this can be done via any of the above methods. [Firebase docs](https://firebase.google.com/docs/auth/admin/custom-claims) have extensive documentation on how to achive this via different methods.
This example is adapted from [this guide](https://firebase.google.com/docs/auth/admin/custom-claims#defining_roles_via_firebase_functions_on_user_creation).
### Pre-requisites
This example assumes that you already have Firebase Auth setup for your app.
### Add the cloud function
Deploy the cloud function inside `functions/` folder:
```shell
firebase deploy --only functions
```
Customize the code to add your logic of assigning roles in the custom claims.
This cloud function is using the `onCreate` trigger. So whenever a user is created, this function is run.
### Client-side code
The client-side code is in `app/` folder.
## Configure Hasura to start in JWT mode
Deploy Hasura GraphQL Engine on Heroku:
[![Deploy HGE on heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/hasura/graphql-engine-heroku)
After deploying, add the following environment variables to configure JWT mode:
```
HASURA_GRAPHQL_ACCESS_KEY : yoursecretaccesskey
```
```
HASURA_GRAPHQL_JWT_SECRET: {"type":"RS512", "jwk_url": "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com"}
```
## Sending JWT to Hasura
Now, whenever you make a request to Hasura GraphQL engine (as an authenticated user), send the `id_token` in `Authorization` header:
```
Authorization: Bearer <firebase-id-token>
```

View File

@ -0,0 +1,17 @@
<html>
<head>
<title> Firebase Auth + Hasura JWT example </title>
</head>
<body>
<h1> Firebase Auth + Hasura JWT example </h1>
<form id="login-form">
Email: <input id="email" type="text"/>
Password: <input id="password" type="password" />
<button type="submit">Login</button>
</form>
<button id="get-token"> Get ID token </button>
<div id="id-token"></div>
<script src="https://www.gstatic.com/firebasejs/5.5.3/firebase.js"></script>
<script src="main.js"></script>
</body>
</html>

View File

@ -0,0 +1,60 @@
// Initialize Firebase
var config = {
apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxxx",
authDomain: "<your-app>.firebaseapp.com",
databaseURL: "https://<your-app>.firebaseio.com",
projectId: "<your-app>",
storageBucket: "<your-app>.appspot.com",
messagingSenderId: "xxxxxxxxxxxx"
};
firebase.initializeApp(config);
document.getElementById('login-form').onsubmit = function(event) {
event.preventDefault();
let email = document.getElementById('email').value;
let pass = document.getElementById('password').value;
login(email, pass);
};
document.getElementById('get-token').onclick = function(event) {
event.preventDefault();
firebase.auth().currentUser.getIdToken(true).
then(token => document.getElementById('id-token').innerHTML = token);
};
function login(email, password) {
firebase.auth().signInWithEmailAndPassword(email, password)
.then(function(user) {
console.log('login success');
})
.catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
console.log(error);
});
let callback = null;
let metadataRef = null;
firebase.auth().onAuthStateChanged(user => {
// Remove previous listener.
if (callback) {
metadataRef.off('value', callback);
}
// On user login add new listener.
if (user) {
// Check if refresh is required.
metadataRef = firebase.database().ref('metadata/' + user.uid + '/refreshTime');
callback = (snapshot) => {
// Force refresh to pick up the latest custom claims changes.
// Note this is always triggered on first call. Further optimization could be
// added to avoid the initial trigger when the token is issued and already contains
// the latest claims.
user.getIdToken(true);
};
// Subscribe new listener to changes on that node.
metadataRef.on('value', callback);
}
});
}

View File

@ -0,0 +1,41 @@
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
// On sign up.
exports.processSignUp = functions.auth.user().onCreate(user => {
console.log(user);
// Check if user meets role criteria:
// Your custom logic here: to decide what roles and other `x-hasura-*` should the user get
let customClaims;
if (user.email && user.email.indexOf('@hasura.io') != -1) {
customClaims = {
'https://hasura.io/jwt/claims': {
'x-hasura-default-role': 'admin',
'x-hasura-allowed-roles': ['user', 'admin'],
'x-hasura-user-id': user.uid
}
};
}
else {
customClaims = {
'https://hasura.io/jwt/claims': {
'x-hasura-default-role': 'user',
'x-hasura-allowed-roles': ['user'],
'x-hasura-user-id': user.uid
}
};
}
// Set custom user claims on this newly created user.
return admin.auth().setCustomUserClaims(user.uid, customClaims)
.then(() => {
// Update real-time database to notify client to force refresh.
const metadataRef = admin.database().ref("metadata/" + user.uid);
// Set the refresh time to the current UTC timestamp.
// This will be captured on the client to force a token refresh.
return metadataRef.set({refreshTime: new Date().getTime()});
})
.catch(error => {
console.log(error);
});
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,16 @@
{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"serve": "firebase serve --only functions",
"shell": "firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"dependencies": {
"firebase-admin": "~6.0.0",
"firebase-functions": "^2.0.3"
},
"private": true
}