I'd like to modify the Daml UI template to use with Auth0 login

Could any of you help me?

I want to modify the Daml UI template with as little change as possible to use Auth0 login.

My plan is to keep the login button as is, ignore the two text input fields on the login page, trigger an Auth0 login via the login button, retrieve the access token from Auth0, get the Daml ledger party id from the token, and pass the party and token variables to the UI template.

I have set up the Auth0 user profile so that I specify the custom Daml ledger claims (including the party id as the only element of the actAs list) in the user profile. In this way, if the user authenticates with Auth0, Auth0 will return the correct Daml access token.

The current form of the loginUser function in the Daml UI template is the following:

function loginUser(
    dispatch : React.Dispatch<LoginAction>,
    party : string,
    userToken : string,
    history : History,
    setIsLoading : React.Dispatch<React.SetStateAction<boolean>>,
    setError : React.Dispatch<React.SetStateAction<boolean>>) {
  setError(false);
  setIsLoading(true);

  if (!!party) {
    const token = userToken || createToken(party)
    localStorage.setItem(damlPartyKey, party);
    localStorage.setItem(damlTokenKey, token);

    dispatch({ type: "LOGIN_SUCCESS", token, party });
    setError(false);
    setIsLoading(false);
    history.push("/app");
  } else {
    dispatch({ type: "LOGIN_FAILURE" });
    setError(true);
    setIsLoading(false);
  }
}

See in the UserContext.tsx file: daml-ui-template/UserContext.tsx at eecf1f7983ec5a65c87f0140908a0ac151a1619c · digital-asset/daml-ui-template · GitHub

From Auth0, I have the @auth0/auth0-react package, see: GitHub - auth0/auth0-react: Auth0 SDK for React Single Page Applications (SPA)

In it, the useAuth0 hook exports the functions which I need for the login, and for token retrieval: auth0-react/use-auth0.tsx at 9a15c540fee6d53514eaaea77cbadfb767b14891 · auth0/auth0-react · GitHub

* const {
 *   // Auth state:
 *   error,
 *   isAuthenticated,
 *   isLoading,
 *   user,
 *   // Auth methods:
 *   getAccessTokenSilently,
 *   getAccessTokenWithPopup,
 *   getIdTokenClaims,
 *   loginWithRedirect,
 *   loginWithPopup,
 *   logout,
 * } = useAuth0<TUser>();

I want to use loginWithRedirect for login, and getAccessTokenSilently for retrieving the access token.

The point where I’m stuck is that the original loginUser function is not async, whereas both functions which I want to use from useAuth0 are async functions.

In theory, I know that I have to use here React hooks, as does the current version of the Login component of the Daml UI template, but it seems I don’t have enough experience yet with React state management to find the simple solution for transforming the loginUser function into an async function, preserving its current handling of the party and token variables.

I would appreciate any hint.

2 Likes

Hi @gyorgybalazsi . If I remember correctly, you can get the token from the user object non-asynchronously, while you hand the login functions as callbacks for the login buttons. This will then set the token in the user object if login succeeds. I wrote a Auth0 login in dbay/App.tsx at 3e02df904680f1e2b1c99c39fbc71a1b295cca49 · RobinKrom/dbay · GitHub (the Auth0 part is commented out for easier development).

2 Likes

Yes, that’s the current version which I want to modify. The current function generates the access token locally, that’s why it’s non-async, I want to retreive the token from Auth0 which is by definition async.

1 Like

Here’s the commented out Auth0 code:

// production
  // const { login, logout, isAuthenticated, user, authResult } = useAuth();
  // const party = user?.sub.replace('|', '_');
  // const email = user?.email;
  // const token = authResult?.idToken

  // useEffect(() => {
    // localStorage.setItem(
      // 'ORIGIN',
      // `${window.location.href.replace(window.location.origin, '')}`
    // );
  // }, []);


  // if (isAuthenticated()) {
    // return (
      // <DamlLedger
        // token = {token}
        // party = {party}
      // >
        // <MainScreen
          // handleLogOut={logout}
          // handleGoToInvoices={() => setView({kind: 'invoices'})}
          // handleGoToCards={() => setView({kind: 'cards'})}
          // handleGoToChannels={() => setView({kind: 'channels'})}
          // handleGoToRatings={() => setView({kind: 'ratings'})}
          // handleGoToUserSummary={(n) => setView({kind: 'usersummary', name:n})}
          // activeView={view}
          // token={token}
          // email={email}
        // />
      // </DamlLedger>
    // );

  // } else {
    // return <LoginScreen onLogin={login}/>;
  // }

The first line uses the Auth0 hook to get the login, logout functions and the user object. The token is contained in this user object as authResult.idToken after you used the login function passed to the login button.

2 Likes

Thank you @drsk !

Am I right to think that this is not a standard OAuth authorization flow? It seems to me that you are using the id token, which serves a different purpose from the access token.

OAuth explicitly warns against using id tokens in this way:

“ID Tokens should never be used to obtain direct access to APIs or to make authorization decisions.”

UPDATE

It seems from the README of the react-auth-hook library you are using that we can also retrieve the access token from the authResult object. I will try that one.

My only concern remains that this is not an official Auth0 package.

I would prefer the official Auth0 SDK which, as stated above, uses the async getAccessTokenSilently to get the access token.

1 Like

Yes, this true. You should be using the access token, not the ID token.

If you want to use the getAccessTokenSilently from the official Auth0 package, I’d try to use an useEffect hook in React executing the getAccessTokenSilently together with a useState hook to keep track of whether you fetched the token already. This is from the Auth0 documentation:

useEffect(() => {
  const getUserMetadata = async () => {
    const domain = "YOUR_DOMAIN";

    try {
      const accessToken = await getAccessTokenSilently({
        audience: `https://${domain}/api/v2/`,
        scope: "read:current_user",
      });

      const userDetailsByIdUrl = `https://${domain}/api/v2/users/${user.sub}`;

      const metadataResponse = await fetch(userDetailsByIdUrl, {
        headers: {
          Authorization: `Bearer ${accessToken}`,
        },
      });

      const { user_metadata } = await metadataResponse.json();

      setUserMetadata(user_metadata);
    } catch (e) {
      console.log(e.message);
    }
  };

  getUserMetadata();
}, []);
2 Likes

Yes, thank you

1 Like