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
Passwordless login, password reset via email & 2FA support #2923
Conversation
Here are my first thoughts:
|
I don't think we discussed this already, but I may also have missed it. What would be the use-case for such a mode that only allows login without password?
Good idea, I will change that!
To be honest I haven't seen this format anywhere so far, I personally prefer the space. Is there a specific reason why you prefer the dash? |
I was thinking of larger teams where a lot of users would go for weak passwords. I've seen this in client projects a lot. You can set rules, but then people still stick to their birthday and their last name 😂 It's a bit of a risky mode as it has to be clear that email transport is working properly, but then it's also a lot safer in such situations.
It was just an initial thought. I got a couple codes via email afterwards and it totally works fine with the space. Just forget about it. There's not really a need to change it. |
Good point! But instead of preventing login with password, wouldn't it be better to introduce an option (maybe in the user blueprint) that prevents users from setting a password? Then the admins could set a password and login with it, while the editors would only be able to login via email. |
Oh, that's even better! |
But we can do that in a different PR and maybe even later. Let's keep the modes like they are. |
I thought about it again: Maybe we should offer both options. Some devs may want to disable passwords completely, some may want to disable them for specific roles. We could have the modes The option to disable setting passwords per role could indeed come later. |
I haven't thought this through, but could something like this work instead? The modes seem to get a bit complicated to understand. return [
'panel' => [
'login' => [
'via' => ['email', 'password'],
'2fa' => true
],
'reset-password' => true
]
]; |
Obviously, return [
'panel' => [
'login' => [
'via' => ['email', 'password', 'github'],
'2fa' => true
],
'reset-password' => true
]
]; |
To be honest I think this syntax is too complex. It's easy to understand from the user's perspective, but we would need to test every combination of option values. Some combinations are impossible, for example both 2FA and password reset (you could circumvent the 2FA via password reset). The fixed mode strings ensure that only those combinations are possible that are secure and supported. We will list and explain the modes in the docs, so it should be pretty simple to pick one. Regarding completely different login methods like GitHub: It's really difficult to integrate these into the core, even when they are defined by plugins. For example GitHub login would be just a button that takes you to GitHub, but that button would need to be defined by a Vue component. We already allow replacing the login form, which would need to be done for that. I'm going to think about this again. Maybe one of us comes up with a good solution for the way forward. |
I think you are right. Maybe we need to work on the mode names instead. It's really just about understanding it without the docs. Here's a first try. What trips me up the most is the use of email and password that feels like I will disable or enable those fields with the setting. Maybe something like email-code vs. password could make clearer that this is the method of authentication and not the field, which will be enabled/disabled. Not 100% sure yet. email-code |
You are right, it isn't very semantic and easy to understand. To be honest, I think your variant isn't much better. However I think a variant of your original suggestion with the array could work. I will try implementing something around that. |
What if we actually allow to configure the forms: <?php
return [
'panel.login' => [
'forms' => ['email+password', 'email', 'email-pwReset'],
'defaultForm' => 'email+password'
'2fa' => true/false
]
]; If both If This setup is a bit more complex, but a lot more flexible and extensible. What do you think? |
@lukasbestle to be honest, I don't think it's very intuitive either. Maybe we could use some outside help here. @afbora @distantnative what do you think? Any ideas? I'm a bit lost to be honest. It all feels like it's almost there, but not quite. |
One thing to keep in mind: The mode option should be neutral about the code challenge that is used (e.g. email, TOTP, SMS...). The challenge will be determined using a different mechanism. So this is only about whether login with any kind of code is enabled at all and about the UI that is displayed. |
I do not have very good knowledge but maybe I can give examples of social provider logins as an idea. It may seem confusing, but it will be understandable if we show all the modes/providers in the documents as lists or tables. return [
'panel' => [
'login' => [
// Default login mode
'mode' => 'email-password',
// Available login modes/providers
'providers' => [
'email-password' => [
'name' => 'Email-Password',
'2fa' => true,
'reset-password' => true,
],
'single-auth-code' => [
'name' => 'Single Auth Code',
'2fa' => false,
'reset-password' => false,
],
'github' => [
'name' => 'GitHub',
'2fa' => false,
'reset-password' => false,
'options' => [
'api_key' => 'XXX',
'secret_key' => 'XXX',
]
],
],
]
]
]; |
You are right about plugin configuration! However I think it's very verbose for our core modes. 😕 |
A variation of @afbora's suggestion: <?php
return [
'panel.login' => [
'methods' => [
// core method with options
'password' => [
'2fa' => true,
// the first method in the list that has this set to `true` will be the default;
// maybe we even go without this (see the open question below)
'default' => true
],
// core methods that are just enabled without options
// (automatic translation of array values to array keys when the key is numeric)
'password-reset',
'code',
// also supported syntax variants:
// 'password-reset' => true
// 'password-reset' => []
// third-party methods (not yet supported, for future expansion)
'github' => [
'apiKey' => 'xxx',
'secretKey' => 'yyy'
]
]
]
]; A very simple config could look like this: <?php
return [
'panel.login' => [
'methods' => ['password', 'password-reset']
]
]; My thoughts behind this:
Open questions:
|
I feel like we are getting somewhere here! I like the methods configuration and especially the option to keep it super short. I wonder though if we should take password-reset out of it. It feels weird to include that as a login method. I know that it's technically working in the same way, but from a user perspective it's a different type of feature. |
I think it makes sense if you think of the login methods as "ways to get into the Panel":
In that context I think it could be more confusing to split the configuration up and define the password reset separately – you wouldn't be able to see at one glance which variants are active. Splitting up the config would also make the implementation more complex. So my vote is to keep it like this. What do you think about the two open questions above? |
Ha, I think that sentence really just made it click for me: think of the login methods as "ways to get into the Panel" I'm totally fine with this as long as we communicate it that way in the docs later. Open questions:
|
@bastianallgeier What do you mean with nice error handling in the login screen? |
@lukasbestle In case you misconfigure the combination of login methods, we could show that in the login screen in debug mode. As a default behaviour we would probably just remove the login methods that should not be allowed in combination, right? But in debug mode we could explain it like "you cannot combine this and that for security reasons" |
Ah, I see. Yes, by default we would remove the conflicting options. We could throw an Exception if in debug mode and then display that Exception in the frontend. |
baa7fd7
to
e555c60
Compare
The discussed changes + further improvements are now implemented:
You can find the full list of changes compared to the original version of this PR in this diff. I have also decided to already implement the 2FA feature as the foundation of the code challenge feature is already pretty much reviewed and final. You can find the implementation in a separate, fifth commit for easier review. The first post of this PR is updated with the latest information. |
Sorry, I probably merged the other PR too soon. Could you fix the conflict or should I do it? Otherwise, I'm super happy with the changes! <3 |
Will do so tonight. :) |
e555c60
to
803454e
Compare
@bastianallgeier Should be ready for merging now. :) |
Describe the PR
This PR adds the logic and UI for creating and verifying login codes to log in to the Panel, which results in the following features:
Login methods
There are now three ways to get into the Panel that can be configured with the
panel.login.methods
option:code
method)Kind of like Notion: The user enters their email address, gets a code and logs in.
password-reset
method)The user enters the email address, gets a code and logs in with it. After logging in, the user will be redirected to the password reset form. After confirming the new password, the user is redirected to the Panel dashboard.
The third and default method is
password
, which will use the same UI and features as previously (i.e. the passwordless login form is disabled). This is because not everyone needs/wants password reset and also because the email configuration needs to be set up and working.For the
password
method,2fa
can be enabled like this:It is also possible to combine the methods:
The first method in the list will be the default.
Other syntax variants that are supported:
Other configuration options
The login codes expire after 10 minutes by default. This can be configured with the
panel.login.timeout
option.The email that gets sent can be fully customized with
panel.login.email.from
,panel.login.email.fromName
,panel.login.email.subject
and email body templates insite/templates/emails/auth/login[.html].php
andsite/templates/emails/auth/password-reset[.html].php
.Usage from site templates & plugins
With the
$kirby->auth()->createChallenge()
,verifyChallenge()
andlogin2fa()
methods, users can use the feature themselves for frontend login forms etc.Implementation
The implementation was designed according to OWASP recommendations, except that:
Regarding the UI: I have copied over the user info styles @bastianallgeier has written for the password reset form to the login code form. Is it intended that the styles are now duplicated? If not, please move them over to where they belong.
Following PR
This PR already prepares plugin extensions for auth challenges (SMS, TOTP, hardware tokens...) that will be implemented in a second PR.
Related issues
Ready?
composer fix