Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Two-Factor Authentication #4024

Closed
jasonm1 opened this issue Jul 19, 2017 · 44 comments
Closed

Two-Factor Authentication #4024

jasonm1 opened this issue Jul 19, 2017 · 44 comments
Labels
type:feature New feature or improvement of existing feature

Comments

@jasonm1
Copy link

jasonm1 commented Jul 19, 2017

I see no straightforward way to implement two-factor authentication (2FA) in Parse Server. Ideally this would allow:

  • 2FA using one or more methods (Google authenticator, SMS, email)
  • Device memory (with periodic expiration) so users don't necessarily need to enter the second factor all the time
  • Any particular user to have 2FA enabled/disabled optionally

I initially started implementing this in my client-side code (PHP) for my Administrator users using Goole Authenticator, but then I realized anyone with the AppID and the Admin password can simply write their own code to get around it. Oops.

I then thought about storing a second factor, or possibly an IP whitelist, and rejecting any requests in beforeSave, beforeDelete, or beforeFind Cloud Code where the client did not provide the second factor, but that seems clunky. It is also difficult to rate-limit, since the malicious user would be able to log in and only be blocked at the point of accessing data. Further, I believe the second factor would need to be stored apart from the User object itself, since once logged in, the user can access its own data.

@flovilmart suggested using a 3rd party Auth adapter, for example Google, which could potentially work in my case since I only care about the extra security for certain users. However, I don't want to require Google login for everyone, which means native username/password login is still available. Checking triggers in Cloud Code for the particular users could be a workaround, but again, the user would be checked at the point of accessing data, not logging in. I also don't know if this solution would generalize to others' use cases.

One solution I see is to add a beforeLogin trigger in Cloud code. A routine in this trigger could check the second factor (stored with the user object) and reject the Login if it does not match or is not provided. This would also allow for IP whitelisting for certain users (reject login attempts from non-listed IPs) or any variety of custom authentication methods.

Another may be to have per-user control over the authentication method. For example, if a user can login with Google, the user can ONLY login with Google, and not username/password.

I am not familiar enough with how Parse Server handles logins to know if the beforeLogin trigger is feasible, or if some other 2FA system would be easier to implement.

@flovilmart
Copy link
Contributor

We could probably bake 2FA in, with QRcode generation, and secure login/sessions. This would be a major feature where users could opt-in for 2FA. That would be quite a major development touching a few areas of the API, but that wouldn’t be anything unachievable

@jasonm1
Copy link
Author

jasonm1 commented Jul 19, 2017

What do you think of the beforeLogin trigger solution? I noticed a discussion on that a few days ago but it didn't end up addressing the suggestion.

@flovilmart
Copy link
Contributor

That would be kind of messy as tou’ll Need to store each user’s secret anyway, enforce etc.. that could help you, but i’d Like that to be baked in :)

@jasonm1
Copy link
Author

jasonm1 commented Jul 20, 2017

I like the sound of a native 2FA solution as well! I will try to start looking into a first stab at this soon.

@montymxb
Copy link
Contributor

montymxb commented Nov 8, 2017

Just bumping this, I think this would be 💯 % awesome to put this in. If I were to just brainstorm off the top of my head this is what comes to mind.

  • Default adapter for 2FA, allowing other services to be added if they pop up
  • optional twoFactorEnabled on _User to track whether or not this is even an option
  • twoFactorSecret for this user (if the above is set), could screen this field away from querying since it's a bit sensitive.
  • add some routes to UsersRouter.js like enableTwoFactor to get a QRCode/token, disableTwoFactor to remove it and verifyTwoFactor to actually run the check. Could be spread out further or consolidated into one endpoint though.
  • Upon login, if twoFactorEnabled is true request that the user proceed to submit their second factor code, again this could go separately to something like verifyTwoFactor, maybe taking the username and code. The session upon login, but pending 2fa, would have to be available to the user, but locked to any further action.
  • If validation succeeds clear the block and allow the user normal access with their session token.

As for blocking requests when pending 2fa, the primary concern is in making sure that until the user can authenticate themselves they should not be granted access as themselves. Since we have ACLs, this, in a nutshell, means we need to treat them like they're anonymous and subjected to the restrictions of public read/write. There are a number of ways this could be done, but I'm wondering if maybe not returning the user on successful login with 2fa enabled would be the easiest. Instead we could return something to indicate that further authentication is required.

That's a pretty rough outline, and it doesn't elaborate on how the SDKs would go about handling this, but it would be a start in the right direction 👍 .

@flovilmart
Copy link
Contributor

That would be at session token emission level, when enabling 2FA, all sessions would be destroyed, the 2FA logIn flow could force having an additional ‘code’ property alongside username/email + password, I dot believe there’s a need for a temporary state (valid username + pass) waiting for ussename + password. Also, providing username + code only should work instead of username + password ?

@flovilmart
Copy link
Contributor

Perhaps providing the 2FA code in je password field would be ok-ish, not sure...

@montymxb
Copy link
Contributor

montymxb commented Nov 8, 2017

The username and the code combined would have to be something separate, but it would use some stage so you can't just jump to it unless you've already logged in.

The thought of having 2FA directly with the username and password did occur to me as well. Normally it's requested separately, but honestly I can't think of a good reason why it's not requested up front. If we did, we just authorize on an all or none basis, either it matches password and the 2F code or it fails. That could make it a lot easier to do this, no extraneous 'stage' or 'steps' required (beyond the step of validating the 2F token itself).

@montymxb
Copy link
Contributor

montymxb commented Nov 8, 2017

The thing above would have to be something like login and then validateTwoFactor being the second required step, if it were separate that is.

The only issue with having the password and two factor go together is when your 2nd factor is something like a static code that's generated upon successful login. You wouldn't be able to get your code to login with until you had successfully logged in in the first place. If we did an adapter to allow extra 2FA methods that could screw things up if someone did something like an SMS or email code.

@flovilmart
Copy link
Contributor

To enable2FA you usually have to provide the generate code to guarantee he user has property setup his account, so no chance to get locked out. Maybe an endpoint /enable2FA that takes the sessionToken (you need to be loggedin) and the 2FA code, if you fail to provide a valid code, your account stays 1FA secured, upon success, the sessionToken gets regenerated, and 2FA is enforced

@montymxb
Copy link
Contributor

montymxb commented Nov 8, 2017

Yep, for setting it up the user would have to successfully provide the proper 2F code at least once before it can be enabled. Could split that up into something like requestEnableTwoFactor and enableTwoFactor. The first endpoint taking the current sessionToken and returning anything necessary to setup 2FA on their second factor. The second endpoint taking the expected 2F token from that user and enabling it on success!

We would need to save and track that a user is in a state of:

  • enabling two factor (turning it on)
  • pending two factor (normal authentication)

Maybe could add a step for disabling two factor if we want to keep it strict.

@mnlbox
Copy link

mnlbox commented Aug 6, 2018

@flovilmart, @jasonm1, @montymxb
Any news about this?
Is it possible to do this with cloud code?
If yes, did you provide some example app or blog post about this?

@flovilmart
Copy link
Contributor

We haven’t worked on it, it’s still up for grabs

@georgesjamous
Copy link
Contributor

@mnlbox

Is it possible to do this with cloud code?

well i think everything can be achieved through cloud code, baking it in is a bit more difficult.
Also when using cloudcode you have to find a way to disable the login route, because this would go around any extra function/restriction you set up there.

@mnlbox
Copy link

mnlbox commented Aug 6, 2018

@georgesjamous are you any other suggestion?
Maybe we need more simpler way.

@georgesjamous
Copy link
Contributor

Well, i would wait for a fully implemented solution without cloudcode to use it.

There are many ways to achieve this, for a quick setup you could do it by implementing 2 functions 'verifyAccount' and 'provide2fa'.
'verifyAccount' could accept the username only, make sure he exists.
The other would accept the password, 2fa code and other important information about the operation. The validation goes in 'provide2fa' as well as the login, it return the session.
Then you can become the user in the front-end sdk. This is how i would do it.

And if the beforeSaveHook works for '_Session', you can deny any session that is authenticated via the legacy login by responding with an error. (i have never tried a hook with sessions)

@addisonElliott
Copy link
Contributor

Session hooks do NOT work.

There is an issue and pending PR for this functionality. Just FYI

@mnlbox
Copy link

mnlbox commented Aug 7, 2018

Guys how about implement this with more general tools like Authelia.

https://github.com/clems4ever/authelia

@georgesjamous
Copy link
Contributor

Well then if there is no way we can intercept the login request just from using cloud then its impossible - at this point - to implement 2FA securely.

@mnlbox
Copy link

mnlbox commented Aug 7, 2018

Maybe we also can set login endpoint to deny for prevent this. 🤔
It's just and idea and please lets talk about this here.

@georgesjamous
Copy link
Contributor

Maybe we also can set login endpoint to deny for prevent this. 🤔

what do you mean by this ?

@mnlbox
Copy link

mnlbox commented Aug 8, 2018

Login endpoint.

@mnlbox
Copy link

mnlbox commented Aug 12, 2018

@georgesjamous and other guys any idea about this?
Really how you handle 2FA? 🤔

@georgesjamous
Copy link
Contributor

georgesjamous commented Aug 13, 2018

@mnlbox
I highly recommend you wait or this to be implemented or open a PR for it your self,
i believe others will help you.

Any other solution using cloud code only will not be secure. The only problem is that you cannot intercept login (or sessions) at this time in cloud code, and if you implement you own, anyone with sufficient knowledge (clientkey, appid, username and password) could login the traditional way and get a session token.
I have already told you how i would do it up there (but it is not secure), again because the traditional login is still exposed.

If you are determined to have 2fa working right away (and this is what i think is going on here), a messy way would be to fork parse-server, alter it so you can get a beforeSave hook on sessions.
I believe there is an open ticker/pr to make sessions read only with hooks out there but i cant find it, this is the line where you should make the change anyway L33.

Once you do, whenever a session is created you can check for a 'temporary-one-time-use-2fa-token' that is generated by your 2fa code.

So,
1- Remove the check on the session token hook. Use your fork in package.json
2- Create the 2 functions for 2fa (and use whatever 2fa endpoint provider you like)
3- After you successfully check 2fa, generate/use a one-time-use-token and store it in a class somewhere (give it a 2-5 seconds of validity)
4- In sessions, whenever a news one is created. Get the user, check if 2fa is on. If it is look for the token of step3 and allow/deny depending on the case then delete the token.

@mnlbox
Copy link

mnlbox commented Aug 26, 2018

Maybe useful: https://passwordless.net/

@georgesjamous

@somq
Copy link

somq commented Oct 4, 2018

@mnlbox Did you find any solution in the end?
@flovilmart, @georgesjamous Do you know if anyone has started working on this finally?

@flovilmart
Copy link
Contributor

I haven’t!

@flovilmart
Copy link
Contributor

flovilmart commented Nov 13, 2018

Too bad you moved to Strapi, I was about to start working on it as we’ll need it for future projects.

@mnlbox
Copy link

mnlbox commented Nov 14, 2018

@flovilmart please tell result here. Maybe we can switch back to Parse if it have 2FA support.
I think this library is more better than other:
https://github.com/yeojz/otplib
Take a look to it 😉

@flovilmart
Copy link
Contributor

We haven’t started yet. The best way to get it faster would for for you to implement it and propose a PR

@stale
Copy link

stale bot commented Dec 29, 2018

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Dec 29, 2018
@stale stale bot closed this as completed Jan 5, 2019
@thisfiore
Copy link

What about:

  • making a sort of 2Factor token on Sessions (2FToken) that you obtain by correctly logging-in with 2FA
  • So if the user has 2FA enabled, Parse Server checks per request basis ( like ACL works ) the session for a valid 2FToken else returns unauthorized to the request

So you may have unauthorized user log-in but not capable of doing anything.

Could be a good solution? We could try to implement it @Etto91

@flovilmart
Copy link
Contributor

This could be implemented on the _User class, adding the TOTP token to the _user object (private obviously) in order to create a session, as user need to provide the username, password and OTP.

One security enhancement would be to generate a restricted session token, that is used solely with an OTP and valid a few minutes. This session Would ne generated on successful user login.

@REPTILEHAUS
Copy link

@mnlbox, @flovilmart, @georgesjamous, @addisonElliott has anyone tried to work on this recently ? or got any pointers.

I found this PR #5305 which is a start, 2FA is a really essential and useful feature that should be part of any authentication system, it would be great if we could pick this back up and work on an implementation

@Etto91
Copy link

Etto91 commented Sep 17, 2020

very bad pr #5305 :)

@REPTILEHAUS
Copy link

@Etto91 you got further than anyone else with it so not so bad. I dont suppose you did any more work on it that I can use for inspiration :D I don't know much about the parse internals but you seem to.

@mtrezza
Copy link
Member

mtrezza commented Sep 21, 2020

Re-opening as discussion around this feature continues.

@mtrezza mtrezza reopened this Sep 21, 2020
@stale stale bot removed the stale label Sep 21, 2020
@mtrezza mtrezza added type:feature New feature or improvement of existing feature and removed enhancement labels Sep 21, 2020
@mtrezza mtrezza changed the title Feature Request: Two-Factor Authentication Two-Factor Authentication Sep 21, 2020
@REPTILEHAUS
Copy link

REPTILEHAUS commented Sep 24, 2020

@mtrezza happy to be a part of the implementation of it but I will need some assistance or someone who knows the parse internals to brainstorm with. @Etto91 seems to be be on the right track aside from the security gaps. For my own use case I want to integrate Google Authenticator app.

In my mind the way this works is that the user is logs in - they then get a basic session which cannot do any parse actions, other than call functions which may utilise masterKey, aside from that they are essentially blocked from doing anything parse related other than logout/kill session (does this make sense?), it is then up to the developer to create the OTP screen where the users basic session has the capability to hit a cloud function which will validate the OTP server side and grant a full session. within 1 minute or all sessions are killed for them

My own use case utilised custom auth adapters which make use of Parse.User.logInWith - so I need to cover all authentication angles including standard basic parse login/registration.

So as I see it in the framework the bases that need to be covered all start with the following:

Parse.User.logIn
Parse.User.become
Parse.User.signUp
Parse.User.logInWith

After that I am unsure of parse internals other than a basic understanding as well as the PR by @Etto91 which has given me some kind of base, aside from that I am happy to take direction and not reinvent the wheel i.e add 3rd party libraries to assist in the creation of Google Auth OTP etc.

@REPTILEHAUS
Copy link

Or maybe we grant some kind of new session, then with the OTP code submission we pass this new session type along with the validation, if it validates then we pass back a normal session string which the user, on their client side OTP page can use Parse.User.become with it

@dblythy
Copy link
Member

dblythy commented Oct 27, 2020

Hey all,

I've just created a PR (#6977) that continues the work towards inbuilt 2FA (thanks to flovilmart for his work in getting this started).

My implementation requires users to enrol via /enable2FA, which will be worked into the JS SDK via user.enable2FA(). This then returns a QR Code to be put in the Google Authenticator. The user will then need to enter the code back into the app, where user.verify2FA() will need to be called with a code from the authenticator. Now, 2FA will be enabled on their account, and you can check for this via cloud functions for req.user.get('MFAEnabled'). Similarly, you can handle un-enrolled users by !req.user.get('MFAEnabled').

Now, once a user logs in, a custom error (212) will be called. This is where the UI would have to show 'enter code', where the user would go to Google Authenticator, copy the code, and enter in the app. Calling Parse.User.logIn('username','password','token') with the token they've entered will allow the entry, providing it's a vaild code.

In order to force every user to enable the 2FA such as @REPTILEHAUS has suggested, you could use beforeFind, beforeSave triggers and throw a custom error, and bring up a 'verify 2fa to do this' screen.

What are all of your thoughts on this approach?

@mtrezza
Copy link
Member

mtrezza commented Jun 28, 2023

@dblythy @Moumouls can we close this?

@dblythy
Copy link
Member

dblythy commented Jun 28, 2023

Yep!

@mtrezza
Copy link
Member

mtrezza commented Jun 28, 2023

Closing via #8156; an example TOTP adapter has been added with #8457.

@mtrezza mtrezza closed this as completed Jun 28, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type:feature New feature or improvement of existing feature
Projects
None yet
Development

Successfully merging a pull request may close this issue.