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!
Related
I have a React frontend with a Node + MySQL backend, I'm sending an email to an user with two buttons to accept or decline a quote. What I'm trying to achieve is to make the buttons in the email modify the database securely without the user having to log into his account. My idea is to have two routes, one that sends the email containing the buttons which will have a url to my website with the jwt token on its parameters, and another for verifying said token and making the changes to the db. Here's some pseudo-code:
app.post("/email-quote", async function (req, res) {
const payload = {
uid: req.body.user.id,
quoteId: req.body.quote.id,
accepted: // true for the accept button, false for the decline button
}
const secret = ?
const token = jwt.sign(payload, secret);
// ...
// Generate and send email with buttons containing the url + the token
});
When the user clicks one of the buttons, I re-direct him to my website and there I can extract the token and verify its validity:
app.get("/verify-email-quote/:token", async function (req, res) {
const decodedJwt = jwt.decode(req.params.token);
const secret = ?
const verifiedJwt = jwt.verify(req.params.token, secret);
if (verifiedJwt) {
// Make the changes to the db
}
});
I wasn't able to find any examples trying to achieve something similar on the web, so I have these questions:
Would a jwt token be a good approach to achieve this?
If yes, what secret should I use to create the token?
If no, what other solutions could I look into?
Yes, you can do it this way.
The secret does not matter. As long as the secret is secret
It doesn't need to be a jwt token. It can just be a normal token. The incentive to using jwt is that you can embed a payload into the token. For your case, it looks like it is exclusively for verification purposes. It doesn't matter in the grand scheme of things, but if you don't have jwt already implemented, there's no need to go through all that extra work just for this use case.
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.
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
I am trying to create a function that, when a device is registered in the app, will attach this device uid to the uid of the signed-in user who registered the device (this is in another firestore collection that is automatically created when a user registers).
Here is my code:
exports.addDeviceToUser = functions.firestore.document('device-names/{device}').onUpdate((change, context) => {
const currentUser = admin.auth().currentUser;
const deviceName = context.params.device;
var usersRef = db.collection('users');
var queryRef = usersRef.where('uid', '==', currentUser.uid);
if (authVar.exists) {
return queryRef.update({sensors: deviceName}).then((writeResult => {
return console.log('Device attached');
}));
} else {return console.log('Device attachment failed, user not signed in');}
});
I am consistently getting this error: "TypeError: Cannot read property 'uid' of undefined." Obviously I am not able to access the auth information of the current user. Why?
The Admin SDK doesn't have a sense of current user. When you say admin.auth(), you're getting back an Auth object. As you can see from the API docs, there is no currentUser property on it. Only the Firebase client SDK has a sense of current user, because you use that to get the user logged in.
If you need the client app to tell Cloud Functions code work with the user's identity, you have to send it an ID token from the client, and verify it on the server. Then the server can know who the end user is, and perform actions on their behalf. Typically you do this with an HTTP type trigger. Callable functions transmit this data automatically between the client and server, but you can do it manually yourself using code that works like this sample.
Right now, Firestore triggers don't have immediate access to the end user that made a change in the database. However, if you use the Auth UID of the user as the key of the document, and protect that document with security rules, you can at least infer the UID of the user based on the changes they make to the document by pulling it out of the id of the document that changed.
Because, by design, Cloud Functions executes on the back end and do not hold any information on which user was authenticated when adding/modifying the data in the database.
When writing the data in the 'device-names/{device}' document (from your app), you could include an extra piece of data which is the uid of the current user.