ory / kratos

Next-gen identity server replacing your Auth0, Okta, Firebase with hardened security and PassKeys, SMS, OIDC, Social Sign In, MFA, FIDO, TOTP and OTP, WebAuthn, passwordless and much more. Golang, headless, API-first. Available as a worry-free SaaS with the fairest pricing on the market!
https://www.ory.sh/kratos/?utm_source=github&utm_medium=banner&utm_campaign=kratos
Apache License 2.0
10.99k stars 949 forks source link

Recovery flow redirect after success not working as expected #2371

Open alexandrebrg opened 2 years ago

alexandrebrg commented 2 years ago

Preflight checklist

Describe the bug

On a self-service instance of Kratos, using http redirects, we can add selfservice.flows.<login[registration|recovery...>.after.default_browser_return_url to redirect the user after completing a flow. It works properly for flows like login or registration but doesn't for recovery flow.

As explained in documentation, a successful recovery does a HTTP 303 redirect to a settings flow, however we can't override it.

Reproducing the bug

  1. Update your kratos configuration file with after keyword:
selfservice:
   ...
   flows:
      ....
       recovery:
           enabled: true
           ui_url: ${kratos_ui_recovery}
           after:
               default_browser_return_url: ${kratos_ui_recovery_redirect}
      settings:
         ui_url: ${kratos_ui_settings}
  1. Run a Kratos self-service instance with this configuration
  2. Register yourself & request a password recovery
  3. When clicking mail link to recover password, you'll be redirected to ${kratos_ui_settings} instead of ${kratos_ui_recovery_redirect}

Relevant log output

No response

Relevant configuration

version: v0.8.0-alpha.3

dsn: memory

cookies:
  domain: localhost
  path: /
  same_site: None

serve:
  public:
    base_url: http://localhost:4433
    cors:
      enabled: true
      allowed_origins:
        - http://localhost:3000
        - http://localhost
        - http://127.0.0.1:4455
        - http://localhost:4455
      allowed_methods:
        - POST
        - GET
        - PUT
        - PATCH
        - DELETE
      allowed_headers:
        - Content-Type
        - Authorization
        - Cookie
      exposed_headers:
        - Content-Type
        - Set-Cookie
      #options_passthrough: true
      debug: true
  admin:
    base_url: http://kratos:4434/

selfservice:
  default_browser_return_url: http://localhost:3000/u/dashboard
  whitelisted_return_urls:
    - http://localhost:3000
    - http://localhost:4455

  methods:
    password:
      enabled: true
    oidc:
      enabled: true
      config:
        providers:
          - id: gitlab 
            provider: gitlab
            client_id: redacted
            client_secret: redacted
            mapper_url: file:///etc/config/kratos/oidc.gitlab.jsonnet
            scope:
              - api
              - openid
              - email

  flows:
    error:
      ui_url: http://localhost:4455/error

    settings:
      ui_url: http://localhost:3000/settingss
      privileged_session_max_age: 15m

    recovery:
      enabled: true
      ui_url: http://localhost:3000/recovery
      after:
        default_browser_return_url: http://localhost:3000/u/mysupertest

    verification:
      enabled: true
      ui_url: http://localhost:3000/u/verify 
      after:
        default_browser_return_url: http://localhost:4455/

    logout:
      after:
        default_browser_return_url: http://localhost:3000/u/signin

    login:
      ui_url: http://localhost:3000/u/signin
      lifespan: 10m

    registration:
      lifespan: 10m
      ui_url: http://localhost:3000/u/signup
      after:
        oidc:
          hooks:
            - hook: session

log:
  level: debug
  format: text
  leak_sensitive_values: true

secrets:
  cookie:
    - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE #TODO
  default:
    - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE
hashers:
  argon2:
    parallelism: 1
    memory: 128MB
    iterations: 2
    salt_length: 16
    key_length: 16

identity:
  schemas:
    - id: default_identity
      url: file:///etc/config/kratos/identity.schema.json
  default_schema_url: file:///etc/config/kratos/identity.schema.json

courier:
  smtp:
    connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true

Version

v0.8.0-alpha.3

On which operating system are you observing this issue?

Linux

In which environment are you deploying?

Kubernetes with Helm

Additional Context

Kratos version is v0.8.0-alpha.3, but after discussing with David Alexandre on Slack, it is the same on latest version.

aeneasr commented 2 years ago

Thank you for the report, where would you like to redirect the user to instead? We redirect them to the settings page as users probably want to update their credentials when recovering the account. If I’m not mistaken, the post-recovery redirect triggers once one updates their settings successfully

alexandrebrg commented 2 years ago

In our current application, the settings flow is used in two pages (due to UX concerns):

That's why we expect the user to be redirected to a different page than the settings flow page. We do not expect to be on settings page after clicking on recover link. Moreover, I tried to edit my settings after having completed the recovery flow, and it did not redirect to recovery.after.default_browser_return_url as you stated

aeneasr commented 2 years ago

I see, it’s possible that the final redirect doesn’t work yet.

Regarding the update, it should be possible (e.g. by checking the messages) to identify that a settings flow is initiated by recovery, you can then filter out all the irrelevant fields in the UI!

alexandrebrg commented 2 years ago

You are right, the settings flow has a message when created by recovery flow.

About the issue, I understood that after keyword should be used as a redirect after completing the settings flow generated by the recovery. Our application is an SPA, so we expected the user to be redirected while clicking on recovery link.

After thinking about it, I can't confirm that your statement about final redirection is false, as we don't redirect the user after settings flow submission (XHR calls). This final redirection isn't documented, so I guess some details about recovery flow can be added (I can spend time to it if needed). Also, the final redirection needs to be tested to ensure it works, and that this issue can be closed as it's not a bug.

aeneasr commented 2 years ago

We can keep it open until your issue has been resolved :) For SPAs, we usually indicate redirects with a specific error type that indicates that a redirect is required. However, for success flows we just return 200 OK as it’s up to the app to decide what to do. Most SPAs for example choose to show some type of flash message using the tooling of their choice - so yes, it’s completely possible that we do not indicate the redirect properly. I’m wondering however how we could do that? If you have any ideas please let us know :)

chromakode commented 11 months ago

Just bumped into this as well on Kratos 1.0.0. I'm using the recovery flow to generate invite links, and would like to control the destination URL the user is redirected to after they click the invite link.

I tried setting recovery.after.default_browser_return_url and adding a return_to query parameter to the recovery URL, but in both cases I was redirected to the settings page. Is there any way to control the redirect destination?

Xenograph commented 9 months ago

Bumping this as I'm in the same situation as @chromakode

Tan-Aki commented 2 months ago

Same here !

It would be nice to have both an intermediary redirect (after the user clicks the invite link, or successfully submits the recovery code that they received by email) and a final redirect that works (after the user successfully changes their password. At the moment I believe the final redirect does not work either as per my tests and I'm not sure which default_browser_return_url it will default to).

Thank you and keep up the amazing work please !

[Edit] I was able to make the final redirect work by setting the selfservice.flows.recovery.after.default_browser_return_url or by specifying a returnTo thanks to the sdk

  ory
      .createBrowserRecoveryFlow({
        returnTo: String(returnTo || ""),
      })

Couldn't find a way for the intermediary redirect though.