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