|
67 | 67 | - [Troubleshooting](#troubleshooting) |
68 | 68 | - [Common Issues](#common-issues) |
69 | 69 | - [Debug Logging](#debug-logging) |
| 70 | +- [Proxy Handler for My Account and My Organization APIs](#proxy-handler-for-my-account-and-my-organization-apis) |
| 71 | + - [Overview](#overview) |
| 72 | + - [How It Works](#how-it-works) |
| 73 | + - [My Account API Proxy](#my-account-api-proxy) |
| 74 | + - [Configuration](#configuration) |
| 75 | + - [Client-Side Usage](#client-side-usage) |
| 76 | + - [`scope` Header](#scope-header) |
| 77 | + - [My Organization API Proxy](#my-organization-api-proxy) |
| 78 | + - [Configuration](#configuration-1) |
| 79 | + - [Client-Side Usage](#client-side-usage-1) |
| 80 | + - [Integration with UI Components](#integration-with-ui-components) |
| 81 | + - [HTTP Methods](#http-methods) |
| 82 | + - [CORS Handling](#cors-handling) |
| 83 | + - [Error Handling](#error-handling-1) |
| 84 | + - [Token Management](#token-management) |
| 85 | + - [Security Considerations](#security-considerations) |
| 86 | + - [Debugging](#debugging) |
70 | 87 | - [`<Auth0Provider />`](#auth0provider-) |
71 | 88 | - [Passing an initial user from the server](#passing-an-initial-user-from-the-server) |
72 | 89 | - [Hooks](#hooks) |
@@ -1550,6 +1567,341 @@ const fetcher = await auth0.createFetcher(req, { |
1550 | 1567 | }); |
1551 | 1568 | ``` |
1552 | 1569 |
|
| 1570 | +## Proxy Handler for My Account and My Organization APIs |
| 1571 | + |
| 1572 | +The SDK provides built-in proxy handler support for Auth0's My Account and My Organization Management APIs. This enables browser-initiated requests to these APIs while maintaining server-side DPoP authentication and token management. |
| 1573 | + |
| 1574 | +### Overview |
| 1575 | + |
| 1576 | +The proxy handler implements a Backend-for-Frontend (BFF) pattern that transparently forwards client requests to Auth0 APIs through the Next.js server. This architecture ensures: |
| 1577 | + |
| 1578 | +- DPoP private keys and tokens remain on the server, inaccessible to client-side JavaScript |
| 1579 | +- Automatic token retrieval and refresh based on requested audience and scope |
| 1580 | +- DPoP proof generation for each proxied request |
| 1581 | +- Session updates when tokens are refreshed |
| 1582 | +- Proper CORS handling for cross-origin requests |
| 1583 | + |
| 1584 | +The proxy handler is automatically enabled when using the SDK's middleware and requires no additional configuration. |
| 1585 | + |
| 1586 | +### How It Works |
| 1587 | + |
| 1588 | +When a client makes a request to `/me/*` or `/my-org/*` on your Next.js application: |
| 1589 | + |
| 1590 | +1. The SDK's middleware intercepts the request |
| 1591 | +2. Validates the user's session exists |
| 1592 | +3. Retrieves or refreshes the appropriate access token for the requested audience |
| 1593 | +4. Generates DPoP proof if DPoP is enabled |
| 1594 | +5. Forwards the request to the upstream Auth0 API with proper authentication headers |
| 1595 | +6. Returns the response to the client |
| 1596 | +7. Updates the session if tokens were refreshed |
| 1597 | + |
| 1598 | +### My Account API Proxy |
| 1599 | + |
| 1600 | +The My Account API proxy handles all requests to Auth0's My Account API at `/me/v1/*`. |
| 1601 | + |
| 1602 | +#### Configuration |
| 1603 | + |
| 1604 | +Enable My Account API access by configuring the audience and scopes: |
| 1605 | + |
| 1606 | +```ts |
| 1607 | +import { Auth0Client } from "@auth0/nextjs-auth0/server"; |
| 1608 | + |
| 1609 | +export const auth0 = new Auth0Client({ |
| 1610 | + useDPoP: true, |
| 1611 | + authorizationParameters: { |
| 1612 | + audience: "urn:your-api-identifier", |
| 1613 | + scope: { |
| 1614 | + [`https://${process.env.AUTH0_DOMAIN}/me/`]: "profile:read profile:write factors:manage" |
| 1615 | + } |
| 1616 | + } |
| 1617 | +}); |
| 1618 | +``` |
| 1619 | + |
| 1620 | +#### Client-Side Usage |
| 1621 | + |
| 1622 | +Make requests to the My Account API through the `/me/*` path: |
| 1623 | + |
| 1624 | +```tsx |
| 1625 | +"use client"; |
| 1626 | + |
| 1627 | +import { useState } from "react"; |
| 1628 | + |
| 1629 | +export default function MyAccountProfile() { |
| 1630 | + const [profile, setProfile] = useState(null); |
| 1631 | + const [loading, setLoading] = useState(false); |
| 1632 | + |
| 1633 | + const fetchProfile = async () => { |
| 1634 | + setLoading(true); |
| 1635 | + try { |
| 1636 | + const response = await fetch("/me/v1/profile", { |
| 1637 | + method: "GET", |
| 1638 | + headers: { |
| 1639 | + "scope": "profile:read" |
| 1640 | + } |
| 1641 | + }); |
| 1642 | + |
| 1643 | + if (!response.ok) { |
| 1644 | + throw new Error(`HTTP ${response.status}: ${response.statusText}`); |
| 1645 | + } |
| 1646 | + |
| 1647 | + const data = await response.json(); |
| 1648 | + setProfile(data); |
| 1649 | + } catch (error) { |
| 1650 | + console.error("Failed to fetch profile:", error); |
| 1651 | + } finally { |
| 1652 | + setLoading(false); |
| 1653 | + } |
| 1654 | + }; |
| 1655 | + |
| 1656 | + const updateProfile = async (updates) => { |
| 1657 | + try { |
| 1658 | + const response = await fetch("/me/v1/profile", { |
| 1659 | + method: "PATCH", |
| 1660 | + headers: { |
| 1661 | + "content-type": "application/json", |
| 1662 | + "scope": "profile:write" |
| 1663 | + }, |
| 1664 | + body: JSON.stringify(updates) |
| 1665 | + }); |
| 1666 | + |
| 1667 | + if (!response.ok) { |
| 1668 | + throw new Error(`HTTP ${response.status}: ${response.statusText}`); |
| 1669 | + } |
| 1670 | + |
| 1671 | + return await response.json(); |
| 1672 | + } catch (error) { |
| 1673 | + console.error("Failed to update profile:", error); |
| 1674 | + throw error; |
| 1675 | + } |
| 1676 | + }; |
| 1677 | + |
| 1678 | + return ( |
| 1679 | + <div> |
| 1680 | + <button onClick={fetchProfile} disabled={loading}> |
| 1681 | + {loading ? "Loading..." : "Load Profile"} |
| 1682 | + </button> |
| 1683 | + {profile && ( |
| 1684 | + <pre>{JSON.stringify(profile, null, 2)}</pre> |
| 1685 | + )} |
| 1686 | + </div> |
| 1687 | + ); |
| 1688 | +} |
| 1689 | +``` |
| 1690 | + |
| 1691 | +#### `scope` Header |
| 1692 | + |
| 1693 | +The `scope` header specifies the scope required for the request. The SDK uses this to retrieve an access token with the appropriate scope for the My Account API audience. |
| 1694 | + |
| 1695 | +Format: `"scope": "scope1 scope2 scope3"` |
| 1696 | + |
| 1697 | +Common scopes for My Account API: |
| 1698 | +- `profile:read` - Read user profile information |
| 1699 | +- `profile:write` - Update user profile information |
| 1700 | +- `factors:read` - Read enrolled MFA factors |
| 1701 | +- `factors:manage` - Manage MFA factors |
| 1702 | +- `identities:read` - Read linked identities |
| 1703 | +- `identities:manage` - Link and unlink identities |
| 1704 | + |
| 1705 | +### My Organization API Proxy |
| 1706 | + |
| 1707 | +The My Organization API proxy handles all requests to Auth0's My Organization Management API at `/my-org/*`. |
| 1708 | + |
| 1709 | +#### Configuration |
| 1710 | + |
| 1711 | +Enable My Organization API access by configuring the audience and scopes: |
| 1712 | + |
| 1713 | +```ts |
| 1714 | +import { Auth0Client } from "@auth0/nextjs-auth0/server"; |
| 1715 | + |
| 1716 | +export const auth0 = new Auth0Client({ |
| 1717 | + useDPoP: true, |
| 1718 | + authorizationParameters: { |
| 1719 | + audience: "urn:your-api-identifier", |
| 1720 | + scope: { |
| 1721 | + [`https://${process.env.AUTH0_DOMAIN}/my-org/`]: "org:read org:write members:read" |
| 1722 | + } |
| 1723 | + } |
| 1724 | +}); |
| 1725 | +``` |
| 1726 | + |
| 1727 | +#### Client-Side Usage |
| 1728 | + |
| 1729 | +Make requests to the My Organization API through the `/my-org/*` path: |
| 1730 | + |
| 1731 | +```tsx |
| 1732 | +"use client"; |
| 1733 | + |
| 1734 | +import { useState, useEffect } from "react"; |
| 1735 | + |
| 1736 | +export default function MyOrganization() { |
| 1737 | + const [organizations, setOrganizations] = useState([]); |
| 1738 | + const [loading, setLoading] = useState(true); |
| 1739 | + |
| 1740 | + useEffect(() => { |
| 1741 | + fetchOrganizations(); |
| 1742 | + }, []); |
| 1743 | + |
| 1744 | + const fetchOrganizations = async () => { |
| 1745 | + setLoading(true); |
| 1746 | + try { |
| 1747 | + const response = await fetch("/my-org/organizations", { |
| 1748 | + method: "GET", |
| 1749 | + headers: { |
| 1750 | + "scope": "org:read" |
| 1751 | + } |
| 1752 | + }); |
| 1753 | + |
| 1754 | + if (!response.ok) { |
| 1755 | + throw new Error(`HTTP ${response.status}: ${response.statusText}`); |
| 1756 | + } |
| 1757 | + |
| 1758 | + const data = await response.json(); |
| 1759 | + setOrganizations(data.organizations || []); |
| 1760 | + } catch (error) { |
| 1761 | + console.error("Failed to fetch organizations:", error); |
| 1762 | + } finally { |
| 1763 | + setLoading(false); |
| 1764 | + } |
| 1765 | + }; |
| 1766 | + |
| 1767 | + const updateOrganization = async (orgId, updates) => { |
| 1768 | + try { |
| 1769 | + const response = await fetch(`/my-org/organizations/${orgId}`, { |
| 1770 | + method: "PATCH", |
| 1771 | + headers: { |
| 1772 | + "content-type": "application/json", |
| 1773 | + "scope": "org:write" |
| 1774 | + }, |
| 1775 | + body: JSON.stringify(updates) |
| 1776 | + }); |
| 1777 | + |
| 1778 | + if (!response.ok) { |
| 1779 | + throw new Error(`HTTP ${response.status}: ${response.statusText}`); |
| 1780 | + } |
| 1781 | + |
| 1782 | + return await response.json(); |
| 1783 | + } catch (error) { |
| 1784 | + console.error("Failed to update organization:", error); |
| 1785 | + throw error; |
| 1786 | + } |
| 1787 | + }; |
| 1788 | + |
| 1789 | + if (loading) return <div>Loading organizations...</div>; |
| 1790 | + |
| 1791 | + return ( |
| 1792 | + <div> |
| 1793 | + <h1>My Organizations</h1> |
| 1794 | + <ul> |
| 1795 | + {organizations.map((org) => ( |
| 1796 | + <li key={org.id}>{org.display_name}</li> |
| 1797 | + ))} |
| 1798 | + </ul> |
| 1799 | + </div> |
| 1800 | + ); |
| 1801 | +} |
| 1802 | +``` |
| 1803 | + |
| 1804 | +Common scopes for My Organization API: |
| 1805 | +- `org:read` - Read organization information |
| 1806 | +- `org:write` - Update organization information |
| 1807 | +- `members:read` - Read organization members |
| 1808 | +- `members:manage` - Manage organization members |
| 1809 | +- `roles:read` - Read organization roles |
| 1810 | +- `roles:manage` - Manage organization roles |
| 1811 | + |
| 1812 | +### Integration with UI Components |
| 1813 | + |
| 1814 | +When using Auth0 UI Components with the proxy handler, configure the client to target the proxy endpoints: |
| 1815 | + |
| 1816 | +```tsx |
| 1817 | +import { MyAccountClient } from "@auth0/my-account-js"; |
| 1818 | + |
| 1819 | +const myAccountClient = new MyAccountClient({ |
| 1820 | + domain: process.env.NEXT_PUBLIC_AUTH0_DOMAIN, |
| 1821 | + baseUrl: "/me", |
| 1822 | + fetcher: (url, init, authParams) => { |
| 1823 | + return fetch(url, { |
| 1824 | + ...init, |
| 1825 | + headers: { |
| 1826 | + ...init?.headers, |
| 1827 | + "scope": authParams?.scope?.join(" ") || "" |
| 1828 | + } |
| 1829 | + }); |
| 1830 | + } |
| 1831 | +}); |
| 1832 | +``` |
| 1833 | + |
| 1834 | +This configuration: |
| 1835 | +- Sets `baseUrl` to `/me` to route requests through the proxy |
| 1836 | +- Passes the required scope via the `scope` header |
| 1837 | +- Ensures the SDK middleware handles authentication transparently |
| 1838 | + |
| 1839 | +### HTTP Methods |
| 1840 | + |
| 1841 | +The proxy handler supports all standard HTTP methods: |
| 1842 | + |
| 1843 | +- `GET` - Retrieve resources |
| 1844 | +- `POST` - Create resources |
| 1845 | +- `PUT` - Replace resources |
| 1846 | +- `PATCH` - Update resources |
| 1847 | +- `DELETE` - Remove resources |
| 1848 | +- `OPTIONS` - CORS preflight requests (handled without authentication) |
| 1849 | +- `HEAD` - Retrieve headers only |
| 1850 | + |
| 1851 | +### CORS Handling |
| 1852 | + |
| 1853 | +The proxy handler correctly handles CORS preflight requests (OPTIONS with `access-control-request-method` header) by forwarding them to the upstream API without authentication headers, as required by RFC 7231 §4.3.1. |
| 1854 | + |
| 1855 | +CORS headers from the upstream API are forwarded to the client transparently. |
| 1856 | + |
| 1857 | +### Error Handling |
| 1858 | + |
| 1859 | +The proxy handler returns appropriate HTTP status codes: |
| 1860 | + |
| 1861 | +- `401 Unauthorized` - No active session or token refresh failed |
| 1862 | +- `4xx Client Error` - Forwarded from upstream API |
| 1863 | +- `5xx Server Error` - Forwarded from upstream API or proxy internal error |
| 1864 | + |
| 1865 | +Error responses from the upstream API are forwarded to the client with their original status code, headers, and body. |
| 1866 | + |
| 1867 | +### Token Management |
| 1868 | + |
| 1869 | +The proxy handler automatically: |
| 1870 | + |
| 1871 | +- Retrieves access tokens from the session for the requested audience |
| 1872 | +- Refreshes expired tokens using the refresh token |
| 1873 | +- Updates the session with new tokens after refresh |
| 1874 | +- Caches tokens per audience to minimize token endpoint calls |
| 1875 | +- Generates DPoP proofs for each request when DPoP is enabled |
| 1876 | + |
| 1877 | +### Security Considerations |
| 1878 | + |
| 1879 | +The proxy handler implements secure forwarding: |
| 1880 | + |
| 1881 | +- HTTP-only session cookies are not forwarded to upstream APIs |
| 1882 | +- Authorization headers from the client are replaced with server-generated tokens |
| 1883 | +- Hop-by-hop headers are stripped per RFC 2616 §13.5.1 |
| 1884 | +- Only allow-listed request headers are forwarded |
| 1885 | +- Response headers are filtered before returning to the client |
| 1886 | +- Host header is updated to match the upstream API |
| 1887 | + |
| 1888 | +### Debugging |
| 1889 | + |
| 1890 | +Enable debug logging to troubleshoot proxy requests: |
| 1891 | + |
| 1892 | +```ts |
| 1893 | +export const auth0 = new Auth0Client({ |
| 1894 | + // ... other config |
| 1895 | + enableDebugLogs: true |
| 1896 | +}); |
| 1897 | +``` |
| 1898 | + |
| 1899 | +This will log: |
| 1900 | +- Request proxying flow |
| 1901 | +- Token retrieval and refresh operations |
| 1902 | +- DPoP proof generation |
| 1903 | +- Session updates |
| 1904 | +- Errors and warnings |
1553 | 1905 |
|
1554 | 1906 | ## `<Auth0Provider />` |
1555 | 1907 |
|
|
0 commit comments