Docs
User Profile
Experimental

User Profile

Provides a detailed user profile with essential information, supporting MFA enrollment.

User Profile

Info about you and your preferences

General

Installation

Install the following dependencies:

npx shadcn-ui@latest add badge button card dialog form input label separator toast
pnpm install zod react-hook-form @hookform/resolvers moment ua-parser-js

Copy and paste the following code into your project.

"use client"; import clsx, { ClassValue } from "clsx"; import { useState } from "react"; import { twMerge } from "tailwind-merge"; import { buttonVariants } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; import useMfaEnrollment from "../hooks/use-mfa-enrollment"; import useUserMetadata from "../hooks/use-user-metadata"; import useUserSessions from "../hooks/use-user-sessions"; import BasicInfoForm from "./basic-info-form"; /** * Make sure to install the MFAEnrollment component from: * - https://components.lab.auth0.com/docs/components/mfa-enrollment */ import MFAEnrollment from "./mfa-enrollment"; /** * Make sure to install the UserMetadata component from: * - https://components.lab.auth0.com/docs/components/user-metadata */ import UserMetadata from "./user-metadata"; import UserSessions from "./user-sessions"; interface KeyValueMap { [key: string]: any; } type MfaEnrollment = { name: string; enabled: boolean; enrollmentId?: string; }; function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } export default function UserProfile({ user, userMetadata, metadataSchema, factors, sessions, }: { user: KeyValueMap; metadataSchema: any; userMetadata?: KeyValueMap; factors?: MfaEnrollment[]; sessions?: KeyValueMap[]; }) { const [currentItem, setCurrentItem] = useState("basic-info"); const metadataDefaultValues = userMetadata; const { updateUserMetadata, fetchUserMetadata } = useUserMetadata(); const { fetchUserSessions, deleteUserSession } = useUserSessions(); const { fetchFactors, createEnrollment, deleteEnrollment } = useMfaEnrollment(); const handleItemClick = (id: string) => () => { setCurrentItem(id); }; return ( <div className="max-w-screen-lg mx-auto gap-5 md:gap-5 lg:gap-5 justify-center p-2 flex flex-col w-full"> <div className="md:block"> <div className="space-y-0.5"> <h2 className="text-2xl font-bold tracking-tight">User Profile</h2> <p className="text-muted-foreground"> Info about you and your preferences </p> </div> <Separator className="my-6" /> <div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0"> <aside className="lg:w-1/5"> <nav className={ "flex space-x-1 lg:flex-col lg:space-x-0 lg:space-y-1 justify-center" } > {[ { title: "General", id: "basic-info" }, { title: "Preferences", id: "preferences" }, { title: "Security", id: "security" }, { title: "Sessions", id: "sessions" }, ].map((item) => ( <button onClick={handleItemClick(item.id)} type="button" key={item.id} className={cn( buttonVariants({ variant: "ghost" }), currentItem === item.id ? "bg-muted hover:bg-muted" : "hover:bg-transparent hover:underline", "justify-start", "px-3 py-1.5" )} > {item.title} </button> ))} </nav> </aside> <div className="flex-1"> {currentItem === "basic-info" && <BasicInfoForm user={user} />} {currentItem === "preferences" && ( <UserMetadata schema={metadataSchema} metadata={metadataDefaultValues} onFetch={fetchUserMetadata} onSave={updateUserMetadata} /> )} {currentItem === "security" && ( <MFAEnrollment factors={factors} onFetch={fetchFactors} onCreate={createEnrollment} onDelete={deleteEnrollment} /> )} {currentItem === "sessions" && ( <UserSessions user={user} sessions={sessions} onFetch={fetchUserSessions} onDelete={deleteUserSession} /> )} </div> </div> </div> </div> ); }

Component behavior

By design, our components provide basic behavior without making any requests to the Auth0 Management API. To help you implement the full feature, we've also included React Hooks and NextJS routers for calling and proxying the Auth0 Management API.

Update the import paths to match your project setup.

React Hooks

useUserMetadata

A hook to update the user metadata.

import { useCallback } from "react"; interface KeyValueMap { [key: string]: any; } export default function useUserMedata() { const fetchUserMetadata = useCallback(async (): Promise<KeyValueMap> => { try { /** * '/api/auth/user/metadata' is a custom endpoint which will proxy * the request to the Auth0 Management API. * * Proxy sample at: https://components.lab.auth0.com/docs/components/user-metadata#nextjs-routers */ const response = await fetch("/api/auth/user/metadata", { method: "GET", headers: { "Content-Type": "application/json", }, }); // TODO: Better handling rate limits if (response.status === 429) { return { status: 429 }; } const userMetadata: KeyValueMap = await response.json(); return { metadata: userMetadata, status: response.status, }; } catch (error) { console.error(error); return { status: 500 }; } }, []); const updateUserMetadata = useCallback( async ( values: KeyValueMap ): Promise<{ metadata?: KeyValueMap; status: number; }> => { try { /** * '/api/auth/user/metadata' is a custom endpoint which will proxy * the request to the Auth0 Management API. * * Proxy sample at: https://components.lab.auth0.com/docs/components/user-metadata#nextjs-routers */ const response = await fetch("/api/auth/user/metadata", { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify(values), }); // TODO: Better handling rate limits if (response.status === 429) { return { status: 429 }; } const metadata: KeyValueMap = await response.json(); return { metadata, status: response.status }; } catch (e) { console.error(e); return { status: 500 }; } }, [] ); return { updateUserMetadata, fetchUserMetadata }; }

useMfaEnrollment

A hook to manage MFA enrollments.

"use client"; import { useCallback } from "react"; type CreateEnrollmentResponse = { ticket_url: string }; type MfaEnrollment = { name: string; enabled: boolean; enrollmentId?: string; }; type DeleteEnrollmentResponse = { id: string }; export default function useMfaEnrollment() { const fetchFactors = useCallback(async (): Promise<{ factors?: MfaEnrollment[]; status: number; }> => { try { /** * '/api/auth/mfa' is a custom endpoint which will proxy * the request to the Auth0 Management API. * * Proxy sample at: https://components.lab.auth0.com/docs/components/mfa-enrollment#nextjs-routers */ const response = await fetch("/api/auth/mfa", { method: "GET", headers: { "Content-Type": "application/json", }, }); // TODO: Better handling rate limits if (response.status === 429) { return { status: 429 }; } const data: MfaEnrollment[] = await response.json(); return { factors: data.filter((factor: MfaEnrollment) => factor.enabled), status: response.status, }; } catch (error) { console.error(error); return { status: 500 }; } }, []); const createEnrollment = useCallback( async ( factor: string ): Promise<{ enrollment?: CreateEnrollmentResponse | undefined; status: number; }> => { try { /** * '/api/auth/mfa' is a custom endpoint which will proxy * the request to the Auth0 Management API. * * Proxy sample at: https://components.lab.auth0.com/docs/components/mfa-enrollment#nextjs-routers */ const response = await fetch("/api/auth/mfa", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ factor }), }); // TODO: Better handling rate limits if (response.status === 429) { return { status: 429 }; } const enrollment: CreateEnrollmentResponse = await response.json(); return { enrollment, status: response.status }; } catch (error) { console.error(error); return { status: 500 }; } }, [] ); const deleteEnrollment = useCallback( async ( enrollmentId: string ): Promise<{ enrollment?: DeleteEnrollmentResponse; status: number; }> => { try { /** * '/api/auth/mfa' is a custom endpoint which will proxy * the request to the Auth0 Management API. * * Proxy sample at: https://components.lab.auth0.com/docs/components/mfa-enrollment#nextjs-routers */ const response = await fetch(`/api/auth/mfa/${enrollmentId}`, { method: "DELETE", }); // TODO: Better handling rate limits if (response.status === 429) { return { status: 429 }; } const data: DeleteEnrollmentResponse = await response.json(); return { enrollment: data, status: response.status }; } catch (error) { console.error(error); return { status: 500 }; } }, [] ); return { fetchFactors, createEnrollment, deleteEnrollment }; }

useUserSessions

A hook to manage current user sessions.

import { useCallback } from "react"; interface KeyValueMap { [key: string]: any; } export default function useUserSessions() { const fetchUserSessions = useCallback(async (): Promise<{ sessions?: KeyValueMap[]; status: number; }> => { try { /** * '/api/auth/user/sessions' is a custom endpoint which will proxy * the request to the Auth0 Management API. * * Proxy sample at: * - https://components.lab.auth0.com/docs/components/user-sessions#nextjs-routers */ const response = await fetch("/api/auth/user/sessions", { method: "GET", headers: { "Content-Type": "application/json", }, }); // TODO: Better handling rate limits if (response.status === 429) { return { status: 429 }; } const sessions: KeyValueMap[] = await response.json(); return { sessions: sessions, status: response.status, }; } catch (error) { console.error(error); return { status: 500 }; } }, []); const deleteUserSession = useCallback( async ( sessionId: string ): Promise<{ id?: string; status: number; }> => { try { /** * '/api/auth/user/sessions/[id]' is a custom endpoint which will proxy * the request to the Auth0 Management API. * * Proxy sample at: * - https://components.lab.auth0.com/docs/components/user-sessions#nextjs-routers */ const response = await fetch(`/api/auth/user/sessions/${sessionId}`, { method: "DELETE", }); // TODO: Better handling rate limits if (response.status === 429) { return { status: 429 }; } return { id: sessionId, status: response.status }; } catch (error) { console.error(error); return { status: 500 }; } }, [] ); return { fetchUserSessions, deleteUserSession }; }

NextJS routers

UserMetadata router

Handles user metadata update.

import { ManagementClient } from "auth0"; import { NextResponse } from "next/server"; import { getSession, withApiAuthRequired } from "@auth0/nextjs-auth0"; /** * Make sure to install the withRateLimit from: * - https://components.lab.auth0.com/docs/rate-limit#helpers */ import { withRateLimit } from "./helpers/rate-limit"; const client = new ManagementClient({ domain: new URL(process.env.AUTH0_ISSUER_BASE_URL!).host, clientId: process.env.AUTH0_CLIENT_ID_MGMT!, clientSecret: process.env.AUTH0_CLIENT_SECRET_MGMT!, }); /** * @example export const GET = handleUserMetadataFetch(); */ export function handleUserMetadataFetch() { return withRateLimit( withApiAuthRequired(async (): Promise<NextResponse> => { try { const session = await getSession(); const user_id = session?.user.sub; const response = await client.users.get({ id: user_id, }); const { data } = response; return NextResponse.json(data.user_metadata || {}, { status: response.status, }); } catch (error) { console.error(error); return NextResponse.json( { error: "Error fetching user metadata" }, { status: 500 } ); } }) ); } /** * @example export const PUT = handleUserMetadataUpdate(); */ // TODO: better error handling export function handleUserMetadataUpdate() { return withRateLimit( withApiAuthRequired(async (request: Request): Promise<NextResponse> => { try { const session = await getSession(); const userId = session?.user.sub; const user_metadata = await request.json(); await client.users.update({ id: userId }, { user_metadata }); return NextResponse.json(user_metadata, { status: 200, }); } catch (error) { console.error(error); return NextResponse.json( { error: "Error updating user metadata" }, { status: 500 } ); } }) ); }

MFA router

Handles list, update and create enrollments.

import { ManagementClient } from "auth0"; import { NextResponse } from "next/server"; import { getSession, withApiAuthRequired } from "@auth0/nextjs-auth0"; /** * Make sure to install the withRateLimit from: * - https://components.lab.auth0.com/docs/rate-limit#helpers */ import { withRateLimit } from "./helpers/rate-limit"; const client = new ManagementClient({ domain: new URL(process.env.AUTH0_ISSUER_BASE_URL!).host, clientId: process.env.AUTH0_CLIENT_ID_MGMT!, clientSecret: process.env.AUTH0_CLIENT_SECRET_MGMT!, }); /** * @example export const GET = handleMFAFactorsList(); */ export function handleMFAFactorsList() { return withRateLimit( withApiAuthRequired(async (): Promise<NextResponse> => { try { const session = await getSession(); const user_id = session?.user.sub; const availableFactors = [ "push-notification", "sms", "voice", "otp", "webauthn-roaming", "webauthn-platform", ]; const { data: factors } = await client.guardian.getFactors(); const response = await client.users.getAuthenticationMethods({ id: user_id, }); const { data: enrollments } = response; return NextResponse.json( factors .filter((factor: any) => { let factorName: string = factor.name; return availableFactors.includes(factorName) && factor.enabled; }) .map((factor: any) => { const enrollmentInfo = enrollments.find((enrollment: any) => { let factorName: string = factor.name; if (factor.name === "sms" || factor.name === "voice") { factorName = "phone"; } return enrollment.type.includes(factorName); }); return { ...factor, enrollmentId: enrollmentInfo?.id, }; }), { status: response.status, } ); } catch (error) { console.error(error); return NextResponse.json( { error: "Error fetching MFA Enrollments" }, { status: 500 } ); } }) ); } /** * @example export const POST = handleMFAFactorEnrollment(); */ export function handleMFAFactorEnrollment() { return withRateLimit( withApiAuthRequired(async (request: Request): Promise<NextResponse> => { try { const session = await getSession(); const user_id = session?.user.sub; const { factor }: { factor: string } = await request.json(); let factorName: string = factor; if (factor === "sms" || factor === "voice") { factorName = "phone"; } const response = await client.guardian.createEnrollmentTicket({ user_id, //@ts-ignore factor: factorName, allow_multiple_enrollments: true, }); const { data } = response; return NextResponse.json(data, { status: response.status, }); } catch (error) { console.error(error); return NextResponse.json( { error: "Error creating MFA Enrollment" }, { status: 500 } ); } }) ); } /** * @example export const DELETE = handleMFADeleteEnrollment(); */ export function handleMFADeleteEnrollment() { return withRateLimit( withApiAuthRequired( async (request: Request, { params }: any): Promise<NextResponse> => { try { const session = await getSession(); const user_id = session?.user.sub; const { enrollmentId }: { enrollmentId: string } = params; await client.users.deleteAuthenticationMethod({ id: user_id, authentication_method_id: enrollmentId, }); return NextResponse.json( { id: enrollmentId }, { status: 200, } ); } catch (error) { console.error(error); return NextResponse.json( { error: "Error deleting MFA Enrollment" }, { status: 500 } ); } } ) ); }

UserSessions router

Handles user sessions fetch and revocation.

import { ManagementClient } from "auth0"; import { NextResponse } from "next/server"; import { getSession, withApiAuthRequired } from "@auth0/nextjs-auth0"; /** * Make sure to install the withRateLimit from: * - https://components.lab.auth0.com/docs/rate-limit#helpers */ import { withRateLimit } from "./helpers/rate-limit"; const client = new ManagementClient({ domain: new URL(process.env.AUTH0_ISSUER_BASE_URL!).host, clientId: process.env.AUTH0_CLIENT_ID_MGMT!, clientSecret: process.env.AUTH0_CLIENT_SECRET_MGMT!, }); /** * @example export const GET = handleUserSessionsFetch(); */ export function handleUserSessionsFetch() { return withRateLimit( withApiAuthRequired(async (): Promise<NextResponse> => { try { const session = await getSession(); const user_id = session?.user.sub; const response = await client.users.getSessions({ user_id, }); const { data } = response; return NextResponse.json(data.sessions || [], { status: response.status, }); } catch (error) { console.error(error); return NextResponse.json( { error: "Error fetching user metadata" }, { status: 500 } ); } }) ); } /** * @example export const DELETE = handleDeleteUserSession(); */ export function handleDeleteUserSession() { return withRateLimit( withApiAuthRequired( async (request: Request, { params }: any): Promise<NextResponse> => { try { const { id }: { id: string } = params; await client.sessions.delete({ id, }); return NextResponse.json( { id }, { status: 200, } ); } catch (error) { console.error(error); return NextResponse.json( { error: "Error deleting MFA Enrollment" }, { status: 500 } ); } } ) ); }