Docs
Organization Info
Experimental

Organization Info

Displays essential organization details, including name and display name, providing a quick glance at organization information.

General

Installation

Install the following dependencies:

npx shadcn-ui@latest add avatar card button form input toast label separator
pnpm install zod react-hook-form @hookform/resolvers

Copy and paste the following code into your project.

"use client"; import { useCallback, useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { Avatar, AvatarImage } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Separator } from "@/components/ui/separator"; import { useToast } from "@/components/ui/use-toast"; import { zodResolver } from "@hookform/resolvers/zod"; interface KeyValueMap { [key: string]: any; } function Spinner() { return ( <svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="mr-2 animate-spin" > <path d="M21 12a9 9 0 1 1-6.219-8.56"></path> </svg> ); } export default function OrganizationInfo({ orgId, organization, onFetch, onSave, }: { orgId: string; organization?: KeyValueMap; onFetch: (id: string) => Promise<KeyValueMap>; onSave?: ( id: string, values: KeyValueMap ) => Promise<{ organization?: KeyValueMap; status: number; }>; }) { const [fetching, setFetching] = useState(false); const [info, setInfo] = useState<KeyValueMap | undefined>(organization); const { toast } = useToast(); const [working, setWorking] = useState<boolean>(false); const schema = z.object({ name: z.string(), display_name: z.string(), branding: z .object({ logo_url: z.string().url(), }) .optional(), }); const form = useForm<z.infer<typeof schema>>({ resolver: zodResolver(schema), defaultValues: organization, }); async function onSubmit(values: z.infer<typeof schema>) { setWorking(true); if (typeof onSave === "function") { const response = await onSave(orgId, values); if (response.status !== 200) { toast({ title: "Info", description: "There was a problem updating the info. Try again later.", }); } } setWorking(false); } const handleFetching = useCallback( async function handleFetching() { setFetching(true); const response = await onFetch(orgId); if (response.status !== 200) { return setFetching(false); } setInfo(response.organization); form.reset(response.organization, { keepValues: false }); setFetching(false); }, [form, onFetch, orgId] ); useEffect(() => { (async () => { if (!info) { await handleFetching(); } })(); }, [handleFetching, info]); return ( <Card className="w-full"> <CardHeader className="p-4 md:p-6"> <CardTitle className="text-lg font-normal">General</CardTitle> <CardDescription></CardDescription> </CardHeader> {fetching && ( <CardContent className="p-4 pt-0 md:p-6 md:pt-0"> <div className="flex w-full items-center justify-left"> <Spinner /> <span className="text-sm text-muted-foreground"> Fetching info... </span> </div> </CardContent> )} {!info && !fetching && ( <CardContent className="p-4 pt-0 md:p-6 md:pt-0"> <div className="flex flex-col gap-6"> <Separator /> <div className="flex items-center justify-between space-x-2"> <Label className="flex flex-col space-y-2"> <p className="font-normal leading-snug text-muted-foreground max-w-fit"> There was a problem retrieving info. Try again later. </p> </Label> </div> </div> </CardContent> )} {info && !fetching && ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> <CardContent className="p-4 pt-0 md:p-6 md:pt-0"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid items-center gap-1.5"> <FormField control={form.control} name={"name"} render={({ field }) => ( <FormItem> <FormLabel>Name</FormLabel> <FormControl> <Input {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> </div> <div className="grid items-center gap-1.5"> <FormField control={form.control} name={"display_name"} render={({ field }) => ( <FormItem> <FormLabel>Display Name</FormLabel> <FormControl> <Input {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> </div> <div className="grid items-center gap-1.5"> <FormField control={form.control} name={"branding.logo_url"} render={({ field }) => ( <FormItem> <FormLabel>Logo URL</FormLabel> <div className="border rounded-md flex justify-center items-center"> <Avatar className="h-8 w-8 flex items-center ml-[3px] mt-[2px] mb-[2px] rounded-md"> <AvatarImage src={info.branding.logo_url} alt={info.name} /> </Avatar> <FormControl> <Input {...field} className="border-0" /> </FormControl> <FormMessage /> </div> </FormItem> )} /> </div> </div> </CardContent> <CardFooter className="p-4 pt-0 md:p-6 md:pt-0"> <Button type="submit" disabled={working} className="disabled:opacity-50 ml-auto" > {working && <Spinner />} Save </Button> </CardFooter> </form> </Form> )} </Card> ); }

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 } ); } } ) ); }