Docs
Organization Profile
Experimental

Organization Profile

Provides a detailed organization profile with essential information, supporting SSO self service.

Organization Settings

All the information about your organization.

General

Installation

Install the following dependencies:

npx shadcn-ui@latest add card button dialog form input
pnpm install zod react-hook-form @hookform/resolvers

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 useOrganizations from "../hooks/use-organizations"; /** * Make sure to install the OrganizationInfo component from: * - https://components.lab.auth0.com/docs/components/organization-info */ import OrganizationInfo from "./organization-info"; /** * Make sure to install the OrganizationMetadata component from: * - https://components.lab.auth0.com/docs/components/organization-metadata */ import OrganizationMetadata from "./organization-metadata"; import OrganizationSSO from "./organization-sso"; function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } interface KeyValueMap { [key: string]: any; } export default function OrganizationProfile({ orgId, organization, connections, metadataSchema, }: { orgId: string; organization?: KeyValueMap; connections?: KeyValueMap[]; metadataSchema: any; }) { const [currentItem, setCurrentItem] = useState("basic-info"); const metadataDefaultValues = organization?.metadata; const { fetchOrganization, updateOrganization, fetchOrganizationConnections, deleteOrganizationConnection, startSelfServiceConfiguration, startSelfServiceConnectionUpdate, } = useOrganizations(); 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"> Organization Settings </h2> <p className="text-muted-foreground"> All the information about your organization. </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: "sso" }, ].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" && ( <OrganizationInfo organization={organization} orgId={orgId} onFetch={fetchOrganization} onSave={updateOrganization} /> )} {currentItem === "preferences" && ( <OrganizationMetadata orgId={orgId} schema={metadataSchema} metadata={metadataDefaultValues} onFetch={fetchOrganization} onSave={updateOrganization} /> )} {currentItem === "sso" && ( <OrganizationSSO orgId={orgId} connections={connections} onFetch={fetchOrganizationConnections} onDelete={deleteOrganizationConnection} onConfigure={startSelfServiceConfiguration} onUpdateConfiguration={startSelfServiceConnectionUpdate} /> )} </div> </div> </div> </div> ); }

Update the import paths to match your project setup.

React Hooks

useOrganizations

The hook can be used to create or update an organization.

"use client"; import { useCallback } from "react"; interface KeyValueMap { [key: string]: any; } type OrganizationCreationResponse = { name: string; display_name?: string; branding?: { logo_url?: string; colors?: { primary: string; page_background: string; }; }; metadata?: { [key: string]: any; }; enabled_connections?: Array<{ connection_id: string; assign_membership_on_login?: boolean; show_as_button?: boolean; }>; id: string; }; export default function useOrganizations() { const createOrganization = useCallback( async ( values: any ): Promise<{ organization?: OrganizationCreationResponse; status: number; }> => { try { /** * '/api/auth/orgs' is a custom endpoint which will proxy * the request to the Auth0 Management API. * * Proxy sample at: https://components.lab.auth0.com/docs/components/organization-creator#nextjs-routers */ const response = await fetch("/api/auth/orgs", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ name: values.organization_name }), }); // TODO: Better handling rate limits if (response.status === 429) { return { status: 429 }; } const organization: OrganizationCreationResponse = await response.json(); return { organization, status: response.status, }; } catch (error) { console.error(error); return { status: 500 }; } }, [] ); const fetchOrganization = useCallback( async (id: string): Promise<KeyValueMap> => { try { /** * '/api/auth/orgs/{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/organization-metadata#nextjs-routers */ const response = await fetch(`/api/auth/orgs/${id}`, { method: "GET", headers: { "Content-Type": "application/json", }, }); // TODO: Better handling rate limits if (response.status === 429) { return { status: 429 }; } const organization: KeyValueMap = await response.json(); return { organization, status: response.status, }; } catch (error) { console.error(error); return { status: 500 }; } }, [] ); const updateOrganization = useCallback( async ( id: string, values: KeyValueMap ): Promise<{ organization?: KeyValueMap; status: number; }> => { try { /** * '/api/auth/orgs/{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/organization-metadata#nextjs-routers */ const response = await fetch(`/api/auth/orgs/${id}`, { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify(values), }); // TODO: Better handling rate limits if (response.status === 429) { return { status: 429 }; } const organization: KeyValueMap = await response.json(); return { organization, status: response.status }; } catch (e) { console.error(e); return { status: 500 }; } }, [] ); const startSelfServiceConfiguration = useCallback( async ( values: KeyValueMap ): Promise<{ selfService?: KeyValueMap; status: number; }> => { try { /** * '/api/auth/orgs/sso' is a custom endpoint which will proxy * the request to the Auth0 Management API. * * Proxy sample at: https://components.lab.auth0.com/docs/components/organization-sso#nextjs-routers */ const response = await fetch("/api/auth/orgs/sso", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ organizations_to_enable: values.organizations_to_enable, }), }); // TODO: Better handling rate limits if (response.status === 429) { return { status: 429 }; } const selfService = await response.json(); return { selfService, status: response.status, }; } catch (error) { console.error(error); return { status: 500 }; } }, [] ); const fetchOrganizationConnections = useCallback( async (id: string): Promise<KeyValueMap> => { try { /** * '/api/auth/orgs/{id}/connections' is a custom endpoint which will proxy * the request to the Auth0 Management API. * * Proxy sample at: https://components.lab.auth0.com/docs/components/organization-sso#nextjs-routers */ const response = await fetch(`/api/auth/orgs/${id}/connections`, { method: "GET", headers: { "Content-Type": "application/json", }, }); // TODO: Better handling rate limits if (response.status === 429) { return { status: 429 }; } const connections: KeyValueMap[] = await response.json(); return { connections, status: response.status, }; } catch (error) { console.error(error); return { status: 500 }; } }, [] ); const startSelfServiceConnectionUpdate = useCallback( async ( values: KeyValueMap ): Promise<{ selfService?: KeyValueMap; status: number; }> => { try { /** * '/api/auth/orgs/sso' is a custom endpoint which will proxy * the request to the Auth0 Management API. * * Proxy sample at: https://components.lab.auth0.com/docs/components/organization-metadata#nextjs-routers */ const response = await fetch(`/api/auth/orgs/sso`, { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify(values), }); // TODO: Better handling rate limits if (response.status === 429) { return { status: 429 }; } const selfService: KeyValueMap = await response.json(); return { selfService, status: response.status }; } catch (e) { console.error(e); return { status: 500 }; } }, [] ); const deleteOrganizationConnection = useCallback( async (id: string, connection_id: string): Promise<KeyValueMap> => { try { /** * '/api/auth/orgs/{id}/connections/{connection_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/organization-sso#nextjs-routers */ const response = await fetch( `/api/auth/orgs/${id}/connections/${connection_id}`, { method: "DELETE", headers: { "Content-Type": "application/json", }, } ); // TODO: Better handling rate limits if (response.status === 429) { return { status: 429 }; } const connection: KeyValueMap[] = await response.json(); return { connection, status: response.status, }; } catch (error) { console.error(error); return { status: 500 }; } }, [] ); return { createOrganization, fetchOrganization, updateOrganization, fetchOrganizationConnections, deleteOrganizationConnection, startSelfServiceConfiguration, startSelfServiceConnectionUpdate, }; }

NextJS routers

Organizations router

The route can be used to create or update an organization.

import { AuthenticationClient, ManagementClient, PostOrganizationsRequest, } from "auth0"; import { NextRequest, 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!, }); const authClient = new AuthenticationClient({ domain: new URL(process.env.AUTH0_ISSUER_BASE_URL!).host, clientId: process.env.AUTH0_CLIENT_ID_MGMT!, clientSecret: process.env.AUTH0_CLIENT_SECRET_MGMT!, }); type HandleOrganizationCreationParams = Pick< PostOrganizationsRequest, "enabled_connections" >; type SelfServiceParams = { clients_to_enable: string[]; }; /** * @example * * export const POST = handleOrganizationCreation({ * enabled_connections: [{ * connection_id: process.env.ORGANIZATIONS_ENABLED_CONNECTION!, * assign_membership_on_login: false, * }] * }); */ // TODO: better error handling export function handleOrganizationCreation( params?: HandleOrganizationCreationParams ) { return withRateLimit( withApiAuthRequired(async (request: Request): Promise<NextResponse> => { try { const session = await getSession(); const userId = session?.user.sub; const body: PostOrganizationsRequest = await request.json(); const postOrganization: PostOrganizationsRequest = { name: body.name, }; if (params && params.enabled_connections) { postOrganization.enabled_connections = params.enabled_connections; } // Create organization const { data: organization } = await client.organizations.create( postOrganization ); // Add current user to new organization await client.organizations.addMembers( { id: organization.id }, { members: [userId] } ); return NextResponse.json( { id: organization.id, name: organization.name, display_name: organization.display_name, }, { status: 200, } ); } catch (error) { console.error(error); return NextResponse.json( { error: "Error creating organization" }, { status: 500 } ); } }) ); } /** * @example export const GET = handleFetchOrganization(); */ export function handleFetchOrganization() { return withRateLimit( withApiAuthRequired( async (request: NextRequest, { params }): Promise<NextResponse> => { try { const org_id = params?.id as string; const response = await client.organizations.get({ id: org_id }); const { data } = response; const org = { id: data.id, name: data.name, display_name: data.display_name, branding: data.branding || { logo_url: `https://cdn.auth0.com/avatars/c.png`, colors: {}, }, metadata: data.metadata || {}, }; return NextResponse.json(org, { status: response.status, }); } catch (error) { console.error(error); return NextResponse.json( { error: "Error fetching user metadata" }, { status: 500 } ); } } ) ); } /** * @example export const PUT = handleOrganizationUpdate(); */ // TODO: better error handling export function handleOrganizationUpdate() { return withRateLimit( withApiAuthRequired( async (request: Request, { params }): Promise<NextResponse> => { try { const org_id = params?.id as string; const org = await request.json(); await client.organizations.update({ id: org_id }, org); return NextResponse.json(org, { status: 200, }); } catch (error) { console.error(error); return NextResponse.json( { error: "Error updating organization" }, { status: 500 } ); } } ) ); } /** * @example export const POST = handleSelfService(); */ export function handleSelfService(params: SelfServiceParams) { return withRateLimit( withApiAuthRequired(async (request: NextRequest): Promise<NextResponse> => { try { const body = await request.json(); const { data: tokens } = await authClient.oauth.clientCredentialsGrant({ audience: `${process.env.AUTH0_ISSUER_BASE_URL}/api/v2/`, }); const $selfServiceProfile = await fetch( `${process.env.AUTH0_ISSUER_BASE_URL}/api/v2/self-service-profiles`, { headers: { Authorization: `Bearer ${tokens.access_token}`, }, } ); const profile = (await $selfServiceProfile.json()).pop(); const randomId = crypto.getRandomValues(new Uint32Array(4)).join("-"); const $ticket = await fetch( `${process.env.AUTH0_ISSUER_BASE_URL}/api/v2/self-service-profiles/${profile.id}/sso-ticket`, { method: "POST", headers: { Authorization: `Bearer ${tokens.access_token}`, "Content-Type": "application/json", }, body: JSON.stringify({ connection_config: { name: `${randomId}`, }, enabled_clients: params.clients_to_enable, enabled_organizations: body.organizations_to_enable.map( (organization_id: string) => ({ organization_id }) ), }), } ); const data = await $ticket.json(); return NextResponse.json(data, { status: 200, }); } catch (error) { console.error(error); return NextResponse.json( { error: "Error fetching user metadata" }, { status: 500 } ); } }) ); } /** * @example export const PUT = handleSelfService(); */ export function handleConnectionUpdate(params: SelfServiceParams) { return withRateLimit( withApiAuthRequired(async (request: NextRequest): Promise<NextResponse> => { try { const body = await request.json(); const { data: tokens } = await authClient.oauth.clientCredentialsGrant({ audience: `${process.env.AUTH0_ISSUER_BASE_URL}/api/v2/`, }); const $selfServiceProfile = await fetch( `${process.env.AUTH0_ISSUER_BASE_URL}/api/v2/self-service-profiles`, { headers: { Authorization: `Bearer ${tokens.access_token}`, }, } ); const profile = (await $selfServiceProfile.json()).pop(); const $ticket = await fetch( `${process.env.AUTH0_ISSUER_BASE_URL}/api/v2/self-service-profiles/${profile.id}/sso-ticket`, { method: "POST", headers: { Authorization: `Bearer ${tokens.access_token}`, "Content-Type": "application/json", }, body: JSON.stringify({ connection_id: body.connection_id, }), } ); const data = await $ticket.json(); return NextResponse.json(data, { status: 200, }); } catch (error) { console.error(error); return NextResponse.json( { error: "Error fetching user metadata" }, { status: 500 } ); } }) ); } /** * @example export const GET = handleFetchEnabledConnections(); */ export function handleFetchEnabledConnections() { return withRateLimit( withApiAuthRequired( async (request: NextRequest, { params }): Promise<NextResponse> => { try { const org_id = params?.id as string; const response = await client.organizations.getEnabledConnections({ id: org_id, }); const { data } = response; return NextResponse.json( data.filter((d) => // Note: Only strategies support on self-service. ["oidc", "okta", "samlp", "waad", "google-apps", "adfs"].includes( d.connection.strategy ) ), { status: response.status, } ); } catch (error) { console.error(error); return NextResponse.json( { error: "Error fetching user metadata" }, { status: 500 } ); } } ) ); } /** * @example export const DELETE = handleDeleteConnection(); */ export function handleDeleteConnection() { return withRateLimit( withApiAuthRequired( async (request: NextRequest, { params }): Promise<NextResponse> => { try { const org_id = params?.id as string; const connection_id = params?.connection_id as string; await client.organizations.deleteEnabledConnection({ id: org_id, connectionId: connection_id, }); await client.connections.delete({ id: connection_id }); return NextResponse.json( {}, { status: 200, } ); } catch (error) { console.error(error); return NextResponse.json( { error: "Error deleting connection" }, { status: 500 } ); } } ) ); }