Skip to content

Commit 94902c2

Browse files
feat: Proxy handler support enabling My Account and My Org (#2400)
2 parents 6deb16d + 6f8d7be commit 94902c2

File tree

11 files changed

+5018
-4
lines changed

11 files changed

+5018
-4
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ web_modules/
7878
.env.test.local
7979
.env.production.local
8080
.env.local
81+
.auth0-credentials
8182

8283
# parcel-bundler cache (https://parceljs.org/)
8384
.cache
@@ -132,3 +133,12 @@ dist
132133
/playwright-report/
133134
/blob-report/
134135
/playwright/.cache/
136+
137+
# local development files
138+
.memory/
139+
.dev-files/
140+
*.env.*
141+
*.tmp
142+
*PLAN*.md
143+
.yalc/
144+
yalc.lock

EXAMPLES.md

Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,23 @@
6767
- [Troubleshooting](#troubleshooting)
6868
- [Common Issues](#common-issues)
6969
- [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)
7087
- [`<Auth0Provider />`](#auth0provider-)
7188
- [Passing an initial user from the server](#passing-an-initial-user-from-the-server)
7289
- [Hooks](#hooks)
@@ -1550,6 +1567,341 @@ const fetcher = await auth0.createFetcher(req, {
15501567
});
15511568
```
15521569

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
15531905

15541906
## `<Auth0Provider />`
15551907

0 commit comments

Comments
 (0)