Redirect to url after signup (from within rule?)

Hi,

I followed the (excellent) guide described here and everything works fine.

I tried to implement a rule for basic redirection to a specific url (e.g. “/:locale/onboarding”) after signup, like this:

function (user, context, callback) {
  var loginCount = context.stats && context.stats.loginsCount ? context.stats.loginsCount : 0;
  var url = "http://localhost:3000/en/onboarding";
  
  if (loginCount <= 1) {
    context.redirect = { url };
  }
  
  return callback(null, user, context);
}

However, the then gets stuck in a loop, with no error shown in the console and this url:

http://localhost:3000/en/onboarding?state=hKFo2SBtLXEyWGdUdkY2S0pGRTAzNy03ajlaeVM2bmRYaEVReKFuqHJlZGlyZWN0o3RpZNkgaGdXaElDSzJrY0M0RGsybGVHRDVvQlAzRG5mTG9wTlmjY2lk2SBsOGFiZGxVandSZ29lOGtVenRWUjNtd2ptd1VHbGNOUQ

Could you please tell me what went wrong?

Looking forward to hearing back from you…

Yassine

Hi @YassineDM,

Are you redirecting the user to /continue to complete authentication?

https://YOUR_DOMAIN/continue?state=THE_ORIGINAL_STATE

You can find more info on redirecting from within rules here:

Do I put this redirection logic here?

	const onRedirectCallback = (appState) => {
		history.push(appState?.returnTo || window.location.pathname)
	}

It would be after the user completes their onboarding. When they submit, the user should be redirected to /continue so that they can be fully authenticated.

I would like to redirect users AFTER authentication. Do I actually need to do that within a rule, or within the Auth0ProviderWithHistory component mentioned here:

Oh, I see! Yes, the application would handle the redirect in that case instead of a rule.

You could alter your rule to instead add a flag in the ID token that will let your app know that the user requires onboarding:

function (user, context, callback) {
  var loginCount = context.stats && context.stats.loginsCount ? context.stats.loginsCount : 0;
  var namespace = "http://your-app-uri"; // a namespace is required for custom claims to avoid collision
  
  if (loginCount <= 1) {
    context.idToken[namespace + 'requiresOnboarding'] = true;
  }
  
  return callback(null, user, context);
}

Here is info about setting up a single callback URL in your app that handles redirects:

Ok. Then where do I get access to the context object? As far as I know, the onRedirectCallback only exposes the appState…

The context object in the code snippet above is within a rule (Context Object Properties in Rules). The rule determines whether the user needs to do onboarding and adds a custom claim to the ID Token. Your app can read this custom claim in the user object to see if they need to be redirected.

You could instead store this in appState if you have a dedicated sign up button, but if a user were to click login, and then switch to “sign up” from within the Universal Login, then your app will have no way to know that the user is new.

Ok. So the rule I create adds a new attribute to the user object, say “http://my-app-uri/requiresOnboarding”. Now where should I put the redirection logic, e.g. history.push("/:locale/onboarding")?

Great! Your app can read the custom claim like so:

const { user } = useAuth0();
const requiresOnbiarding = user['http://my-app-uri/requiresOnboarding'];

I’m not sure of the best way to handle the redirect in React. I will have to do a little more research on that and let you know!

Hi @YassineDM,

Here are the options have found for redirecting after signup (summarizing ones we’ve already discussed):

  1. Progressive profiling using rules - This is the recommended approach, but it does require your app to redirect the user back to your Auth0 tenant to complete the authentication flow. The user would log in, fill out their onboarding, and then be redirected to the https://YOU_AUTH00_DOMAIN/continue endpoint along with the state query param.

Example rule:

function (user, context, callback) {
  var loginCount = context.stats && context.stats.loginsCount ? context.stats.loginsCount : 0;
  var url = "http://localhost:3000/en/onboarding";
  
  if (context.protocol !== "redirect-callback" && loginCount <= 1) {
    context.redirect = { url };
  }
  
  return callback(null, user, context);
}
  1. Redirecting using appState - This would be the easiest to implement, but it would mean that you would need to prevent users from logging in from the Universal Login. If they click “Sign up”, then they would need to be a new user signing up, and they could not select the “Already have an account? Login” option. This can be configured, but it is probably not ideal.

Example sign up button:

      <button
        className="btn btn-primary btn-block"
        onClick={() => loginWithRedirect({
            screen_hint: signup,
            appState: {redirectTo: '/onboarding'} })}
      >
        Sign up
      </button>
  1. Custom component wrapper - You can use a rule to inform your app if it is the first time the user has logged in, and then write a component wrapper to use on your protected routes. I tested this out using the example app from the blog you mentioned:

New src/auth/with-onboarding-redirect.js file:

// src/auth/with-onboarding-redirect.js

import React from 'react';
import { useState, useEffect } from "react";
import { useHistory } from 'react-router-dom';
import { useAuth0 } from '@auth0/auth0-react';

const statusClaim = 'https://demo.com/isOnboarded';

export function WithOnboardingRedirect(props) {
  const history = useHistory();
  const { getIdTokenClaims } = useAuth0();
  const [isOnboarded, setOnboarded] = useState(false);
  useEffect(() => {
    async function getOnboardingStatus () {
      const claims = await getIdTokenClaims();
      return claims && claims[statusClaim];
    }
    async function checkOnboardingStatus () {
      const status = await getOnboardingStatus();
      const isOnboarded = status;
      if (!isOnboarded) {
        history.push('/onboarding');
      } else {
        return setOnboarded(true);
      }
    }
    checkOnboardingStatus();
  }, [getIdTokenClaims, history]);

  return isOnboarded ? props.children : <div>Your onboarding component</div>
};

src/auth/protected-route.js file

// src/auth/protected-route.js

import React from "react";
import { Route } from "react-router-dom";
import { withAuthenticationRequired } from "@auth0/auth0-react";
import { WithOnboardingRedirect } from "./with-onboarding-redirect"
import { Loading } from "../components/index";

class ProtectedRoute extends React.Component {
  render() {
    return (
      <WithOnboardingRedirect
        component={this.props.component}
      >
      <Route
        component={withAuthenticationRequired(this.props.component, {
          onRedirecting: () => <Loading />,
        })}
        {...this.props.args}
      />
    </WithOnboardingRedirect>);
  }
}

export default ProtectedRoute;

Hi,

Thanks for these options. Here are my comments:

  1. Based on the blog post example, I still don’t know where I put the https://my_auth0_domain/continue redirection. In the redirectUri prop of the Auth0Provider?

  2. Like you said, not ideal…

  3. I get that it would redirect any user to my onboarding flow but only when he would attempt to access a protected route, so not necessarily after signup, right? Do I need, on top of that, to change my redirectUri then?

Thank you again for your help,

(In a couple of my previous messages, I accidentally put /complete when I meant /continue :sweat:. Sorry for any confusion that may have caused! I am going to edit those for future readers)

  1. The redirect to https://my_auth0_domain/continue would take place after the user completes their onboarding. For instance, if you have an onboarding form with a submit button, the submit handler would need to redirect the user like so:
const urlParams = new URLSearchParams(window.location.search);
const state = urlParams.get('state');
window.location.href = `https://my_auth0_domain/continue?state=${state}`

Auth0 will then redirect the user back to the original redirectUri in your Auth0Provider, as if it is a normal login.

  1. Yes, makes sense!

  2. Yes, that is a good point! You’d likely need to wrap all routes in the custom wrapper instead of the protected routes only. Again, I think option one might be the best solution since you would not have to add a great deal of this logic in your own app.

Hi again,

Before even considering to redirect users to https://my_auth0_domain/continue at the END of onboarding, the app is stuck after signup BEFORE onboarding starts when I activate the rule (as I explained in my first message). Could you please use the blog post I mentioned to explain what adaptations I should make to my React app?

Besides, I stumbled upon Auth0 Actions, would it be interesting to use this instead (and how)?

Hi @YassineDM,

Here is a code example based on the app in the article: GitHub - schamblee-auth0/react-app-with-onboarding-redirect-example

Here is the code for the redirect rule:

function (user, context, callback) {
  var loginCount = context.stats && context.stats.loginsCount ? context.stats.loginsCount : 0;
  var url = "http://localhost:4040/onboarding";
  
  if (context.protocol !== "redirect-callback" && loginCount <= 1) {
    context.redirect = { url };
  }
  
  return callback(null, user, context);
}

Regarding Actions, this feature is in beta, and you can read the documentation here: Actions - Beta. There is not a public general release date yet, but I’d estimated it to be within this quarter.

In the current beta version of actions, you would redirect users like this:

module.exports = async (event, context) => {
  return {
    command: {
      type: "redirect",
      url: "http://localhost:4040/onboarding"
    }
  };
};

Actions follow the same pattern of rules where you will pass the state to the /continue URL.