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

Sign in to an End-User Application with an External 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 the use case where the methods exposed by this library can be used to develop sign in functionality for external IDPs configured in the User Service.

Sign In

The user sign in functionality of an end-user can be easily developed using the following @axinom/mosaic-user-auth library methods.

The first step would be to retrieve the available IDPs that are configured for the end-user application. This can be done via the getIdpConfiguration method.

Then we must render each of the configured IDPs with a link/button to sign in, and for the onClick event, the authUrl returned by getIdpConfigurations() for each IDP must be called.

Let’s think of a scenario where 3 IDPs are configured using the User Service. The IDP Provider IDs are as follows.

  • AX_GOOGLE

  • AX_APPLE

  • AX_FACEBOOK

The component which renders the sign-in page could be written as follows.

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

export const Login: React.FC = () => {
    const { getIdpConfigurations } = useUserService();
    const [idpConfigurations, setIDPConfigurations] = useState<
        IdpConfiguration[]
    >([]);

    useEffect(() => {
        // Get the configured IDPs
        (async () => {
            const idpConfigs = await getIdpConfigurations(
            `${END_USER_APP_BASE_URL}/profiles`,
            );
            setIDPConfigurations(idpConfigs);
        })();
    }, [getIdpConfigurations]);

    return (
        <Grid textAlign="center" style={{ height: '100vh' }} verticalAlign="middle">
        <Grid.Column style={{ maxWidth: 450 }}>
            <Header as="h2" textAlign="center">
            Sign in to your account
            </Header>
            <Segment>
            <Header as="h5" textAlign="center">
                Social Connectors
            </Header>
            // Iterate through the IDP Configurations
            {idpConfigurations
                .sort(
                (a, b) =>
                    (a.sortOrder ?? Number.MAX_VALUE) -
                    (b.sortOrder ?? Number.MAX_VALUE),
                )
                .map((idpConfig) => {
                return (
                    <Button
                    key={idpConfig.idpConnectionId}
                    color={
                        idpConfig.providerId === 'AX_GOOGLE'
                        ? 'google plus'
                        : idpConfig.providerId === 'AX_APPLE'
                        ? 'grey'
                        : idpConfig.providerId === 'AX_FACEBOOK'
                        ? 'facebook'
                        : 'teal'
                    }
                    fluid
                    style={{ marginTop: '5px' }}
                    size="large"
                    // Call the IDP's authorization URL when clicked on the IDP
                    // Sign In button
                    onClick={() => {
                        if (idpConfig.authUrl !== undefined) {
                        window.location.replace(idpConfig.authUrl);
                        }
                    }}
                    >
                    Sign in with {idpConfig.title}
                    </Button>
                );
                })}
            </Segment>
        </Grid.Column>
        </Grid>
    );
}

The above code will render a Sign In component as below, and when a user clicks on either of the Social Connector buttons, the user will be redirected to the IDP’s authentication flow. After signed in, the user will be redirected to the path mentioned as the origin_url.

user lib sign in
Figure 1. Sign In Page

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.