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

feat: login & registration via code #3378

Merged
merged 24 commits into from
Aug 29, 2023
Merged

feat: login & registration via code #3378

merged 24 commits into from
Aug 29, 2023

Conversation

Benehiko
Copy link
Contributor

@Benehiko Benehiko commented Jul 12, 2023

Implements Login and Registration via the code strategy.

Related issue(s)

This feature adds passwordless email code login. When a user signs up, or signs in, a code is sent to their email address which they can use to complete the authentication process.

This feature is currently only working for browser facing APIs.

Closes #2029
Closes https://github.com/ory-corp/cloud/issues/3573

Checklist

  • I have read the contributing guidelines.
  • I have referenced an issue containing the design document if my change
    introduces a new feature.
  • I am following the
    contributing code guidelines.
  • I have read the security policy.
  • I confirm that this pull request does not address a security
    vulnerability. If this pull request addresses a security vulnerability, I
    confirm that I got the approval (please contact
    security@ory.sh) from the maintainers to push
    the changes.
  • I have added tests that prove my fix is effective or that my feature
    works.
  • I have added or changed the documentation.

Further Comments

@Benehiko Benehiko force-pushed the feat-login-code branch 2 times, most recently from 33b1c05 to 62ce450 Compare July 13, 2023 08:31
@Benehiko Benehiko force-pushed the feat-login-code branch 3 times, most recently from 41cfae9 to ffb2035 Compare July 17, 2023 15:20
@Benehiko Benehiko force-pushed the feat-login-code branch 2 times, most recently from 5c5d4c3 to 0666a33 Compare July 25, 2023 08:09
@Benehiko Benehiko force-pushed the feat-login-code branch 3 times, most recently from 42a1173 to bd2bbeb Compare August 3, 2023 15:19
.schema/openapi/patches/selfservice.yaml Outdated Show resolved Hide resolved
driver/config/config.go Outdated Show resolved Hide resolved
driver/registry_default.go Show resolved Hide resolved
driver/registry_default.go Show resolved Hide resolved
driver/registry_default_login.go Outdated Show resolved Hide resolved
selfservice/strategy/code/strategy.go Show resolved Hide resolved
selfservice/strategy/code/strategy.go Show resolved Hide resolved
selfservice/strategy/code/strategy.go Outdated Show resolved Hide resolved
selfservice/strategy/code/strategy.go Outdated Show resolved Hide resolved
@aeneasr aeneasr marked this pull request as ready for review August 4, 2023 09:51
Copy link
Member

@aeneasr aeneasr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice! I still need to review the login tests, the registration code, and the registration tests, as well as e2e and trying out the flow myself. In summary, this looks great! There are a few things we need to address:

  1. Should we enable / disable login / regstration for the code method? If yes, then why not also for verification / recovery?
  2. The introduced state parameters are not "backwards" looking - they only apply to the new flows which I find a bit strange?
  3. The SQL migrations should be kept in as few files as possible and avoid DEFAULTs on existing large tables
  4. The state machine we use in login/registration sets values in the callbacks of outer functions. If a callback is not called, these values might be nil, leading to panics

driver/registry_default.go Show resolved Hide resolved
driver/config/config.go Outdated Show resolved Hide resolved
driver/registry_default_login.go Outdated Show resolved Hide resolved
driver/registry_default_registration.go Outdated Show resolved Hide resolved
@@ -38,6 +38,19 @@
"type": "boolean"
}
}
},
"code": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we should use a different credential name here. oob_otp (out of band one time password) is probably the "correct" terminology, but noone understands what this means.

What are your thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

otp?. code is also okay since the method you need to enable is code.

selfservice/strategy/code/strategy_login.go Show resolved Hide resolved
selfservice/strategy/code/strategy.go Show resolved Hide resolved
selfservice/strategy/code/strategy_login.go Outdated Show resolved Hide resolved
selfservice/strategy/code/strategy_login.go Show resolved Hide resolved
Copy link
Member

@aeneasr aeneasr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice ! I looked at everything now - see comments

Comment on lines 167 to 168
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Accept", "application/json")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take a look at the password tests - we are testing various flows there (browser html, browser ajax, native api) and I think we need to do this here and in registration too. There are also some testhelpers you can use to easily submit these flows!


// Step 2: Check if the flow traits match the identity traits
for _, n := range container.NewFromJSON("", node.DefaultGroup, p.Traits, "traits").Nodes {
if !strings.EqualFold(f.GetUI().GetNodes().Find(n.ID()).Attributes.GetValue().(string), n.Attributes.GetValue().(string)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure these are all strings allways?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually don't know tbh. I also don't know what the best way would be to check if the traits are the same between the submitted UI nodes and the DB UI nodes :/

Maybe @zepatrik knows more about UI node checking ;)

app: "express" as "express",
profile: "code",
},
// {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably enable this :)

app: "express" as "express",
profile: "code",
},
// {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enable

app: "express" as "express",
profile: "code",
},
// {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enable

}
console.log({ mailItems: response.body.mailItems })
console.log({ mailItem })
console.log({ email })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove logs?

test/e2e/run.sh Outdated
@@ -136,7 +136,7 @@ prepare() {
nc -zv localhost 4445 && exit 1
nc -zv localhost 4446 && exit 1
nc -zv localhost 4455 && exit 1
nc -zv localhost 4456 && exit 1
# nc -zv localhost 4456 && exit 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re-enable?

test/e2e/run.sh Outdated
>"${base}/test/e2e/ui-node.e2e.log" 2>&1 &
)
fi
# if [ -z ${NODE_UI_PATH+x} ]; then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re-enable?

@Benehiko Benehiko force-pushed the feat-login-code branch 3 times, most recently from 92c2292 to 951485b Compare August 16, 2023 08:18
@Benehiko Benehiko requested a review from hperl as a code owner August 16, 2023 08:18
@codecov
Copy link

codecov bot commented Aug 16, 2023

Codecov Report

Merging #3378 (267b07d) into master (37f1657) will increase coverage by 0.10%.
Report is 1 commits behind head on master.
The diff coverage is 81.29%.

❗ Current head 267b07d differs from pull request most recent head 99922c1. Consider uploading reports for the commit 99922c1 to get more accurate results

@@            Coverage Diff             @@
##           master    #3378      +/-   ##
==========================================
+ Coverage   78.14%   78.24%   +0.10%     
==========================================
  Files         327      339      +12     
  Lines       21742    22495     +753     
==========================================
+ Hits        16991    17602     +611     
- Misses       3492     3582      +90     
- Partials     1259     1311      +52     
Files Changed Coverage Δ
persistence/sql/persister_login.go 100.00% <ø> (ø)
persistence/sql/persister_recovery.go 85.29% <ø> (+0.29%) ⬆️
persistence/sql/persister_verification.go 83.82% <ø> (+0.69%) ⬆️
schema/extension.go 73.33% <ø> (ø)
selfservice/flow/flow.go 100.00% <ø> (ø)
selfservice/flow/login/hook.go 87.74% <ø> (ø)
selfservice/flow/recovery/strategy.go 100.00% <ø> (ø)
selfservice/flow/verification/error.go 81.66% <ø> (ø)
selfservice/flow/verification/strategy.go 75.00% <ø> (ø)
selfservice/strategy/lookup/settings.go 62.50% <0.00%> (ø)
... and 71 more

... and 2 files with indirect coverage changes

@Benehiko Benehiko force-pushed the feat-login-code branch 2 times, most recently from 726b76a to e117fef Compare August 21, 2023 15:13
@Benehiko
Copy link
Contributor Author

Some things to consider before merging:

  1. The SPA experience isn't that great with this PR since the SDK for one returns the successfulNativeLogin object on login submit.
    https://github.com/ory/kratos/blob/master/selfservice/flow/login/handler.go#L687

See this example:
https://github.com/ory/kratos-selfservice-ui-react-nextjs/pull/59/files#diff-efe206089575e5aa92a4bacded0c0c25b4ef6b11199e0d8620eecd1d65b66f50R79-R81

One alternative would be to return the continue_with property like we do in the registration flow when a verification flow is needed.

if (data.continue_with) {
...
}
  1. The naming and configuration. Right now all of the UI nodes are under the code group which I think is fine since this is the same group we use in Recovery and Verification when they use the code method.
    The configuration keys need a bit of adjustment. As mentioned by @aeneasr we could use something like this:
code:
  enabled: true # enables verification, recovery
  passwordless_enabled: true # additionally enables passwordless flows
  # aal2_enabled: true

Which I'm in favor of.

  1. A lot of the scaffolding for the settings flow is already in this PR and can be done in a follow-up PR. This reduces the size of the PR and makes it easier to review.

  2. We need to merge feat: registration & login with code elements#117 and feat: login and registration via code kratos-selfservice-ui-react-nextjs#59

  3. To get the tests passing on kratos-selfserivce-ui-react-nextjs and kratos-selfservice-ui-node we would need to do an SDK pre-release off of this branch.

@jonas-jonas
Copy link
Member

  1. Where do you need to "redirect"/continue to?
  2. That sounds good to me. In recovery/verification we have the use key that indicates whether to use link/code, maybe something like that is an option here, too?
  3. SGTM

To get the tests passing on kratos-selfserivce-ui-react-nextjs and kratos-selfservice-ui-node we would need to do an SDK pre-release off of this branch.

Make sure to actually do a pre-release, if you just use a random id it is uploaded as the latest package. (I had that happen a while back.)

Copy link
Contributor

@hperl hperl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks very good! I just had some minor questions and one security concern regarding the code that checks the code.

@@ -19,6 +19,8 @@ type (
CourierTemplatesVerificationValid() *config.CourierEmailTemplate
CourierTemplatesRecoveryInvalid() *config.CourierEmailTemplate
CourierTemplatesRecoveryValid() *config.CourierEmailTemplate
CourierTemplatesLoginValid() *config.CourierEmailTemplate
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this interface being used or implemented?

@@ -739,6 +748,7 @@ func (p *Config) SelfServiceStrategy(ctx context.Context, strategy string) *Self
// we need to forcibly set these values here:
if !pp.Exists(enabledKey) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing fallthrough?

secrets:
for _, secret := range p.r.Config().SecretsSession(ctx) {
suppliedCode := []byte(p.hmacValueWithSecret(ctx, codeVal, secret))
for i := range loginCodes {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This for loop looks a bit concerning. If an attacker can create multiple login codes in quick succession, the probability that one of the codes matches a random guess rises.

From a security perspective we should only ever check against one login code, IMHO the latest.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, i added this since you could have multiple identifiers listed under the code credentials type.

The user submits one of those identifiers, an email is sent out to all of their identifiers linked to the code credential. Each email contains a unique code. The second submit the user can submit any of the codes sent out.

See this identity schema https://github.com/ory/kratos/blob/f1a0fae19e32014a9151f320ed2ad6a903f129ea/test/e2e/profiles/code/identity.complex.traits.schema.json

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're both right. In the default case, where an identity has only one or two "login emails", I think that Alano is correct. As far as I can tell, multiple codes (related to one flow id) would only ever be sent out if the identity schema has more than one code credentials identifier.

However, if an identity schema allows an array of identifiers (so 1..n emails used for login), this could theoretically be abused.

I guess this can be fixed by sending EVERY address the same code. At the moment we send every address a different code, which makes brute forcing more likely because we reduce the search space.

Does that make sense?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm true, i didn't think of that 🤔

Yeah i agree, sending each the same code will prevent this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to revert this logic, every address needs a unique code, otherwise we don't know which address got the code - and thus can't verify the correct email.

}

secrets:
for _, secret := range p.r.Config().SecretsSession(ctx) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same concern about checking multiple codes applies here.

return registrationCode, nil
}

func (p *Persister) UseRegistrationCode(ctx context.Context, flowID uuid.UUID, rawCode string, addresses ...string) (*code.RegistrationCode, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is very similar to UseLoginCode. Can we extract this out into a generic function?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did this already on review branch

Benehiko and others added 21 commits August 29, 2023 09:59
style: format

refactor: code cleanup and flow refinement

feat: store code credential address type

fix: login resend button and field errors

fix: invalid code handling and error messages

test(e2e): registration with code

test: login and registration code

fix: login and registration tests

style: format

test: registration with code error cases

test: login with code error messages

test: login with code

test: login and registration code

test: login errors

fix: unit tests and verification flow

fix: ui rendering on code group instead of default

fix: sdk generation

fix: sdk generation and tests

chore: improve registration with code test

chore: code review

chore: code review

chore: cleanup based on review comments
@aeneasr aeneasr merged commit eaaf375 into master Aug 29, 2023
26 checks passed
@aeneasr aeneasr deleted the feat-login-code branch August 29, 2023 13:43
@aeneasr aeneasr mentioned this pull request Sep 1, 2023
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Passwordless email flows
4 participants