I wrote the following code for Firebase funcations:
exports.deleteVolunteer = functions.firestore.document(`${COLLECTION_PREFIX}/users/{userPhoneNumber}`).onDelete(async (snap, context) => {
const userPhoneNumber = context.params.userPhoneNumber;
try {
const userRecord = await admin.auth().getUserByPhoneNumber(userPhoneNumber);
await admin.auth().deleteUser(userRecord.uid);
console.log('Successfully deleted user with phone number: ' + userPhoneNumber);
} catch (error) {
console.log('Failed to delete user with phone number: ' + userPhoneNumber + ' with error ' + error);
}
return null;
});
Basically, once it see some document is removed in the cloud database, it removes the user from the auth service. I would like to exit all sessions from all devices that this user is logged in. As you can see the user connects to the app with a phone number. How can I do it?
When a user signs in to Firebase Authentication they get back an ID token that is valid for one hour. Until that token expires, there is no way to revoke it - at least not without changing the key that is used to sign all tokens.
This means that there is no way for the server to terminate existing sessions instantly.
Instead the common way to instantly lock out users is:
Send a signal to the clients that they need to refresh the token, which will sign out those clients - and prevent them from signing in again. This of course won't stop a malicious user from trying to use the existing token, so...
Check server-side whether the user account was deactivated before performing a sensitive operation. You can do this against the Firebase Authentication Admin SDK, but more common is to store the UIDs of recently deactivated accounts in the database you use, and then check in security rules or code.
For an example of this see the documentation on checking for ID token revocation.
Related
Update
As Gerardo pointed out, vapidKey != FCM Token.
I finally got things going, though im unsure if this is the best way to do it.
This was what i needed to generate a NEW token:
try{
if(currentToken) await deleteToken(messaging,currentToken)
if(Notification.permission !== 'granted') await Notification.requestPermission();
currentToken = await getToken(messaging,vapidKey);
$.ajax({
type:"GET",
url:"/fcm/register-token",
data:{token:currentToken},
success:(saved)=>{
if(saved) console.log(saved,' New token registered ',currentToken)
else console.log('Unable to save token!')
}
})
}
catch(e){console.log(e)}
Question
I am testing FCM for push notifications in javascript. I have a single "Web Push Certificate" (vapidKey) generated manually in the FCM console UI. This works for sending a test notification to a single token, but i am unclear on how any additional users would be able to register a new key with FCM.
I must be misunderstanding something fundamental here, and am not even sure what to be searching for.
// TODO: Find out how to generate new registration token as needed
const vapidKey = {vapidKey:"BLgq_6FY0suqAy9llMyYX03o9K9n4FHba1gCnPz6KmG9oh6K8v3Ws4SIG9KTILfN0_8np9PfT5bPqgTDXfCA9dc"};
async function fcm_init(){
try{
// TODO: user interaction for notification permission
await Notification.requestPermission()
const currentToken = await getToken(messaging,vapidKey);
// TODO: Send token to server to save in tbl_fcm
console.log(currentToken);
}
catch(err){
console.log(err);
}
}fcm_init()
In this example, the vapidKey is hard-coded, but how would i go about getting a different key for each user in the app without manually doing so in the FCM Console UI?
This is where i have generated the key to get a token for a user. This is what im trying to avoid doing manually for each user on the system.
The vapidKey is needed for identifying the app (not the user). This is mentioned in the second screenshot "Firebase Cloud Messaging can use Application Identity key pairs to connect with external push services".
With this information, your app can generate an FCM Token (or registration token) in the client:
const currentToken = await getToken(messaging,vapidKey);
You can use these FCM tokens to send messages to a specific device. These FCM Tokens identify the device (user) and they will be different for each one.
Check this example on how you would send a message from the back end to a specific device using it's FCM token (registration token).
Action:
- signInWithPhoneNumber(NUMBER NOT IN DB, recaptchaVerifier)
Expected Behavior:
- Since number not in DB, it should not log me in.
Current Behavior:
- If the number does not exist in DB, it CREATES a new user after going through recaptcha + sms verification. WHY?
Code:
function loginWithSMS(phoneNumber) {
firebase.auth().useDeviceLanguage();
//#ts-ignore
window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier("recaptcha-container");
//#ts-ignore
window.recaptchaVerifier.render().then(function (widgetId) {
//#ts-ignore
window.recaptchaWidgetId = widgetId;
});
// #ts-ignore
firebase
.signInWithPhoneNumber(phoneNumber, window.recaptchaVerifier)
.then((confirmationResult) => {
console.log("Login success", confirmationResult);
window.recaptchaVerifier.clear();
// SMS sent. Prompt user to type the code from the message, then sign the
// user in with confirmationResult.confirm(code).
const verificationCode = window.prompt(
"Please enter the verification " + "code that was sent to your mobile device."
);
return confirmationResult.confirm(verificationCode);
})
.catch((error) => {
console.error(error);
// Error; SMS not sent
// Handle Errors Here
window.recaptchaVerifier.clear();
return Promise.reject(error);
});
}
This is just how the API is defined: by sending a text to the number, Firebase allows the user to verify that they have access to that phone number. If they do, they're allowed to sign in.
This is the same for the email+password provider in Firebase Authentication. Calling firebase.auth().createUserWithEmailAndPassword(email, password) creates the user, even if they didn't exist yet. And while your code may not call this API, any developer can take the Firebase configuration data from your app and call the API themselves.
Most often when developers are asking about this they're confusing authentication with authorization.
When you authenticate, you are proving that you are you. So in the examples above, that you have access to a certain phone number, or that you know the email+password combination of the account.
Based on knowing who the user is, the application then authorizes that user to perform certain actions or to access certain data.
For example, if you're using Realtime Database, Cloud Storage, or Cloud Firestore, you can control access with Firebase's server-side security rules.
If you have a different back-end, you'd control it there by checking the information in the ID token of the user (which you get from Firebase Authentication) against some set of authorization rules for your application.
Also see:
Prevent user account creation with sign in by email in firestore (similar question, but then for passwordless email signin)
How to disable Signup in Firebase 3.x
How does the firebase authentication and realtime application database secure itself?
For some time I have been writing an application based on this tutorial:
https://jasonwatmore.com/post/2019/04/06/react-jwt-authentication-tutorial-example
My application is already much more developed, it uses other rest API servers, etc. Currently, after positive authentication, a JWT token is sent to the client, which is stored in localStorage. But!
I need to add such functionality: the assumption is that only one user can use one account at a time.
There are two solutions:
a) when the user is logged in, he logs out in the previous place after logging in on the other device/browser.
b) when user trying log in another device/browser get a throw for example "Client already login"
So, From what I learned, I can't do it using JWT. My question is :
What can I use instead of JWT to handle the session because I think it's about?
Thanks.
EDIT : My authenticate funtion from server side :
async function authenticate({ username, password }) {
console.log(username + " " + password);
const user = await User.findOne({ username });
if (user && bcrypt.compareSync(password, user.hash)) {
const { hash, ...userWithoutHash } = user.toObject();
const token = jwt.sign({ sub: user.id }, config.secret, { expiresIn: 300 });
return {
...userWithoutHash,
token
};
}
}
You may use a more traditional session ID based authentication approach. When a user logs in, a session ID (something along the lines of a UUID) gets generated. Then, using the user ID as a key, the session ID gets stored in a hashmap on the server. In all subsequent login attempts, the session ID in the map would be overwritten for the same user.
I suggest kicking out any other session during subsequent login for usability reasons. Imagine logging into your application from some device, but suddenly getting pulled into a meeting somewhere. Now, if we don't kick out previous sessions during login, any attempt on your part to login from a different device would fail, and you would be forced to wait to be able to login.
Note that once the new session ID has been recorded in the hashmap, any access attempts by the first device using the old session ID would fail.
You can use JWT to handle the session itself.
But to handle multiple logins to a single account, you can for example add a locked attribute to your users table in your database, provided you have such thing in your architecture. This would allow you to lock your session.
This solution only makes sense if you want to totally lock your session until logout (and not be able to login from another device for instance). If that is not your intention, I recommend Tim Biegeleisen's answer.
Example:
users || id | name | locked
----- || -- + ---- + ------
|| 1 | John | 0
|| 2 | Bob | 0
Then, we could imagine your session handling to be as such (simple example for an Express app, but see it as pseudo code):
database.query("SELECT locked FROM users WHERE name = $1", [name])
.then((data) => {
if (data.locked === 1) {
res.status(403).json();
} else {
database.query("UPDATE users SET locked = 1 WHERE name = $1", [name])
.then(() => {
// Define token key and object for jwt
// ...
var token = jwt.sign(...);
res.status(200).cookie('token', token).json();
});
}
});
And then, when a user logs out or is logged out by an expired jwt token, you call another endpoint of your API and you set the locked attribute to 0.
Hope this helps!
I'm working with an unofficial n26 API (https://github.com/PierrickP/n26/tree/develop). The api requires the email and password to login to the account.
I don't want the user to login every single time so I need to store these information somehow.
I can't find any way to get a session token or something for later use from the api. (maybe someone of you can find it??)
So my question: How do I store Email/Password for later use in a secure way?
const N26 = require('n26');
// Log into the account
const account = new N26('example#mail.com', 'password');
// Get the last 10 transactions
account.then(account => account.transactions({limit: 10}))
.then(transactions => {
// Do something with it
});
http://www.passportjs.org/ Check out this package.
things to consider:
- json web tokens with refresh token
- API-keys
I have implemented the signin method using Firebase Auth for several providers like that:
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL).then(() => {
let provider = new firebase.auth.GoogleAuthProvider(); // + facebook, gitHub
provider.addScope('profile');
provider.addScope('email');
firebase.auth().signInWithPopup(provider).then(result => {
// app logic here
However this code gives me 60 min lasting sessions and I want to learn how to automatically renew the current user session without being forced to login every hour.
I'm also 'listening' to the current user session state using this code.
firebase.auth().onAuthStateChanged(user => if (!user) //goto LoginPage
But it doesn't 'listen' per se, it works only when I try to navigate or update the page. So I don't know how to restrict access by the exact amount of time (e.g. 15 minutes max) using Firebase methods.
The documentation says there is a getIdToken method but I can't wrap my head around where to use this code. Should it be invoked every 60 minutes just before the expiration or it should be set at the time of login? Please give some hints or any tutorials covering this very situation.
EDIT:
Also I get this code in the console after some period of inactivity (I think less than 1 hour):
auth.esm.js:121 POST https://securetoken.googleapis.com/v1/token?key=AIza... 403
Firebase tokens are set to expire after 60 min. Then it gets refreshed for you automatically. There is no way to configure the expiration time, and you don't need to do anything special in your front-end code for that.
The only trick is that, you need to grant your application API key the permission to use the Token Service API to be able to mint a new id token for you once it's expired. This is done in the GCP console, API & Services (Credentials).
So, the code should be simple as the following
Add the user authentication state change Listener
fbAuth.onAuthStateChanged(user => {
if (user) {
// User is logged in
// Proceed with your logged in user logic
} else {
// USer is not logged in
// Redirect to the login page
}
})
Implement the login logic
fbAuth.setPersistence(firebase.auth.Auth.Persistence.LOCAL)
.then(() => {
return fbAuth.signInWithEmailAndPassword(email, password)
.then(userCredential => {
// Login success path
})
.catch(error => {
// Login error path
});
})
.catch(error => {
// Persistence setting error path
})
You can set the Authentication State Persistence before the login, depending on your use cases auth-state-persistence.
Make sure that your application API key has access to the Token Service API in the GCP console
This is under
GCP Console | APIs & Services | Credentials
Then edit the corresponding key to your deployment environment to grant the API key the access to the Token Service API.
Good luck ;)
Hello first I am gonna say sorry fro my bad english. I dont really understand firebase but i think it should work if you write something like this:
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION).then(() => {
let provider = new firebase.auth.GoogleAuthProvider(); // + facebook, gitHub
provider.addScope('profile');
provider.addScope('email');
firebase.auth().signInWithPopup(provider).then(result => {
// app logic here
I meant that you should have firebase.auth.Auth.Persistence.SESSION insted of LOCAL