Help with calling Auth0 management api

I am following the quick start guide for react. I copied all the code for calling an api and everything before it such as login button, logout button, and profile page. However, my fetch to the api to read the current user is always returning this error: Cannot read properties of undefined (reading ‘sub’). I’m not sure how to fix this error and was wondering if anyone could help. Thanks in advance.

This is my main.jsx

import React from 'react';
import { createRoot } from 'react-dom/client';
import { Auth0Provider } from '@auth0/auth0-react';
import App from './App';

const root = createRoot(document.getElementById('root'));

root.render(
  <Auth0Provider
    domain="dev-o751rwb3mvfsttg1.us.auth0.com"
    clientId="5VuwmvtQ0290VPQiDalLveiZQMWuspR7"
    authorizationParams={{
      redirect_uri: window.location.origin,
      audience: "https://dev-o751rwb3mvfsttg1.us.auth0.com/api/v2/",
      scope: "read:current_user update:current_user_metadata"
    }}
  >
    <App />
  </Auth0Provider>
);

This is my Profile.jsx

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

const Profile = () => {
    const { user, isAuthenticated, getAccessTokenSilently } = useAuth0();
    const [userMetadata, setUserMetadata] = useState(null);
    useEffect(() => {
        const getUserMetadata = async () => {
            const domain = "dev-o751rwb3mvfsttg1.us.auth0.com";

            try {
                const accessToken = await getAccessTokenSilently({
                    authorizationParams: {
                        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();
    }, [getAccessTokenSilently, user?.sub]);
    return (
        isAuthenticated && (
            <div>
                <img src={user.picture} alt={user.name} />
                <h2>{user.name}</h2>
                <p>{user.email}</p>
                <h3>User Metadata</h3>
                {userMetadata ? (
                    <pre>{JSON.stringify(userMetadata, null, 2)}</pre>
                ) : (
                    "No user metadata defined"
                )}
            </div>
        )
    );
};

export default Profile;

Hey there @rodyli123 welcome to the community!

Thanks for the detailed description :slight_smile: Is the main reason for calling the Management just to get the user’s metadata?

Correct, but later down the line I would like to use the management api to update my user. The work around I’m currently implementing is storing a user model in my database, but obviously it would be more convenient to update the user on auth0 database.

Thanks for clarifying!

Do you plan on only updating userMetadata? As Management API Access Tokens in a SPA are limited in scope, it may be easier/more flexible to add metadata as a custom claim(s) in tokens and access it through the user object directly. As far as updating a user, if you need to update more than userMetadata it would be best to consider proxying the request through a backend service - This allows for fully scoped Management API Access Tokens:

1 Like

I appreciate the response. I now understand that I am unable to update the user from the SPA and instead will have to go through my backend. However, shouldn’t the code above theoretically work. I remember seeing somewhere that reading the current user through the management api is possible through the SPA.

1 Like

That’s correct, you should be able to read user metadata through the SPA - I don’t see anything that immediately stands out in your code, but I’ll try to test this flow in my own environment to see what I can come up with.

1 Like

No worries if nothing comes to mind, thanks for the replies I appreciate it.

1 Like

Happy to help!

While not exactly your code, I was able to get it working by building off the React sample app.

Here’s my index.js

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { Auth0Provider } from "@auth0/auth0-react";

ReactDOM.render(
  <Auth0Provider
    domain="{your_domain}.us.auth0.com",
    clientId="{client_id}",
    authorizationParams={{
      redirect_uri: window.location.origin,
      audience: "https://{domain}/api/v2/",
      scope: "read:current_user"
    }}
  >
    <App />
  </Auth0Provider>,
  document.getElementById("root")
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

Profile.js

import React, { useEffect, useState } from "react";
import { Container, Row, Col } from "reactstrap";
import Highlight from "../components/Highlight";
import Loading from "../components/Loading";
import { useAuth0, withAuthenticationRequired } from "@auth0/auth0-react";

export const ProfileComponent = () => {
  const { user, getAccessTokenSilently } = useAuth0();
  const [userMetadata, setUserMetadata] = useState(null);

  useEffect(() => {
    const getUserMetadata = async () => {
      try {
        const domain = "{your_domain}";
        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}`,
          },
        });

        if (!metadataResponse.ok) {
          throw new Error(`Failed to fetch user metadata: ${metadataResponse.statusText}`);
        }

        const { user_metadata } = await metadataResponse.json();
        setUserMetadata(user_metadata);
      } catch (e) {
        console.error(e.message);
      }
    };

    getUserMetadata();
  }, [getAccessTokenSilently, user?.sub]);

  if (user["https://yournamespace.com/isNewUser"]) {
    alert("this is a new user");
  }

  return (
    <Container className="mb-5">
      <h2>{user.name}</h2>
      <Row className="align-items-center profile-header mb-5 text-center text-md-left">
        <Col md={2}>
          <img
            src={user.picture}
            alt="Profile"
            className="rounded-circle img-fluid profile-picture mb-3 mb-md-0"
          />
        </Col>
        <Col md>
          <p className="lead text-muted">{user.email}</p>
        </Col>
      </Row>
      <Row>
        <Highlight>{JSON.stringify(user, null, 2)}</Highlight>
      </Row>
      <Row>
        <h3>User Metadata</h3>
        {userMetadata ? (
          <Highlight>{JSON.stringify(userMetadata, null, 2)}</Highlight>
        ) : (
          <p>No user metadata defined</p>
        )}
      </Row>
    </Container>
  );
};

export default withAuthenticationRequired(ProfileComponent, {
  onRedirecting: () => <Loading />,
});
1 Like

Another question that came up in my mind is that whenever I add the audience and scope parameter for the authorization params my user in const { user } = useAuth0(); only displays a sub field rather than everything else in the meta data such as name, email, etc. Why is this?

Additionally, if I wanted to work around this by making a call to the backend to get the user meta data would I still have to include the audience and scope the authorization params?

If you inspect the request to /authorize (browser dev tools) do you see any other scopes being passed? You’ll just want to make sure openid and profile are being passed as well to get more claims on the user.

No, basically you would authorize the user to call your own API/backend - Your API would validate this request and then make a M2M call to Auth0 Management API on behalf of the user.

Passing in openid and profile as well fixed the issue thank you.

1 Like

Awesome, thanks for confirming! :slight_smile: