I am doing a project using NextJS and Firebase.
I need to verify the user auth in the server side to be able to call the API from twitter in my project.
From now I added the firebase-admin.js
import * as admin from 'firebase-admin';
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.cert({
projectId: process.env.NEXT_PUBLIC_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
privateKey: process.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n')
}),
databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL
});
}
const firestore = admin.firestore();
const auth = admin.auth();
export { firestore, auth };
Then I tried in my API page to create a file to check if I am getting the info right from the user:
import { auth } from '../../utils/firebase-admin';
export default async (req, res) => {
if (!req.headers.token) {
return res.status(401).json({ error: 'Please include id token' });
}
try {
const idToken = req.headers.token.toString();
const decodedToken = await auth.verifyIdToken(idToken);
const uid = decodedToken.uid;
// You can check for the uid here and do any additional authorization checks.
// Then, you can proceed with your code to fetch the follower count
} catch (error) {
console.error(error);
res.status(401).json({ error: 'Unauthorized request' });
}
};
But I always get an error 401.
{"error":"Please include id token"}
GET http://localhost:3000/api/twitterfollowers 401 (Unauthorized)
Has anyone been able to solve this auth problem before using Firebase and NextJS?
I have a simple React web app created from create-react-app.
I also have an existing user pool (set up by a third party) on Amazon Cognito. I have been given a username and password for authentication. Once authenticated, Cognito provides a JWT token.
What I want to achieve is to authenticate the user and get a JWT access_token within the componentDidMount method of the App component; then use the token to call other APIs to retrieve some data and then show the data on the App component.
The following info is known:
region
USER_POOL_ID
APP_CLIENT_ID
USER_ACCOUNT
USER_PASSWORD
AWS provides the authenticate_and_get_token function for Python developers. Is there an equivalent for JavaScript and React developers? Any sample code is appreciated.
Don't know about the authenticate_and_get_token you mentioned, couln't find this function in Boto3 docs.
But I do know different function to retrieve a token called adminInitiateAuth. This is function available for JS.
you can implement this using Lambda, like this:
const AWS = require('aws-sdk');
const USER_POOL_ID = "us-east-1_*******";
const APP_CLIENT_ID = "***********";
const USER_ACCOUNT = "***********";
const USER_PASSWORD = "***********";
const cognitoClient = new AWS.CognitoIdentityServiceProvider({
apiVersion: "2016-04-19",
region: 'us-east-1'
});
exports.handler = async (event) => {
try {
const userData = await userSignIn(USER_ACCOUNT, USER_PASSWORD);
return userData // refer to userData.AuthenticationResult.IdToken;
} catch(e){
throw e;
}
};
const userSignIn = async (username, password) => {
try {
var params = {
AuthFlow: 'ADMIN_NO_SRP_AUTH',
ClientId: APP_CLIENT_ID ,
UserPoolId: USER_POOL_ID,
AuthParameters: {
USERNAME: username,
PASSWORD: password
}
};
return await cognitoClient.adminInitiateAuth(params).promise();
} catch (err) {
return err.message ? err : {message : err};
}
};
Please make sure you have the specific permission to invoke this method, like "Action": "cognito-idp:AdminInitiateAuth", in the lambda permissions, or in IAM role.
I have a problem:
I want a logged in user to be able to use my getRealtimeUsers function. But I get the following error: FirebaseError: Missing or insufficient permissions.
Below you will find the code. Some explanations:
On the React side, I created a firebase, registered a getRealtimeUsers function there, and tried to use it.
firebase.js file
import firebase from "firebase/app";
import "firebase/firestore";
const firebaseConfig = {
apiKey: process.env.REACT_APP_FIREBASE_KEY,
authDomain: process.env.REACT_APP_FIREBASE_DOMAIN,
projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_FIREBASE_SENDER_ID,
appId: process.env.REACT_APP_FIREBASE_APP_ID,
measurementId: process.env.REACT_APP_FIREBASE_MEASUREMENT_ID
};
firebase.initializeApp(firebaseConfig);
export default firebase;
A function that uses the firebase I created:
import firebase from '../../firebase';
export const getRealtimeUsers = () => {
return async () => {
const db = firebase.firestore();
const unsubscribe = db.collection("users")
return unsubscribe;
}
}
Testing of the function
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getRealtimeUsers } from '../redux/actions/chatActions';
import firebase from '../firebase';
let unsubscribe;
class chat extends Component {
componentDidMount() {
unsubscribe = this.props.getRealtimeUsers()
.then(unsubscribe => {
return unsubscribe;
}).catch(error => {
console.log(error);
});
}
componentWillUnmount() {
return () => {
unsubscribe.then(f => f()).catch(error => console.log(error));
}
}
render() {
const ref = firebase.firestore().collection("users");
return (
<div>
check if works
</div>
);
}
}
const mapStateToProps = (state) => ({
});
export default connect(
mapStateToProps,
{
getRealtimeUsers
}
);
Here I let the user do the login
login.js action
export const loginUser = (userData) => (dispatch) => {
axios
.post('/login', userData)
.then((res) => {
console.log(res)
setAuthorizationHeader(res.data.token);
})
.catch((err) => {
console.log(err)
});
};
The login is done on the nodejs side, which is also where I set it to firebase:
const config = {
apiKey: "WqcVU",
authDomain: "c468.firebaseapp.com",
projectId: "c468",
storageBucket: "c468.appspot.com",
messagingSenderId: "087",
appId: "1:087:web:c912",
measurementId: "G-SQX1"
};
;
const firebase = require("firebase");
firebase.initializeApp(config);
// Log user in
exports.login = (req, res) => {
const user = {
email: req.body.email,
password: req.body.password,
};
firebase
.auth()
.signInWithEmailAndPassword(user.email, user.password)
.then((data) => {
console.log(JSON.stringify(data));
return data.user.getIdToken();
})
.catch((err) => {
console.error(err);
});
};
app.post('/login', login);
the rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth != null;
}
}
}
I want only a logged in user to use the getRealtimeUsers function, but even if the user is logged in it doesnt work. That's my problem.
I do not know at all how to solve it
Your case is really problematic, the only way to do security in rules is to access your correntUser or auth. Because you connect through the nodejs, you only return json object and there will be no access to these objects.
For you to have access to these objects what you need to do is connect once more, in react itself, with the same firebase and that's how you enjoy all the worlds that firebase has to offer.
When you log in again, the user does not need to know about it, since when you log in you will reset the password, and choose a random password, it will solve all your problems.
I think this is the most likely solution, but it is not for production
Please update me.
Is there a reason why you're not authenticating directly from the client? The thing is you have to pass in some way the authenticated state from the server to the front...
You have signed in with firebase on your server but you did not sign in with firebase on the front end. Or maybe you did in setAuthorizationHeader(res.data.token); but I'd be curious to know how you did that. It would have been interesting to be able to authenticate with nodejs and then send the tokenId which has been generated, that is data.user.getIdToken(), to the client which the client would then pass to firebase.auth().signInWithToken(token) but this is not possible. If you want to know more about it, I invite you to read this issue (a feature request).
The way to solve this problem is to use Admin SDK and create a custom token.
Firebase gives you complete control over authentication by allowing you to authenticate users or devices using secure JSON Web Tokens (JWTs). You generate these tokens on your server, pass them back to a client device, and then use them to authenticate via the signInWithCustomToken() method.
To achieve this, you must create a server endpoint that accepts sign-in credentials—such as a username and password—and, if the credentials are valid, returns a custom JWT. The custom JWT returned from your server can then be used by a client device to authenticate with Firebase (iOS, Android, web). Once authenticated, this identity will be used when accessing other Firebase services, such as the Firebase Realtime Database and Cloud Storage. Furthermore, the contents of the JWT will be available in the auth object in your Realtime Database Rules and the request.auth object in your Cloud Storage Security Rules.
Here are the steps:
First, you will add the SDK to your server:
yarn add firebase-admin --save
and import it
const admin = require("firebase-admin")
Go over to your Project settings under the "Service accounts" tab.
and click on "Generate a new private key"
a file has been downloaded and saved on your machine. From there, you will either import the file and initialize admin like this:
var serviceAccount = require("/path/to/serviceAccountKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
or better yet, in the console
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/serviceAccountKey.json"
and in your file
const credential = admin.credential.applicationDefault() // this will automatically find your env variable
admin.initializeApp({
credential
})
Note: don't forget to set the proper permissions to the private key file with chmod (I had to).
nodejs
firebase
.auth()
.signInWithEmailAndPassword(user.email, user.password)
.then((data) => {
admin
.auth()
.createCustomToken(data.user.uid)
.then((customToken) => {
console.log('customToken', customToken)
// send token back to client here
res.send(customToken)
})
.catch((error) => {
console.log('Error creating custom token:', error);
});
})
.catch((err) => {
console.error(err);
});
and then on the client:
// get the token and then
firebase.auth().signInWithCustomToken(token)
.then((userCredential) => {
// Signed in
var user = userCredential.user;
})
.catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
});
Another solution would be not using firebase on the client at all and use the backend as some sort of proxy between firebase and the client. The client would make a request to the backend which in turn would interrogate firebase.
in my case, faced same problem with rule's.
please give a try, change your rue and check whether its working or not
Im trying to make a login page to my firebase project, but always when i send the params to the functions its returns "Firebase is not defined . Here's my code:
<button id="login" onclick="signIn()"><Login</button>
The function signIn() only change the href to http://localhost:5000/signin/:email/:password
then i have this
const functions = require('firebase-functions');
const adm = require('firebase-admin');
const express = require('express');
const signin = require('./modules/signin');
const firebase = require("firebase");
// // Create and Deploy Your First Cloud Functions
adm.initializeApp(
functions.config().adm
);
const app = express();
app.get('/signin/:email/:password', (request, response) => {
exports.signin = signin(request.params.email, request.params.password);
});
exports.app = functions.https.onRequest(app);
and my function is only
function signin (email, password) {
firebase.auth().signInWithEmailAndPassword(email, password).then(function(user) {
// user signed in
console.log ("Usuário logado com sucesso!");
return True;
}).catch(function(error) {
var errorCode = error.code;
var errorMessage = error.message;
if (errorCode === 'auth/wrong-password') {
// alert('Wrong password.');
return false;
} else {
alert(errorCode+": "+errorMessage);
return false;
}
});
}
module.exports = signin;
im trying to use the index.js on functions folder as a type of "controller" to call functions on backend, but im having trouble to solve this simple problem.
You can't use the Firebase client SDK in Cloud Functions. Cloud Functions is considered backend code, and doesn't run on the browser. If you want to sign in the user, you have to use the SDK in your browser code, not backend code.
I'm trying to download some images that I have uploaded to my Google Cloud Storage (aka into buckets). I'm unable to use the .ref() method on any of my const storage or const bucket because they are part of the admin SDK. The admin.storage has only the method .bucket() (https://firebase.google.com/docs/reference/admin/node/admin.storage.Storage).
I'm able to access the buckets.
bucket.getFiles() works, and the result comes out to be an Array of Files objects with a ton of metadata (like file name, bucket owner, etc.). How can I get my images from the cloud storage and insert them into html objects?
var admin = require("firebase-admin");
var serviceAccount = require("./randomDB-f12d3-admin-correctly-working.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://randomDB-f12d3.firebaseio.com",
storageBucket: "randomDB-f12d3.appspot.com"
});
const gcs = require("#google-cloud/storage");
gcs.projectId = "randomDB-f12d3";
gcs.keyFilename = "randomDB-f12d3-firebase-admin-correctly-working.json";
exports.getFile = functions.https.onRequest((req, res) => {
cors(req, res, () => {
if (req.method !== "GET") {
return res.status(500).json({
message: "Not allowed"
});
}
const storage = admin.storage();
const bucket = admin.storage().bucket();
bucket.getFiles().then((result)=>{
console.log(result);
res.send(result);
});
});
});
The Admin SDK for Cloud Storage is just a wrapper around the #google-cloud/storage module. When you call admin.storage(), what you're getting back is a Storage object from that library. With admin.storage().bucket(), you're getting the default storage Bucket for your project. From there, you should use file() to create a reference to files in that bucket and download them as needed.