Okta API problem: Redirect to custom URL after password recovery reset

Hello,

I have been running into a problem while trying to use the POST /api/v1/authn/recovery/password Okta API endpoint detailed on this documentation page:

https://developer.okta.com/docs/reference/api/authn/#recovery-operations

This endpoint is used to trigger an e-mail for performing a password reset flow.

The way I currently have it implemented:

  1. the user submits a password reset request through my application

  2. my application hits the password recovery Okta API endpoint above using a body object that looks like this:
    { factorType: 'EMAIL', username: 'some.user@some.domain.com', relayState: 'http://localhost:4700' }

  3. When I test the endpoint with this object using a debugging tool such as Postman, I can see the Okta API’s response object confirms the relayState with a response that looks like this:

{ status: 'RECOVERY_CHALLENGE', factorResult: 'WAITING', relayState: 'http://localhost:4700', factorType: 'EMAIL', recoveryType: 'PASSWORD' }

  1. I have previously checked the Okta admin portal, under Security > API > Trusted Origins, to ensure that the http://localhost:4700 origin being used for the relayState parameter is marked for both Redirect and CORS.

  2. The user receives the forgot password reset e-mail in their inbox

  3. User clicks the link in the e-mail, bringing them to Okta. They fill out a security question and their new password in Okta

  4. Once the new password is correctly set, instead of Okta redirecting the user to the desired URL specified by the relayState parameter, the user is instead redirected to <Okta server URL goes here>/user/notifications.

My question is: How can I make the relayState parameter work so that the user is directed to the relayState URL, instead of the /user/notifications URL? Has anyone had any success in using the relayState parameter?

Thank you!

I don’t have the answer off right off, but I can tell you how to look for it (I’d have to set up a test case myself). If you’re letting the Okta hosted widget handle the password reset then it may be the widget is losing the relay state in the middle of the transaction flow. Debugging may also depend on which version of the widget your org is configured to use.

This is too hard to do in Postman because of the page loads and redirects. What you need to do is turn on the network capture in a browser (make sure it “preserves” entries across page loads) and look closely at the requests and responses there. See if the widget is dropping the relay state on one of its calls.

The source to the widget is here: GitHub - okta/okta-signin-widget: Okta SignIn widget that renders the new login/auth/recovery flows.

Thank you very much for the response, @jmussman .

I will try rooting around in the code to see if I can find any smoking guns, as well as look at the network traffic in a browser again. I think I did have the “preserve log” setting enabled. From what I recall, it just goes to the <okta root>/user/notifications URL after the password reset submission is completed. I will have another, more critical look at it.

I can see that there’s at least a reference to the relayState in the forgotPassword function in okta-signin-widget/ForgotPasswordController.js at master · okta/okta-signin-widget · GitHub, though I wouldn’t know if the variable is set with a meaningful value, or what happens downstream. I don’t suppose you’d know where the authClient.forgotPassword function is defined by any chance, would you?

I would. The widget depends on the okta-auth-js SDK at GitHub - okta/okta-auth-js: The official js wrapper around Okta's auth API. The function calls the api endpoint /authn/recovery/password which starts a password recovery. Line 693 in lib/OktaAuth.js.

That’s where you were starting out above, right?

If you are able to capture part of the network traffic int he browser I’ll look at it. I can’t find anything that references the /user/notifications URL mentioned above. I’ve never seen it before. I’m not sure how you are landing there. However, it’s real because it isn’t kicked back as a 404 in my orgs, so it may be an undocumented private API endpoint.

On another thought have you checked the logs for any messages? And what does the user look like in the API after this is finished? Not locked or anything, are they?

I ran this scenario with the same results as you in both class and OIE tenants. I found your original question in the community from a few days and there is a response with a couple of links there. I’m going to test more after I review those.

Thank you again for the responses, and all of your testing efforts. It is somewhat reassuring to hear that the behavior I’m seeing is also seen by you.

I went back and reproduced the behavior in my local environment again. When the new password is submitted through the Okta widget, I can see an HTTP POST is performed against the /api/v1/authn/credentials/reset_password endpoint in my browser network traffic. The payload for this POST contains a newPassword attribute which has my new password value in it, and a stateToken attribute, which looks like some sort of hash string. I don’t know offhand what information would be stored in that token without combing through the code a lot more.

After that POST is made (which got an HTTP 200 response), the next thing that immediately happens in the network traffic is an HTTP GET made against the login/sessionCookieRedirect endpoint, with a redirectUrl parameter set to the /user/notifications URL, which is the undesired URL that I end up at.

It seems like at this step, it ought to inject the relayState value from the initial API request in for the redirectUrl value. Possibly a bug?

I’m not a wizard with the Okta source (I only just became involved in it within the last few days), but perhaps the bug lies somewhere in this region of code (seems like it should check for a relayState param):

/okta-signin-widget/src/v1/util/RouterUtil.js

setCookieAndRedirect: function(redirectUri) {
          if (redirectUri) {
            Util.debugMessage(`
              Passing a "redirectUri" to "setCookieAndRedirect" is strongly discouraged.
              It is recommended to set a "redirectUri" option in the config object passed to the widget constructor.
            `);
          }

          redirectUri = redirectUri || router.settings.get('redirectUri');
          if (!redirectUri) {
            throw new Errors.ConfigError('"redirectUri" is required');
          }

          redirectFn(
            sessionCookieRedirectTpl({
              baseUrl: router.settings.get('baseUrl'),
              token: encodeURIComponent(res.sessionToken),
              redirectUrl: encodeURIComponent(redirectUri),
            })
          );
        },

I think I misled you earlier when I mentioned the ForgotPasswordController before, my apologies. I think the widget I am interacting with is actually the PasswordResetController.

I saw messages in the Okta server log indicating that the password had been successfully reset, but no mention of any redirect behaviors. The admin directory shows the user’s status is Active, not Locked, after the password reset is performed.

My best guess is it’s an issue with that snippet of code I pasted above?

Yes, I did mistakenly post on the community forums before coming here, and I did review the solutions offered there. The feasible ones seemed to either leverage fully custom password reset flows which I would prefer to avoid, or it required making changes to the password reset e-mail template which I would also prefer to avoid due to causing changes that would impact other users of the same Okta server.

I should add that the issue might not necessarily lie within the snippet of code I mentioned, but could also be the parameters that that function is called with, by some other piece of code.