Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3701,6 +3701,61 @@ Common scopes for My Organization API:
- `roles:read` - Read organization roles
- `roles:manage` - Manage organization roles

### Reading Organization Permissions

Auth0 includes a `urn:auth0:my_org_current_user_permissions` claim in the ID token containing all `my_org:*` permissions the authenticated user holds in their current organization. The SDK passes this claim through to `session.user` automatically — no additional scope or `beforeSessionSaved` configuration is required.

The permissions are cached in the encrypted session cookie and refreshed only when the ID token is re-issued, so there is no extra network call on each component mount.

#### Server Component

```tsx
import { auth0 } from "@/lib/auth0";

export default async function Page() {
const session = await auth0.getSession();
const permissions: string[] =
session?.user["urn:auth0:my_org_current_user_permissions"] ?? [];

const canInvite = permissions.includes("my_org:invite_members");

return (
<div>
{canInvite && (
<button type="button">Invite Member</button>
)}
</div>
Comment thread
Piyush-85 marked this conversation as resolved.
);
}
```

#### Client Component

```tsx
"use client";

import { useUser } from "@auth0/nextjs-auth0";

export function OrgActions() {
const { user } = useUser();
const permissions: string[] =
user?.["urn:auth0:my_org_current_user_permissions"] ?? [];

const canInvite = permissions.includes("my_org:invite_members");
const canManageRoles = permissions.includes("my_org:manage_member_roles");

return (
<div>
<button disabled={!canInvite}>Invite Member</button>
<button disabled={!canManageRoles}>Manage Roles</button>
</div>
);
}
```

> [!NOTE]
> This claim is for UI gating only. The My Organization API enforces authorization server-side on every request regardless of what the claim contains.

### Integration with UI Components

When using Auth0 UI Components with the proxy handler, configure the client to target the proxy endpoints:
Expand Down Expand Up @@ -4359,6 +4414,7 @@ By default, the following properties claims from the ID token are added to the `
- `email`
- `email_verified`
- `org_id`
- `urn:auth0:my_org_current_user_permissions` — effective `my_org:*` permissions for the authenticated user in their current organization. Present when Auth0's My Organization API is enabled. Useful for permission-based UI gating (see [Reading Organization Permissions](#reading-organization-permissions)).

If you'd like to customize the `user` object to include additional custom claims from the ID token, you can use the `beforeSessionSaved` hook (see [beforeSessionSaved hook](#beforesessionsaved))

Expand Down
58 changes: 58 additions & 0 deletions src/server/user.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,64 @@ describe("filterDefaultIdTokenClaims", async () => {
});
});

it("should retain urn:auth0:my_org_current_user_permissions with populated permissions", () => {
const permissions = ["my_org:read_members", "my_org:invite_members"];
expect(
filterDefaultIdTokenClaims({
sub: "user_123",
"urn:auth0:my_org_current_user_permissions": permissions
})
).toEqual({
sub: "user_123",
"urn:auth0:my_org_current_user_permissions": permissions
});
});

it("should retain urn:auth0:my_org_current_user_permissions when the array is empty", () => {
expect(
filterDefaultIdTokenClaims({
sub: "user_123",
"urn:auth0:my_org_current_user_permissions": []
})
).toEqual({
sub: "user_123",
"urn:auth0:my_org_current_user_permissions": []
});
});

it("should retain all standard claims together with urn:auth0:my_org_current_user_permissions", () => {
const permissions = ["my_org:manage_member_roles"];
expect(
filterDefaultIdTokenClaims({
sub: "user_123",
name: "Jane",
email: "jane@example.com",
org_id: "org_456",
"urn:auth0:my_org_current_user_permissions": permissions,
iat: 1234567890,
exp: 9999999999
})
).toEqual({
sub: "user_123",
name: "Jane",
email: "jane@example.com",
org_id: "org_456",
"urn:auth0:my_org_current_user_permissions": permissions
});
});

it("should strip unrecognised custom claims while preserving standard ones", () => {
expect(
filterDefaultIdTokenClaims({
sub: "user_123",
custom_claim: "foo",
"https://example.com/role": "admin"
})
).toEqual({
sub: "user_123"
});
});

it("should return an empty object if no claims are provided", () => {
expect(filterDefaultIdTokenClaims({})).toEqual({});
});
Expand Down
3 changes: 2 additions & 1 deletion src/server/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export const DEFAULT_ID_TOKEN_CLAIMS = [
"email",
"email_verified",
"org_id",
"act"
"act",
"urn:auth0:my_org_current_user_permissions"
];

/**
Expand Down
7 changes: 7 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ export interface User {
* ceiling on the local session; reads return no-session once it passes.
*/
session_expiry?: number;
/**
* The effective `my_org:*` permissions for the authenticated user within their
* current organization. Derived from directly assigned and effective roles.
* Populated automatically from the ID token — no additional scope required.
* Applies to user tokens only (M2M flows do not produce an ID token).
*/
"urn:auth0:my_org_current_user_permissions"?: string[];

[key: string]: any;
}
Expand Down
Loading