Where do you get/find the JWT-Secret from firebase? - firebase

I'm using firebase for my web/mobile apps and now have a back-end API I'm looking to use as well.
The API requires a JWT token to authenticate the requests and to set it up, I need to specify the JWT Secret that is used to encrypt/decrypt the token.
In firebase, I believe I retrieve the token using
const token = await firebase.auth().currentUser.getIdToken(); This is what I pass to the API.
However, I have not figured out where I get the JWT-secret to configure? I have tried the API key that is shown in firebase console, I have also tried the server/client keys found at my console at https://console.developers.google.com.
however, no matter what, I'm getting a JWSInvalidSignature when trying to make requests to the API Call.
Has anyone got this working? Where do I get the JWT-secret from firebase to configure on the API back-end? Thanks in advance.
Here are the details:
1. I am using a service called postGrest which auto-creates a web API on top of postgres DB. In order to authenticate the requests, you configure the service by specifying a custom claim called "role", and you also need to specify the JWT-secret so it can decode the token.
Here is my simple call to the API:
const fetchdata = async () => {
const token = await firebase.auth().currentUser.getIdToken();
let axiosConfig = {
headers: {
'Authorization': 'Bearer' + token
}
}
const data = await axios.get(`http://localhost:8080/users`,
axiosConfig);
}
Also note, I can simulate this in the bash command line using the following code: Note here that I'm getting the token from the getIdToken() above.
export TOKEN="eyJhbGciOiJSUzI1NiIsImtpZCI6ImQ2YzM5Mzc4YWVmYzA2YzQyYTJlODI1OTA0ZWNlZDMwODg2YTk5MjIiLCJ0eXAiOiJKV1QifQ.eyJ1c2VyaWQiOiI1NSIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9wb3N0Z3Jlc3QtYjRjOGMiLCJhdWQiOiJwb3N0Z3Jlc3QtYjRjOGMiLCJhdXRoX3RpbWUiOjE1NzExNTIyMjQsInVzZXJfaWQiOiJNMXZwQ3A2ZjlsaFdCblRleHh1TjlEdXIzUXAyIiwic3ViIjoiTTF2cENwNmY5bGhXQm5UZXh4dU45RHVyM1FwMiIsImlhdCI6MTU3MTE1OTQ0NSwiZXhwIjoxNTcxMTYzMDQ1LCJlbWFpbCI6InNwb25nZWJvYkBnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsic3BvbmdlYm9iQGdtYWlsLmNvbSJdfSwic2lnbl9pbl9wcm92aWRlciI6InBhc3N3b3JkIn19.nKuovs0Gx_ZKp17dI3kfz6GQofIMEOTA8RqTluwEs-5r-oTbKgpG33uS7fs7txVxvWIb_3fbN3idzfDHZevprMkagbHOd73CxTFBM7pr1bD2OKSK9ZPYfSt9OhvgJL51vBN3voLcNAb5iWVVl2XMqkcXeDoBi8IOKeZr27_DsRx48GSi7HieHWscF1lujSEr2C9tdAek3YyNnr3IcGI8cTSPHPaIbYl-8CaHQO2fUiGHEAaD7sqHxp3otJio56zOoNAy44P_nwORlMFZC0Rm8SaATpbmIkgbGYWHZHty70lmlYGVHTuM_hr2s7z2YhAjuacvBMgusZpyoVnoe3FQeA"
curl http://localhost:8080/contacts -H "Authorization: Bearer $TOKEN"
What's returned is: {"message":"JWSError JWSInvalidSignature"}
For the JWT-secret, I have tried several values, but none seem to work. This includes the "API Key" from firebase project, as well as trying "Generate key" which downloads a new .json file and inside there is a "private_key": that is along string.

From your service account downloaded file, use the private_key value to validate/decode the JWT token you got from getIdToken()...
The steps for using a third-party library to validate a Firebase Auth ID token describe it in more detail.

First you need to get your service_account.json
Then setup firebase on your server.
Example:
// set_up_firebase.js
const admin = require("firebase-admin");
var serviceAccount = require("./service_account.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
storageBucket: "your-app.appspot.com"
});
exports.admin = admin;
Then create a middleware function to check jwt.
Example:
// verifyJWT.js
const fire = require("./set_up_fire.js");
function _getToken(req) {
return req.headers.authorization !== undefined
&& req.headers.authorization !== ""
&& req.headers.authorization.split(' ').length === 2
&& req.headers.authorization.split(' ')[1] !== undefined
&& req.headers.authorization.split(' ')[1] !== ""
? req.headers.authorization.split(' ')[1]
: "";
}
function getJWTExpiredPayload() {
return { "status": 401, "result": "ERROR", "error": "Unauthorized!" };
}
function getJWTNotValidPayload() {
return { "status": 403, "result": "ERROR", "error": "Forbidden!" };
}
module.exports = async (req, res, next) => {
const token = _getToken(req);
let verified = undefined;
if (token !== undefined && token !== null && token !== "") {
try {
verified = await fire.admin.auth().verifyIdToken(token, true);
if (verified !== undefined && verified["exp"] !== undefined
&& Date.now() < verified["exp"] * 1000) {
req.user = verified;
return next();
}
return res.status(401).json(getJWTExpiredPayload());
} catch (error) {
console.log(error);
}
}
return res.status(403).json(getJWTNotValidPayload());
}
Then in your router, call it before your api:
Example with Express:
const verifyJWT = require("./jwt/verifyJWT.js");
router.post("/verifyJWT", verifyJWT, (req, res) => {
return res.status(200);
})

Related

Firebase google authentication in Chrome Extension - Error getting credential

I'm working on updating a chrome extension to MV3, and therefore I can't use the firebase UI to login any more. What I trying to do is use chrome.identity.launchWebAuthFlow, get the token, create the credential, and sign in with firebase.
Here's what I have:
function launchGoogleAuthFlow(interactive) {
return new Promise((resolve, reject) => {
console.log('launching webauthflow')
const manifest = chrome.runtime.getManifest();
const clientId = encodeURIComponent(manifest.oauth2.client_id);
const scopes = encodeURIComponent(manifest.oauth2.scopes.join(' '));
let redirectUri = chrome.identity.getRedirectURL();
let nonce = Math.random().toString(36).substring(2, 15)
const authUrl = new URL('https://accounts.google.com/o/oauth2/v2/auth');
authUrl.searchParams.set('client_id', clientId);
authUrl.searchParams.set('response_type', 'id_token');
authUrl.searchParams.set('redirect_uri', redirectUri);
// Add the OpenID scope. Scopes allow you to access the user’s information.
authUrl.searchParams.set('scope', 'openid profile email');
authUrl.searchParams.set('nonce', nonce);
// Show the consent screen after login.
authUrl.searchParams.set('prompt', 'consent');
chrome.identity.launchWebAuthFlow(
{
'url': authUrl.href,
'interactive': interactive
},
(redirectedTo) => {
if (chrome.runtime.lastError) {
console.log(chrome.runtime.lastError.message);
resolve(null)
}
else {
let idToken = redirectedTo.substring(redirectedTo.indexOf('id_token=') + 9)
idToken = idToken.substring(0, idToken.indexOf('&'))
resolve(idToken)
}
}
)
})
}
launchGoogleAuthFlow(true).then((token)=>{
if (token) {
console.log('token:' + token);
const credential = GoogleAuthProvider.credential(null, token);
console.log(credential);
signInWithCredential(auth, credential).then((result) => {
showMain();
document.getElementById('loggedInAs').textContent = result.email;
console.log("Success!!!")
console.log(result)
}).catch((error) => {
// You can handle errors here
console.log(error)
});
} else {
console.error('The OAuth token was null');
}
});
console.log('finished authflow');
}
I'm getting prompted to sign in with my google credentials, then in the console I get Failed to load resource: the server responded with a status of 400 ()
and then the a log from SignInWithCredentials
FirebaseError: Firebase: Unsuccessful check authorization response from Google: {
"error_description": "Invalid Value"
}
(auth/invalid-credential).
at _errorWithCustomMessage (index-6bd8d405.js:453:1)
at _performFetchWithErrorHandling (index-6bd8d405.js:973:1)
at async _performSignInRequest (index-6bd8d405.js:988:1)
at async _signInWithCredential (index-6bd8d405.js:4721:1)
In the response_type you are requesting an id_token:
authUrl.searchParams.set('response_type', 'id_token');
So your launchGoogleAuthFlow returns an id_token and that's it what you must give to GoogleAuthProvider.credential. This method expects an a id_token as the first parameter and an a access_token as the second parameter.
So all you have to do is change from:
const credential = GoogleAuthProvider.credential(null, token);
to:
const credential = GoogleAuthProvider.credential(token);
Everything should works fine.
If you may want the access_token you must request response_type=token and remove nonce. Finally you'll need to extract the returned access_token from response URL (your redirectedTo variable) as you did with id_token.
PS: In your code I also noticed that you got scopes from manifest but did not use them while requesting the token.

Getting some metadata from cloud storage to cloud function

I want to grab a json file from my cloud storage to use in cloud function (this is in the '/post-account' route). Although, I'm not sure if I'm doing it correctly, as the error logs are printing that web3 cant instantiate a contract without its abi (json interface).
I just need some hardcoded metadata for the cloud function. I have tried deploying with the json file local to the function dir, and requiring in the index.js, this did not work.
I could try copying the entire json interface into index.js as var, but this is thousands of lines of metadata.
index.js
'use strict';
const Web3 = require('web3');
const Contract = require('web3-eth-contract');
const axios = require('axios')
const fs = require('fs');
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const express = require('express');
const cookieParser = require('cookie-parser')();
const cors = require('cors');
const app = express();
//options for cors midddleware
const options = {
allowedHeaders: [
'Authorization',
'Origin',
'X-Requested-With',
'Content-Type',
'Accept',
'X-Access-Token',
],
credentials: true,
methods: 'GET,HEAD,OPTIONS,PUT,PATCH,POST,DELETE',
origin: 'https://test-cf-97bfc.web.app',
preflightContinue: false,
};
// set headers for app.
app.use(function (req, res, next) {
// Website you wish to allow to connect
res.setHeader('Access-Control-Allow-Origin', 'https://test-cf-97bfc.web.app');
// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
// Request headers you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
// Set to true if you need the website to include cookies in the requests sent
// to the API (e.g. in case you use sessions)
res.setHeader('Access-Control-Allow-Credentials', true);
// Pass to next layer of middleware
next();
});
// 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 = async (req, res, next) => {
console.log('Check if request is authorized with Firebase ID token');
if ((!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) &&
!(req.cookies && req.cookies.__session)) {
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>',
'or by passing a "__session" cookie.');
res.status(403).send('Unauthorized');
return;
}
let idToken;
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
console.log('Found "Authorization" header');
// Read the ID Token from the Authorization header.
idToken = req.headers.authorization.split('Bearer ')[1];
} else if (req.cookies) {
console.log('Found "__session" cookie');
// Read the ID Token from cookie.
idToken = req.cookies.__session;
} else {
// No cookie
res.status(403).send('Unauthorized');
return;
}
try {
const decodedIdToken = await admin.auth().verifyIdToken(idToken);
console.log('ID Token correctly decoded', decodedIdToken);
req.user = decodedIdToken;
next();
return;
} catch (error) {
console.error('Error while verifying Firebase ID token:', error);
res.status(403).send('Unauthorized');
return;
}
};
app.use(cors(options))
app.use(cookieParser);
app.use(validateFirebaseIdToken);
app.get('/hello', (req, res) => {
// #ts-ignore
res.send(`Hello ${req.user.email}`);
});
app.post('/post-account', async (req, res) => {
const { account, price } = req.body;
console.log('account' + account);
console.log('price' + price);
//changing to matic node
const web3 = new Web3.providers.HttpProvider('https://ropsten.infura.io/v3/09cbbdd922284d95a70c627ddf29012d');
const address = "0xE981aFEeA8E3dB77003C1D56e7c1D42e470Ea775";
//fetch json from cloud storage. Incorrect?
const abi = axios.get('https://firebasestorage.googleapis.com/v0/b/test-cf-97bfc.appspot.com/o/abis%2Fabi.json?alt=media&token=26bb0e2f-872c-4ee9-ac0f-525b9d84af39')
console.log('after artifact call');
Contract.setProvider(web3);
//error here.
const contract = new Contract(abi, address);
await contract.methods.totalSupply().call( function(error, result){
if (error) {
console.log(error);
res.status(400).end;
}
else if (result) {
res.status(200).json({ txResolved: "NFT awarded post", account: account, currentSupply: result })
}
});
});
exports.testAPI = functions.https.onRequest(app);
Before you can fetch any file from Cloud storage check the IAM policies and test if you can access it. If the Cloud Function exists inside the same project as the Cloud Storage file there should be no problem with auth unless you modified these rules to be more strict. If you need to finetune the network access for the function you can check out these links for VPC networks and for general networks
In a Cloud Functions you can use the npm #google-cloud/storage to get a file from a cloud storage. You can get a file like show in documentation link
const {Storage} = require('#google-cloud/storage');
const storage = new Storage();
const myBucket = storage.bucket('my-bucket');
const file = myBucket.file('my-file');
file.get(function(err, file, apiResponse) {
// file.metadata` has been populated.
});
//-
// If the callback is omitted, we'll return a Promise.
//-
file.get().then(function(data) {
const file = data[0];
const apiResponse = data[1];
// Code to use json file
});
Just remember to include the dependency in your package.json
"dependencies": {
"#google-cloud/storage": "^5.8.1"
}

Cloud Vision: Credentials issues

I am attempting to setup Cloud Vision on my local machine in a Firebase project but I am encountering problems with the default credentials.
First, I encountered Could not load the default credentials. This post suggested that I do gcloud auth application-default login. Upon attempting that, I encountered this:
Error: 7 PERMISSION_DENIED: Your application has authenticated using end user credentials from the Google Cloud SDK or Google Cloud Shell which are not supported by the vision.googleapis.com. We recommend configuring the billing/quota_project setting in gcloud or using a service account through the auth/impersonate_service_account setting. For more information about service accounts and how to use them in your application, see https://cloud.google.com/docs/authentication/.
I also attempted exports GOOGLE_APPLICATION_CREDENTIALS = "pathToServiceAccount" but it didn't work in my case.
Note that I have no issue reading/writing data to Firestore and Firebase Storage. The moment my code hits the Cloud Vision part, it throws the error. I have activated the API on cloud console and enabled billing. Security rules in firestore and storage are in testing mode at this moment.
const vision = require('#google-cloud/vision');
var admin = require('firebase-admin');
let serviceAccount = require('../path-to-service-account.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
storageBucket: "mybucket.appspot.com"
});
let db = admin.firestore()
let bucket = admin.storage().bucket();
//Some networking code, return a promise
.then(response => {
//setup storagePath
return pipeToStorage(item, response, storagePath) //Save to firebase storage ok
})
.then((item) => {
return performTextDetection(item.id) //Throws error here
})
function pipeToStorage(item, response, storagePath) {
return new Promise((resolve, reject) => {
gcFile = bucket.file(storagePath)
response.data
.pipe(gcFile.createWriteStream({
metadata : {
contentType: "application/pdf"
}
}))
.on('error', (error) => {
reject(error)
})
.on('finish', () => {
resolve(item)
})
})
}
function performTextDetection(id) {
const client = new vision.ImageAnnotatorClient();
const bucketName = bucket.name;
const fileName = `items/${id}.pdf`
const outputPrefix = 'ocr_results'
const gcsSourceUri = `gs://${bucketName}/${fileName}`;
const gcsDestinationUri = `gs://${bucketName}/${outputPrefix}/${id}/`;
const inputConfig = {
mimeType: 'application/pdf',
gcsSource: {
uri: gcsSourceUri,
},
};
const outputConfig = {
gcsDestination: {
uri: gcsDestinationUri,
},
};
const features = [{type: 'DOCUMENT_TEXT_DETECTION'}];
const request = {
requests: [
{
inputConfig: inputConfig,
features: features,
outputConfig: outputConfig,
},
],
};
return client.asyncBatchAnnotateFiles(request)
.then(([operation]) => {
return operation.promise()
})
.then(([filesResponse]) => {
const resultsUri = filesResponse.responses[0].outputConfig.gcsDestination.uri
return resultsUri
})
}
This happens because you have exports rather than export:
exports GOOGLE_APPLICATION_CREDENTIALS = "pathToServiceAccount"
please try:
export GOOGLE_APPLICATION_CREDENTIALS="/home/user/credentials.json"
note that there are not spaces, see details here. By the way, I have also found the same PERMISSION_DENIED error when export is omitted.
The validation step is executing a REST request with curl:
curl -X POST \
-H "Authorization: Bearer "$(gcloud auth application-default print-access-token) \
-H "Content-Type: application/json; charset=utf-8" \
-d #request.json \
https://vision.googleapis.com/v1/images:annotate
See the complete example here.

Error 403 on Calling Firebase Firestore Rest API

Here the code how i am generating the accessToken for Calling API.
import firebaseConfig from './../firebaseConfig';
import firebase from "firebase/app";
import "firebase/auth";
firebase.initializeApp(firebaseConfig);
var provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(provider).then(function(result) {
var token = result.credential.accessToken;
sessionStorage.setItem("__token", token); // Token saved
}.bind(this)).catch(function(error) {
});
With that code i am getting the token in sessionStorage.
Here's the snippet how i am using firebase Rest API.
var URL = https://firestore.googleapis.com/v1/projects/qtbt-a8bf8/databases/(default)/documents/users/[USER_ID]?key=[YOUR_API_KEY]
var token = sessionStorage.getItem("__token");
const config = {
headers: { Authorization: `Bearer ${token}` , Accept: 'application/json'}
};
axios.get(URL, config)
.then(function (response) {
// handle success
console.log(response);
})
.catch(function (error) {
// handle error
console.log(error);
})
.finally(function () {
// always executed
});
In the axios call, i am getting Error 403.
Response:
code: 403
message: "Request had insufficient authentication scopes."
status: "PERMISSION_DENIED"
your missing the api-key in the call. apikey and token should both be in the headers.
var token = sessionStorage.getItem("__token");// verify your token is correct.
var headers = {'x-api-key' : 'key valu goes here', 'authorization': ${token} }
The security rule is slightly different when using an IdToken rather than an OAuth token.
Instead of using:
if request.auth.uid == resource.id;
I use:
if request.auth.token.email == resource.id;
I'm matching the token's owner email to the document id, which also happens to be the owner's email.
You can also access the user's child documents using something like:
request.auth.token.email == resource.data.ownerEa;
In that case, the document(s) being matched must have an attribute ownerEa that has value == user's email.
Very annoying documentation isn't it.
Hope that helps...

OAuth2 fails to return auth token using simple-oauth2 and Firebase Functions for Spotify Authentication

I have been working on a oauth2 flow for spotify by following this similar tutorial by the Firebase team for Instagram HERE
I am able to submit my credentials and return the user code and state in the url, but when I run the method to submit the code to return an auth token, the auth token that I print to console in the Firebase functions returns: Auth Token Error Not Found. Here's my workflow:
Here's the Spotify docs
FIRST, I have a function to configure my spotifyOAuth:
function spotifyOAuth2Client() {
// Spotify OAuth 2 setup
const credentials = {
client: {
id: functions.config().spotify.clientid,
secret: functions.config().spotify.clientsecret,
},
auth: {
tokenHost: 'https://accounts.spotify.com',
authorizePath: '/authorize'
},
};
return require('simple-oauth2').create(credentials);
}
I use that function in this Firebase function that is called using https://us-central1-<my project string>.cloudfunctions.net/redirect:
exports.redirect = functions.https.onRequest((req, res) => {
const oauth2 = spotifyOAuth2Client();
cookieParser()(req, res, () => {
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.authorizationCode.authorizeURL({
redirect_uri: OAUTH_REDIRECT_URI,
//scope: OAUTH_SCOPES,
state: state,
});
console.log('Redirecting to:', redirectUri);
res.redirect(redirectUri);
});
});
The code above returns a url string with the proper parameters, the following code block is where my code breaks, I have another cloud function that runs after being redirected from the res.redirect(redirectUri) above. And when I try to run the getToken() method, it appears to not return anything because I hit the catch block instead? This is where I observe the Auth Token Error Not Found.
const oauth2 = spotifyOAuth2Client();
try {
return cookieParser()(req, res, async () => {
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);
console.log(OAUTH_REDIRECT_URI);
// Get the access token object (the authorization code is given from the previous step).
const tokenConfig = {
code: req.query.code,
redirect_uri: 'http://localhost:8100/popup'
};
// Save the access token
try {
const result = await oauth2.authorizationCode.getToken(tokenConfig)
const accessToken = oauth2.accessToken.create(result);
console.log('inside try');
console.log(result);
console.log(accessToken);
} catch (error) {
console.log('Access Token Error', error.message);
}
I've double checked my spotify client/secret credentials in the config, what is going wrong with this OAuth2 flow?
Resolved my issue, I was not using the correct endpoints:
const credentials = {
client: {
id: functions.config().spotify.clientid,
secret: functions.config().spotify.clientsecret,
},
auth: {
tokenHost: 'https://accounts.spotify.com',
authorizePath: '/authorize',
tokenPath: '/api/token'
},
};

Resources