At the moment I am creating a Firebase API on nodejs. I would like to handle all Firebase stuff (like authentication) with firebase-admin on nodejs. But what is the correct way to authenticate a user over nodejs in firebase-admin without the Javascript Firebase SDK on the client side? On the official documentation for admin I didn't find a function called signInWithEmailAndPassword (like as on the client side SDK) for nodejs. There is only a function called: "getUserByEmail", but this function doesn't check if the user has entered the correct password.
This is my form:
<form class="sign-box" action="/login" method="post">
<div class="form-group">
<input id="username" name="username" type="text" class="form-control" placeholder="E-Mail"/>
</div>
<div class="form-group">
<input id="password" name="password" type="password" class="form-control" placeholder="Password"/>
</div>
<button type="submit" class="btn btn-rounded">Sign in</button>
</form>
Once the form is submitted I pass the values to my API in nodejs:
app.post('/login', urlencodedParser, function (req, res) {
// getting the values
response = {
username: req.body.username,
password: req.body.password
};
// authenticate the user here, but how ?
});
My first idea was to use the Firebase SDK on the client side to sign in with signInWithEmailAndPassword and to get the uid. Once I had the UID I wanted to sent the UID to nodejs and call the function createCustomToken and to return the generated token (with some additional claims) back to the client. Once I get the token back I would use the function signWithCustomToken (on the client side) to authenticate the user. Is this way correct or is there a better way ?
Actually for authentication you will need to use the firebase regular api, no the admin.
First this will give you a refreshed firebase token, not a custom token.
If you like you can do the same to obtain a custom token, if you need a custom token, I also have an example.
npm install firebase --save
const firebase = require("firebase");
const config = {
apiKey: "",
authDomain: "",
databaseURL: "",
projectId: "",
storageBucket: "",
messagingSenderId: ""
};
firebase.initializeApp(config);
I am posting my login firebase function but you will be able to change it to express easily.
exports.login = functions.https.onRequest((req, rsp)=>{
const email = req.body.email;
const password = req.body.password;
const key = req.body.key;
const _key = '_my_key_';
let token = '';
if(key === _key){
firebase.auth().signInWithEmailAndPassword(email,password).then((user)=>{
//The promise sends me a user object, now I get the token, and refresh it by sending true (obviously another promise)
user.getIdToken(true).then((token)=>{
rsp.writeHead(200, {"Content-Type": "application/json"});
rsp.end(JSON.stringify({token:token}));
}).catch((err)=>{
rsp.writeHead(500, {"Content-Type": "application/json"});
rsp.end(JSON.stringify({error:err}));
});
}).catch((err)=>{
rsp.writeHead(500, {"Content-Type": "application/json"});
rsp.end(JSON.stringify({error:err}));
});
} else {
rsp.writeHead(500, {"Content-Type": "application/json"});
rsp.end(JSON.stringify('error - no key'));
}
});
NOTE: I am using this login function to test my other functions with Postman, so that is why i am sending a key, so I can use this privately.
Now combining the ADMIN and FIREBASE node apy I am able to do a lot of very interesting stuff with HTTP functions on my firebase.
Hope it helps somehow.
For Any Server Side React Users
I was brought here because I was attempting to authenticate users in firebase without the Javascript Firebase SDK on the client side as well. I am building a server side rendered react app. The client-side firebase.auth() does not work on a server-side node environment.
It turns out that you can run firebase.auth() commands inside of componentDidMount(), because that does not run on the server. This is where you can authenticate and get your user's token, and then send it to a cloud function for any server-side rendering that requires user authentication.
On the server side, you can then verify the token with the admin sdk.
You will also need to require firebase/app and firebase/auth, and initialize firebase in your browser-specific bundle.js, so that it is not included in your server's bundle.js
componentDidMount() {
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
console.log("User signed in!");
} else {
console.log("User NOT signed in!");
}
});
}
The officially recommended and supported way is to use ID TOKENS
From the official docs:
If your Firebase client app communicates with a custom backend server, you might need to identify the currently signed-in user on that server. To do so securely, after a successful sign-in, send the user's ID token to your server using HTTPS. Then, on the server, verify the integrity and authenticity of the ID token and retrieve the uid from it. You can use the uid transmitted in this way to securely identify the currently signed-in user on your server.
The workflow is:
Use the Firebase Web SDK in your client
The user logs in with any of the authentication methods
Retrieve the ID token on the client
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
// Send token to your backend via HTTPS
// ...
}).catch(function(error) {
// Handle error
});
Send this token to the server
Server verifies the ID token with the Firebase Admin SDK
// idToken comes from the client app
getAuth()
.verifyIdToken(idToken)
.then((decodedToken) => {
const uid = decodedToken.uid;
// ...
})
.catch((error) => {
// Handle error
});
your user is securely authenticated and uniquely identified
This is my solution, maybe it can help someone (Node/react). For some reason the client side method signInWithEmailAndPassword seems to work both on the client AND server. Basically this lets you keep the default security rule ".read": "auth != null" without having to use signInAnonymously() hence avoid creating an infinite number of stale users.
server:
const { firebase } = require('../../firebase/frontend');
const { firebase: admin } = require('../../firebase/backend');
const email = process.env.READ_ONLY_EMAIL;
const password = process.env.READ_ONLY_PASSWORD;
export default async (req, res) => {
try {
const { user } = await firebase.auth().signInWithEmailAndPassword(email, password);
const customToken = await admin.auth().createCustomToken(user.uid);
return res.status(200).send(JSON.stringify(customToken));
} catch (error) {
return res.status(404).send(error);
}
};
client:
import fetch from 'isomorphic-unfetch';
import { useState, useEffect } from 'react';
import { firebase } from '../firebase/frontend';
const useUser = (props) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [isAnonymous, setIsAnonymous] = useState(true);
const getCustomToken = async () => {
const response = await fetch('/api/auth', { method: 'POST' });
const json = await response.json();
return json;
};
useEffect(() => {
try {
const unsubscribe = firebase.auth().onAuthStateChanged(async (user) => {
// user exists
if (user && user.email !== 'readonly#myEmailAddress.com') {
setUser(user);
setIsAnonymous(false);
// else sign in user "anonymously"
} else {
setIsAnonymous(true);
const token = await getCustomToken();
firebase.auth().signInWithCustomToken(token);
}
setLoading(false);
});
return () => unsubscribe();
} catch (error) {
console.log('Error signing in user', error);
}
}, []);
return {
user,
isAnonymous,
loading
// etc...
};
};
export default useUser;
Related
At the moment I am creating a Firebase API on nodejs. I would like to handle all Firebase stuff (like authentication) with firebase-admin on nodejs. But what is the correct way to authenticate a user over nodejs in firebase-admin without the Javascript Firebase SDK on the client side? On the official documentation for admin I didn't find a function called signInWithEmailAndPassword (like as on the client side SDK) for nodejs. There is only a function called: "getUserByEmail", but this function doesn't check if the user has entered the correct password.
This is my form:
<form class="sign-box" action="/login" method="post">
<div class="form-group">
<input id="username" name="username" type="text" class="form-control" placeholder="E-Mail"/>
</div>
<div class="form-group">
<input id="password" name="password" type="password" class="form-control" placeholder="Password"/>
</div>
<button type="submit" class="btn btn-rounded">Sign in</button>
</form>
Once the form is submitted I pass the values to my API in nodejs:
app.post('/login', urlencodedParser, function (req, res) {
// getting the values
response = {
username: req.body.username,
password: req.body.password
};
// authenticate the user here, but how ?
});
My first idea was to use the Firebase SDK on the client side to sign in with signInWithEmailAndPassword and to get the uid. Once I had the UID I wanted to sent the UID to nodejs and call the function createCustomToken and to return the generated token (with some additional claims) back to the client. Once I get the token back I would use the function signWithCustomToken (on the client side) to authenticate the user. Is this way correct or is there a better way ?
Actually for authentication you will need to use the firebase regular api, no the admin.
First this will give you a refreshed firebase token, not a custom token.
If you like you can do the same to obtain a custom token, if you need a custom token, I also have an example.
npm install firebase --save
const firebase = require("firebase");
const config = {
apiKey: "",
authDomain: "",
databaseURL: "",
projectId: "",
storageBucket: "",
messagingSenderId: ""
};
firebase.initializeApp(config);
I am posting my login firebase function but you will be able to change it to express easily.
exports.login = functions.https.onRequest((req, rsp)=>{
const email = req.body.email;
const password = req.body.password;
const key = req.body.key;
const _key = '_my_key_';
let token = '';
if(key === _key){
firebase.auth().signInWithEmailAndPassword(email,password).then((user)=>{
//The promise sends me a user object, now I get the token, and refresh it by sending true (obviously another promise)
user.getIdToken(true).then((token)=>{
rsp.writeHead(200, {"Content-Type": "application/json"});
rsp.end(JSON.stringify({token:token}));
}).catch((err)=>{
rsp.writeHead(500, {"Content-Type": "application/json"});
rsp.end(JSON.stringify({error:err}));
});
}).catch((err)=>{
rsp.writeHead(500, {"Content-Type": "application/json"});
rsp.end(JSON.stringify({error:err}));
});
} else {
rsp.writeHead(500, {"Content-Type": "application/json"});
rsp.end(JSON.stringify('error - no key'));
}
});
NOTE: I am using this login function to test my other functions with Postman, so that is why i am sending a key, so I can use this privately.
Now combining the ADMIN and FIREBASE node apy I am able to do a lot of very interesting stuff with HTTP functions on my firebase.
Hope it helps somehow.
For Any Server Side React Users
I was brought here because I was attempting to authenticate users in firebase without the Javascript Firebase SDK on the client side as well. I am building a server side rendered react app. The client-side firebase.auth() does not work on a server-side node environment.
It turns out that you can run firebase.auth() commands inside of componentDidMount(), because that does not run on the server. This is where you can authenticate and get your user's token, and then send it to a cloud function for any server-side rendering that requires user authentication.
On the server side, you can then verify the token with the admin sdk.
You will also need to require firebase/app and firebase/auth, and initialize firebase in your browser-specific bundle.js, so that it is not included in your server's bundle.js
componentDidMount() {
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
console.log("User signed in!");
} else {
console.log("User NOT signed in!");
}
});
}
The officially recommended and supported way is to use ID TOKENS
From the official docs:
If your Firebase client app communicates with a custom backend server, you might need to identify the currently signed-in user on that server. To do so securely, after a successful sign-in, send the user's ID token to your server using HTTPS. Then, on the server, verify the integrity and authenticity of the ID token and retrieve the uid from it. You can use the uid transmitted in this way to securely identify the currently signed-in user on your server.
The workflow is:
Use the Firebase Web SDK in your client
The user logs in with any of the authentication methods
Retrieve the ID token on the client
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
// Send token to your backend via HTTPS
// ...
}).catch(function(error) {
// Handle error
});
Send this token to the server
Server verifies the ID token with the Firebase Admin SDK
// idToken comes from the client app
getAuth()
.verifyIdToken(idToken)
.then((decodedToken) => {
const uid = decodedToken.uid;
// ...
})
.catch((error) => {
// Handle error
});
your user is securely authenticated and uniquely identified
This is my solution, maybe it can help someone (Node/react). For some reason the client side method signInWithEmailAndPassword seems to work both on the client AND server. Basically this lets you keep the default security rule ".read": "auth != null" without having to use signInAnonymously() hence avoid creating an infinite number of stale users.
server:
const { firebase } = require('../../firebase/frontend');
const { firebase: admin } = require('../../firebase/backend');
const email = process.env.READ_ONLY_EMAIL;
const password = process.env.READ_ONLY_PASSWORD;
export default async (req, res) => {
try {
const { user } = await firebase.auth().signInWithEmailAndPassword(email, password);
const customToken = await admin.auth().createCustomToken(user.uid);
return res.status(200).send(JSON.stringify(customToken));
} catch (error) {
return res.status(404).send(error);
}
};
client:
import fetch from 'isomorphic-unfetch';
import { useState, useEffect } from 'react';
import { firebase } from '../firebase/frontend';
const useUser = (props) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [isAnonymous, setIsAnonymous] = useState(true);
const getCustomToken = async () => {
const response = await fetch('/api/auth', { method: 'POST' });
const json = await response.json();
return json;
};
useEffect(() => {
try {
const unsubscribe = firebase.auth().onAuthStateChanged(async (user) => {
// user exists
if (user && user.email !== 'readonly#myEmailAddress.com') {
setUser(user);
setIsAnonymous(false);
// else sign in user "anonymously"
} else {
setIsAnonymous(true);
const token = await getCustomToken();
firebase.auth().signInWithCustomToken(token);
}
setLoading(false);
});
return () => unsubscribe();
} catch (error) {
console.log('Error signing in user', error);
}
}, []);
return {
user,
isAnonymous,
loading
// etc...
};
};
export default useUser;
I am managing an application written in Node.js (restful backend) and React (frontend) and use AWS Cognito for user authentication.
In the frontend, I have a form which requests my backend to delete a user. The only information the frontend can send me is the username (email in my case) and an access token which I sent to the frontend after a successful login.
Now I want to delete a user. Until now, I tried it with the deleteUser method (see Use case 13 in the js documentation).:
const poolData = {
UserPoolId: process.env.COGNITO_USER_POOL_ID,
ClientId: process.env.CLIENT_ID
};
AWS.config.region = process.env.AWS_DEFAULT_REGION;
const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
// ...
router.post('/deleteUser', (req, res) => {
var userData = {
Username: req.body.email,
Pool: userPool
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
if (cognitoUser == null) {
res.status(400).send('User not found');
return;
}
cognitoUser.deleteUser(function(err, result) {
if (err) {
res.status(400).send('Could not delete user');
} else {
res.status(200).send('Deleted user');
}
});
});
This throws an error in the error block of deleteUser section with Error: User is not authenticated. I need to firstly authenticate the user but I won't get the password from the frontend to do so. I just have the access token it can provide me.
How can I authenticate the user with the provided token? Or am I misunderstanding a concept here? If so, what am I doing wrong?
Any help will much appreciated.
I am working on some firebase functions. This one will check if an user is logged in in firebase first. However this is a bit of a hassle in development. Now I need to login on the frontend first to get the id_token, pass it to my function url, then see the result.
The process I am following is described in the official docs: https://firebase.google.com/docs/auth/admin/verify-id-tokens
node.js
const admin = require('firebase-admin');
admin.initializeApp();
module.exports = function( request, response ) {
if( !request.query.id_token )
response.status(400).json({message: 'id token has not been provided'});
admin.auth()
.verifyIdToken( request.query.id_token )
.then( token => {
// TODO: redirect to payment portal
return response.status(200).json({message: 'Success'});
})
.catch( error => {
return response.status(401).json({message: 'You are currently not logged in as an authorised user'});
})
}
Is there a way to get an id_token that is valid from firebase without having to spin up my frontend? Good and simple alternatives solutions are welcome too.
NOTE: I am using the firebase emulators during development.
Since you're using the Firebase emulators you may create a fake user and retrieve an id token programmatically. The code below creates and logs in a user and returns an id_token that will be accepted by your function.
var firebase = require("firebase/app");
require("firebase/auth");
// Initialize Firebase and connect to the Authentication emulator
var firebaseConfig = {
// Insert Firebase config here
};
firebase.initializeApp(firebaseConfig);
firebase.auth().useEmulator('http://localhost:9099/');
// Create a fake user and get the token
firebase.auth().createUserWithEmailAndPassword("example#example.com", "password")
.then((userCredential) => {
console.log("User created")
});
firebase.auth().signInWithEmailAndPassword("example#example.com", "password")
.then((userCredential) => {
console.log("User logged in")
userCredential.user.getIdToken().then((idToken) => {
console.log(idToken)
});
});
Dears,
I'm trying to find how managing authentification on client side using the hhtp-only cookie sent by the server.
What I don't understand is that since the HTTP only cookie can't be accessed by the front end, how the front end knows that the user is (still) authenticated ?
So far, the only solution if found is to send to the client a token when the authentication succeed. And keep this token in a second cookie created by the client.
But it seems to me that I'm doing the same job twice.
1- managing the HTTP only cookie on server side, especially the expiration date
2- managing also on client side the expiration date of the second cookie.
How can avoid this ? I'd like to manage the authentification on client side based on the HTTP only server cookie. If there is a server cookie, then go on, else redirect to login page.
I'm using node/express on server side and react on client one. The session is stored in redis, both sides are HTTPS using certificates.
Thks
You don't need to store another cookie.
I suppose you use token based authentication on your endpoint, eg. JWT. Then you think about this scenario:
User send username/password to server.
Check user credentials and if there are valid, create http-only cookie with the token
const user = await getUser({ where: { email } });
const valid = await bcrypt.compare(password, user.password);
if (!valid) {
throw new UserInputError('Form Arguments invalid', {
invalidArgs: {
'password': 'Invalid password!',
},
});
}
const token = jwt.sign({ userId: user.id }, process.env.APP_SECRET);
/
res.cookie('token', token, {
httpOnly: true,
maxAge: 1000 * 60 * 60 * 24 * 365,
});
Write auth middlerware to put the userId onto the req for future requests to access
const jwt = require('jsonwebtoken');
const { AuthenticationError } = require('apollo-server');
module.exports = async function(req, res, next) {
const { token } = req.cookies;
if (token) {
try {
const { userId } = jwt.verify(token, process.env.APP_SECRET);
if (!userId) return next();
req.userId = userId;
} catch (e) {
console.log(e);
}
}
next();
};
Check on each request the userId. If there is no userId, user doesn't logged in
if (!req.userId) {
throw new AuthenticationError('Log in!');
}
If user's token is invalid/expired you will get AuthenticationError. Catch it and redirect to login page.
If your UI depends on user status, you can create easy-to-use component (i am using React) to check it.
User Component:
import { Query } from 'react-apollo';
import gql from 'graphql-tag';
import PropTypes from 'prop-types';
const CURRENT_USER_QUERY = gql`
query CURRENT_USER_QUERY {
me {
userId
firstName
lastName
profilePictureUrl
}
}
`;
const User = props => (
<Query {...props} query={CURRENT_USER_QUERY} fetchPolicy={'cache-first'}>
{payload => props.children(payload)}
</Query>
);
User.propTypes = {
children: PropTypes.func.isRequired,
};
export default User;
If we get me object from server, you know, there is a logged in user, so you can render depends on user's status:
import { Link } from 'react-router-dom';
import React from 'react';
<User>
{({ loading, error, data: { me } }) => {
if (loading || error || !me) return (
<Button component={Link} to={'/login'}>Login</Button>
);
if(me) return (
<Button component={Link} to={'/dashboard'}>Go to dashboard</Button>
)
}}
</User>
I'm trying to create JWT tokens in node.js for use with the REST api in firebase, but when I try to use them, I get the error "Error: Invalid claim 'kid' in auth header."
This is my code
http.createServer(function (req, res) {
var payload = {
uid: "bruh"
};
var token = jwt.sign(payload, sact["private_key"], {
algorithm: 'RS256',
issuer: sact["client_email"],
subject: sact["client_email"],
audience: 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit',
expiresIn: '3600s',
jwtid: sact["private_key_id"],
header: {
"kid": sact["private_key_id"]
}
});
res.writeHead(200);
res.end("It worked. (" + token + ")");
}).listen(port);
These are my requires
var http = require('http');
var jwt = require('jsonwebtoken');
Please use returnSecureToken: true, with correct Spellings
I hope it will solve the problem of Invalid claim 'kid' in the auth header.
This is an issue because you're generating a Firebase ID token, not an access token for the Firebase REST API.
To generate a REST API token I would use the legacy Firebase Token Generator library which still works perfectly well (but only generates REST tokens, not general purpose access tokens).
Note that your Firebase Database secret is now located under the gear icon in the top left of the console.
So I had this error and I've fixed it. Now here is the solution:
You'll need to retrieve the ID-token using an additional function. Here is the function you can use:
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
// Send token to your backend via HTTPS
// ...
}).catch(function(error) {
// Handle error
});
I implemented it somewhat like this:
//google OAuth login handler
const googleLoginHandler = () => {
const provider = new firebase.auth.GoogleAuthProvider();
firebase.auth()
.signInWithPopup(provider)
.then((result) => {
/** #type {firebase.auth.OAuthCredential} */
setgoogleAuthStatus(true)
// The signed-in user info.
const userId = result.user.uid;
const displayName = result.user.displayName;
const email = result.user.email;
//This is the function for getting the ID-Token
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then((idToken) => {
// Send token to your backend via HTTPS
console.log(idToken)
}).catch((error) => {
// Handle error
console.log(error.message)
alert(error.message)
});
console.log(result)
}).catch((error) => {
console.log(error)
// Handle Errors here.
alert(error.message)
})
}
The id token you get by this method can be used to access the firebase real-time database and other firebase services.
check out these links for more details:
https://firebase.google.com/docs/auth/admin/verify-id-tokens#retrieve_id_tokens_on_clients
https://firebase.google.com/docs/database/rest/auth#firebase_id_tokens