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: improved oidc flow on duplicate account registration #3151

Merged
merged 13 commits into from
Mar 14, 2023

Conversation

Benehiko
Copy link
Contributor

@Benehiko Benehiko commented Mar 7, 2023

This PR improves the OIDC registration flow when a duplicate account error happens.

Currently the flow looks as follows:

  1. User registers with password (or other credentials)
  2. User forgot they registered with password and tries to login through an OIDC provider (e.g. Google)
  3. Kratos attempts a registration since the OIDC credentials do not exist
  4. (optional) User needs to add missing traits (e.g. full name) which could not be retrieved from the OIDC provider
  5. User gets a duplicate account error with a "Continue" button.
  6. After submitting the "Continue" button the flow continues again to the OIDC provider, back to Kratos and redirects to UI with duplicate error (Steps 3 to 5)

Instead of causing a confusing redirect loop we should show the user the error with a fresh login flow (since the account exists). This also gives the user the option to do a recovery flow.

  1. User registers with password (or other credentials)
  2. User forgot they registered with password and tries to login through an OIDC provider (e.g. Google)
  3. Kratos attempts a registration since the OIDC credentials do not exist
  4. (optional) User needs to add missing traits
  5. User is returned to a Login flow with the duplication error

Related issue(s)

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-oidc-improvement branch 3 times, most recently from d040a53 to b4b9bb4 Compare March 9, 2023 14:38
selfservice/flow/registration/error.go Outdated Show resolved Hide resolved
selfservice/flow/registration/hook.go Outdated Show resolved Hide resolved
selfservice/strategy/oidc/strategy.go Show resolved Hide resolved
route: registration,
})

cy.get('[data-testid="ui/message/4000007"]').should("be.visible")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

checks if the duplicate error message is present on the form

Comment on lines +251 to +253
cy.get("[name='provider'][value='hydra']").should("be.visible")
cy.get("[name='provider'][value='google']").should("be.visible")
cy.get("[name='provider'][value='github']").should("be.visible")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

ensure we have form values for logging in


cy.get("input[name='identifier']").type(email)
cy.get("input[name='password']").type(password)
cy.submitPasswordForm()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

ensure we can actually log in after getting such an error.

Comment on lines +45 to +47
password:
hooks:
- hook: session
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the spa profile we always have a session after registration. Just added it here to the oidc profile used by the express app.

@Benehiko
Copy link
Contributor Author

Added a new error specific to OIDC linking to an existing account. I think this might help confused users
image

@Benehiko Benehiko marked this pull request as ready for review March 10, 2023 11:13
Copy link
Member

@jonas-jonas jonas-jonas left a comment

Choose a reason for hiding this comment

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

Looking good already. Just a few nits.

selfservice/strategy/oidc/strategy_registration.go Outdated Show resolved Hide resolved
selfservice/strategy/oidc/strategy_registration.go Outdated Show resolved Hide resolved
selfservice/strategy/oidc/strategy_registration.go Outdated Show resolved Hide resolved
selfservice/strategy/oidc/strategy.go Show resolved Hide resolved
@codecov
Copy link

codecov bot commented Mar 13, 2023

Codecov Report

Merging #3151 (c47bbe1) into master (9252f5a) will increase coverage by 0.08%.
The diff coverage is 69.38%.

❗ Current head c47bbe1 differs from pull request most recent head 87d56ae. Consider uploading reports for the commit 87d56ae to get more accurate results

@@            Coverage Diff             @@
##           master    #3151      +/-   ##
==========================================
+ Coverage   77.61%   77.69%   +0.08%     
==========================================
  Files         317      317              
  Lines       20009    20036      +27     
==========================================
+ Hits        15530    15567      +37     
+ Misses       3289     3279      -10     
  Partials     1190     1190              
Impacted Files Coverage Δ
selfservice/strategy/oidc/strategy_registration.go 65.08% <53.57%> (+0.27%) ⬆️
selfservice/strategy/oidc/strategy.go 63.71% <71.42%> (+0.23%) ⬆️
cmd/clidoc/main.go 68.05% <100.00%> (+0.14%) ⬆️
selfservice/flow/login/handler.go 79.26% <100.00%> (+0.38%) ⬆️
selfservice/flow/registration/error.go 70.76% <100.00%> (+0.92%) ⬆️
selfservice/flow/registration/hook.go 82.11% <100.00%> (ø)
text/message_validation.go 100.00% <100.00%> (ø)

... and 2 files with indirect coverage changes

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

Copy link
Member

@jonas-jonas jonas-jonas left a comment

Choose a reason for hiding this comment

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

Thanks for fixing the issues :)

LGTM, apart from the nil return in handleError.

return err
}
x.AcceptToRedirectOrJSON(w, r, s.d.Writer(), lf, lf.AppendTo(s.d.Config().SelfServiceFlowLoginUI(r.Context())).String())
return nil
Copy link
Member

Choose a reason for hiding this comment

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

This seems wrong, right? This should either return the error, or not return at all, IIUC. If we return nil other callers "up" the line might end up with a situation, where err == nil, while there was an error.

Do we have a test covering this code path?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The x.AcceptToRedirectJSON doesn't return anything.

It redirects or writes json back to the browser.

the return nil just prevents the rest of the function from executing. In this case we don't want to propagate the error further since we want to return a login flow with the error message attached to the login flow.

Copy link
Member

Choose a reason for hiding this comment

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

We are always calling this function as part of the return part of functions, which return someValue, error. For example here https://github.com/ory/kratos/pull/3151/files/9b1b977a8bbc43e86687bb631fc46ac4f3d08241.

In that example, if you now return nil from handleError, the returned values of processRegistration will be nil, nil. And that is an issue, because the caller of processRegistration (here for example) will check whether err != nil, which is false (because you returned nil here) and then assume that the *login.Flow is a valid object and processRegistration completed successfully. It will then go on and reference the nil pointer (*login.Flow), which will panic.

I would really like to see a test that executes this code path. Did you already write one?

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 have a test for this though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Please see https://github.com/ory/kratos/pull/3151/files#diff-03d9986942bad676c4567865d479f219a54bb633d7ccc9eed5820703e3d296a3

and an e2e test https://github.com/ory/kratos/pull/3151/files#diff-2695d9d779ed70b5b6a2d9cc5111dcac495b41bfc8353c0e0a50154ff787f7a6

Remember this PR changes the behaviour of the registration flow with oidc when the account already exists.
Instead of just giving back an error code and message, it responds with a new Login flow and the error message inside the login flow.

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 should return ErrAbortFlow here. This prevents further calls in case something changes at some point.

CaptainStandby
CaptainStandby previously approved these changes Mar 14, 2023
Copy link
Contributor

@CaptainStandby CaptainStandby left a comment

Choose a reason for hiding this comment

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

LGTM

aeneasr
aeneasr previously approved these changes Mar 14, 2023
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.

Nicely done

return err
}
x.AcceptToRedirectOrJSON(w, r, s.d.Writer(), lf, lf.AppendTo(s.d.Config().SelfServiceFlowLoginUI(r.Context())).String())
return nil
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 should return ErrAbortFlow here. This prevents further calls in case something changes at some point.

@aeneasr
Copy link
Member

aeneasr commented Mar 14, 2023

Ups, please address the one comment with abort flow

@Benehiko Benehiko dismissed stale reviews from aeneasr and CaptainStandby via 87d56ae March 14, 2023 10:02
@aeneasr aeneasr merged commit 4d2fda4 into master Mar 14, 2023
@aeneasr aeneasr deleted the feat-oidc-improvement branch March 14, 2023 12:35
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.

4 participants