Mosaic products documentation: Concepts, API Reference, Technical articles, How-to, Downloads and tools

Sign in to an End-User Application with AxAuth IDP

Introduction

@axinom/mosaic-user-auth is a react library provided to be used by the frontend application to make end-user related authentication and other end-user related operations easier.

This article describes use cases where the library methods can be used in relation with AxAuth IDP.

Sign Up

Apart from the signing in through an OAuth 2.0 compatible IDP, the Mosaic User Service supports standalone user management. Using this functionality, frontend application developers can introduce user sign up functionality and signing in through those newly created user accounts using username/passsword authentication flow.

Below is how user sign up can be configured using the helper methods provided by @axinom/mosaic-user-auth library. To utilize the user sign up process, the Administrator must configure an AxAuth IDP in the User Service. And the corresponding AxAuth user store must have a valid User Sign Up Webhook configured. (Figure 2)

ax auth user store
Figure 1. AxAuth User Store configuration

When the initiateUserSignUp is called, the User Sign Up Webhook configured in the AxAuth user store is called passing the User Sign Up OTP. The developer must handle this and pass this OTP to the end-user (i.e. via email).

The developer has the choice here to provide a link/a page to the user, where the user would click on/provide the OTP, and eventually call the completeUserSignUp method in the @axinom/mosaic-user-auth library, or to verify the user straightaway after initiating the sign up process.

The sign in process will only work for verified users, and the user service library gives the freedom of choice for the developer to decide how the sign up process is handled.

const SignUp: React.FC = () => {
  const { initiateUserSignUp } = useUserService();
  const [email, setEmail] = useState<string>('');
  const [password, setPassword] = useState<string>('');
  const [error, setError] = useState<string>('');

  return (
    <Grid textAlign="center" style={{ height: '100vh' }} verticalAlign="middle">
    <Grid.Column style={{ maxWidth: 450 }}>
      <Header as="h2" textAlign="center">
        Sign-up for a new account
      </Header>
      <Form size="large" onSubmit={async (event) => {
        event.preventDefault();
        // Make the first request to the User Service, initiating the User Sign Up flow.
        const signUpResponse = await initiateUserSignUp(`${END_USER_APP_BASE_URL}/signup`, {email, password});
        if (signUpResponse.code ===  UserSignUpResponseCode.SUCCESS) {
          window.location.assign('/login');
        } else {
          setError(signUpResponse.message ? signUpResponse.message : 'Error Signing up.');
        }

      }}>
        <Segment>
          <Message color="red"hidden={error === ''}>{error}</Message>
          <Form.Input
            fluid
            icon="user"
            iconPosition="left"
            placeholder="Email address"
            type="email"
            onChange={(event) => setEmail(event.target.value)}
            value={email}
          />
          <Form.Input
            fluid
            icon="lock"
            iconPosition="left"
            placeholder="Password"
            type="password"
            onChange={(event) => setPassword(event.target.value)}
            value={password}
          />

          <Button color="black" fluid size="large">
            Register
          </Button>
        </Segment>
      </Form>
    </Grid.Column>
  </Grid>
  )
};
sign up user
Figure 2. Sign Up User

Signing In

For users that are signing up using AxAuth IDP, the @axinom/mosaic-user-auth library provides the capability of signing in using the username/password flow. The developers can make use of signIn method for this purpose. Below is a sample code where a react component is making use of this method.

import { useUserService } from '@axinom/mosaic-user-auth';

export const Login: React.FC = () => {

  const [email, setEmail] = useState<string>('');
  const [password, setPassword] = useState<string>('');
  const [error, setError] = useState<string>('');

  const handleSignIn = async(event: React.FormEvent<HTMLFormElement>): Promise<void> => {
    event.preventDefault();
    // call the signIn method with the email and password
    const signInResponse = await signIn({
      email: email,
      password: password
    });
    if (signInResponse.code === SignInResponseCode.SUCCESS) {
      window.location.assign('/');
    } else {
      setError(`Unable to Sign In. ${signInResponse.details?.error ?? signInResponse.message}`)
    }
  }

  return (
    <Grid textAlign="center" style={{ height: '100vh' }} verticalAlign="middle">
      <Grid.Column style={{ maxWidth: 450 }}>
        <Form size="large" onSubmit={async (event: React.FormEvent<HTMLFormElement>, data: FormProps) => {
          await handleSignIn(event);
        }}>
          <Segment>
            <Header as="h5" textAlign="center">
              StreamApp Account
            </Header>
            <Message color="red" hidden={error === ''}>{error}</Message>
            <Form.Input
              fluid
              icon="user"
              iconPosition="left"
              placeholder="Email address"
              id="email"
              value={email}
              onChange={(event) => {setEmail(event.target.value)}}
            />
            <Form.Input
              fluid
              icon="lock"
              iconPosition="left"
              placeholder="Password"
              type="password"
              id="password"
              value={password}
              onChange={(event) => {setPassword(event.target.value)}}
            />

            <Button type = "submit" primary fluid size="large">
              Sign In
            </Button>
          </Segment>
        </Form>
      </Grid.Column>
    </Grid>
  );
};
sign in ropc
Figure 3. Sign In

Handling the Access Token

After the sign in process is completed, the access token for the user can be retrieved by calling the getToken method. This can be done in the Root component as below.

export const App: React.FC = () => {
  const { getToken, addTokenChangedHandler, removeTokenChangedHandler } =
    useUserService();
  const [tokenResponse, setTokenResponse] = useState<TokenResponse | null>(
    null,
  );

  useEffect(() => {
    // Get the access token and store in tokenResponse.
    // If the token changes or removed, call getToken()
    // and refresh the token.
    (async () => {
      setTokenResponse(await getToken());
    })();
  }, [addTokenChangedHandler, getToken, removeTokenChangedHandler]);

  return (
    <BrowserRouter>
      <Switch>
        <Route path="/profiles">
          <ProfileSelector />
        </Route>

        <Route>
          {tokenResponse === null ? (
            <LoadingPlaceholder />
          ) : tokenResponse.userToken === undefined ? (
            <LandingPage />
          ) : (
            <ProfileSelector />
          )}
        </Route>
      </Switch>
    </BrowserRouter>
  );
}

In the above code, the router checks if there’s a valid access token in the tokenResponse variable. If there is an access token, which means the user has successfully signed in, it displays the ProfileSelector component. Or else, it displays a generic LandingPage component.

Handling Password Resets

AxAuth IDP provides Forgot Password functionality for users that want to reset their password. This is a two step process where first the user initiates a request showing the intent of wanting to change their password (initiateResetPassword method). Next, a Password Reset OTP will be generated by AxAuth that must be subsequently communicated to the user by the developer. This Password Reset OTP will be forwarded to the Forgot Password Webhook defined in AxAuth User Store once its generated. The developer must take care of communicating this to the end-user via email or any other means.

Then the password reset flow must be completed by the user entering the Password Reset OTP (completeResetPassword method). The new password must be either provided with the initiate request or the complete request. Failing to do so will result in an error.

Initiating the Password Reset flow

const ResetPassword: React.FC = () => {
  const { initiateResetPassword } = useUserService();
  const [email, setEmail] = useState<string>('');
  const [error, setError] = useState<string>('');

  return (
    <Grid textAlign="center" style={{ height: '100vh' }} verticalAlign="middle">
    <Grid.Column style={{ maxWidth: 450 }}>
      <Header as="h2" textAlign="center">
        Reset Password
      </Header>
      <Form size="large" onSubmit={async (event) => {
        event.preventDefault();
        // call initiateResetPassword method
        const signUpResponse = await initiateResetPassword(`${END_USER_APP_BASE_URL}/reset-password`, email);
        if (signUpResponse.code === PasswordResponseCode.SUCCESS) {
          window.location.assign('/complete-reset-password');
        } else {
          setError(signUpResponse.message ?? 'Error resetting password.');
        }

      }}>
        <Segment>
          <Message>An OTP code will be sent to the following email address to reset the password.</Message>
          <Message color="red"hidden={error === ''}>{error}</Message>
          <Form.Input
            fluid
            icon="user"
            iconPosition="left"
            placeholder="Email address"
            type="email"
            onChange={(event) => setEmail(event.target.value)}
            value={email}
          />

          <Button color="black" fluid size="large">
            Confirm
          </Button>
        </Segment>
      </Form>
    </Grid.Column>
  </Grid>
  )
};
initiate reset password
Figure 4. Initiate Reset Password Flow

Completing the Password Reset flow

const CompleteResetPassword: React.FC = () => {
  const { completeResetPassword } = useUserService();
  const [password, setPassword] = useState<string>('');
  const [otp, setOtp] = useState<string>('');
  const [error, setError] = useState<string>('');

  return (
    <Grid textAlign="center" style={{ height: '100vh' }} verticalAlign="middle">
    <Grid.Column style={{ maxWidth: 450 }}>
      <Header as="h2" textAlign="center">
        Confirm New Password
      </Header>
      <Form size="large" onSubmit={async (event) => {
        event.preventDefault();
        // call the completeResetPassword with the Reset OTP
        const signUpResponse = await completeResetPassword({newPassword: password, resetOtp: otp});
        if (signUpResponse.code === PasswordResponseCode.SUCCESS) {
          window.location.assign('/login');
        } else {
          setError(signUpResponse.message ?? 'Error resetting password.');
        }

      }}>
        <Segment>
          <Message hidden={error !== ''}>Enter the new password along with the OTP code sent to your registered email.</Message>
          <Message color="red"hidden={error === ''}>{error}</Message>
          <Form.Input
            fluid
            icon="lock"
            iconPosition="left"
            placeholder="New password"
            type="password"
            onChange={(event) => setPassword(event.target.value)}
            value={password}
          />
          <Form.Input
            fluid
            icon="key"
            iconPosition="left"
            placeholder="OTP"
            onChange={(event) => setOtp(event.target.value)}
            value={otp}
          />

          <Button color="black" fluid size="large">
            Reset Password
          </Button>
        </Segment>
      </Form>
    </Grid.Column>
  </Grid>
  )
};
complete reset password
Figure 5. Complete Reset Password Flow