diff --git a/README.md b/README.md index 7da90f6..372d54e 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,6 @@ code never enters the server bundle. | --------------------------------------------------------- | -------------------------------------------------------------------- | | `@auth0/auth0-react-router` (root, resolves to `/client`) | Provider, hooks, UI components, route guards | | `@auth0/auth0-react-router/server` | `Auth0Server` class, handlers, session and token helpers, middleware | -| `@auth0/auth0-react-router/routes` | Pre-built route config objects for the full OIDC flow | | `@auth0/auth0-react-router/errors` | Typed error classes | | `@auth0/auth0-react-router/types` | TypeScript types | | `@auth0/auth0-react-router/testing` | Test utilities, mock factories, test provider | @@ -67,8 +66,8 @@ threading the instance through every call site. - Login, callback, and logout handled server-side with an encrypted JWE session cookie. - Back-channel logout so Auth0 can end a user's session from the Dashboard or another app. -- Pre-built route helpers (`auth0Routes`, `handleAuth`) to register the full OIDC flow in one line, - or individual handlers (`handleLogin`, `handleCallback`, `handleLogout`) for custom paths. +- `handleAuth` on a splat route registers the full OIDC flow from a single file, or use the + individual handlers (`handleLogin`, `handleCallback`, `handleLogout`) for custom paths. **Route protection** @@ -129,26 +128,31 @@ the constructor. **2. Register the auth routes:** +Create a splat route file that handles all `/auth/*` paths: + +```tsx +// app/routes/auth.$.tsx +import { handleAuth } from '@auth0/auth0-react-router/server'; +import { auth0 } from '../auth0.server'; + +export const loader = ({ request }) => handleAuth(auth0, request); +export const action = ({ request }) => handleAuth(auth0, request); +``` + +Register it in your route config: + ```ts // app/routes.ts -import { auth0Routes } from '@auth0/auth0-react-router/routes'; -import { auth0 } from './auth0.server'; +import { route } from '@react-router/dev/routes'; export default [ - { - id: 'root', - path: '/', - // ... - children: [ - ...auth0Routes(auth0) // registers /auth/login, /auth/callback, /auth/logout - ] - } + route('auth/*', 'routes/auth.$.tsx'), + // ... ]; ``` -`auth0Routes` is the quickest option. If you need custom paths or logic, use the individual -handlers (`handleLogin`, `handleCallback`, `handleLogout`) directly in your own route files, or use -`handleAuth` on a splat route to dispatch all three from one file. +`handleAuth` dispatches internally to `handleLogin`, `handleCallback`, `handleLogout`, and +`handleBackchannelLogout` based on the URL path and HTTP method. **3. Add the provider to the root layout:** diff --git a/__tests__/server/handlers.test.ts b/__tests__/server/handlers.test.ts index 3f30ead..9bc8960 100644 --- a/__tests__/server/handlers.test.ts +++ b/__tests__/server/handlers.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { describe, it, expect, vi } from 'vitest'; import { MissingTransactionError, BackchannelLogoutError @@ -9,7 +9,6 @@ import { handleLogout, handleBackchannelLogout, handleAuth, - auth0Routes, stripIdTokenClaims } from '../../src/server/handlers.js'; import { CallbackError } from '../../src/errors/index.js'; @@ -698,117 +697,6 @@ describe('handleBackchannelLogout', () => { }); }); -// ─── auth0Routes ────────────────────────────────────────────────────────────── - -describe('auth0Routes', () => { - let auth0: Auth0Server; - - beforeEach(() => { - auth0 = makeAuth0(); - }); - - it('returns an array of 4 route objects', () => { - const routes = auth0Routes(auth0); - expect(routes).toHaveLength(4); - }); - - it('registers /auth/login with a loader', () => { - const routes = auth0Routes(auth0); - const login = routes.find(r => r.path === '/auth/login'); - expect(login).toBeDefined(); - expect(login?.loader).toBeTypeOf('function'); - expect(login?.action).toBeUndefined(); - }); - - it('registers /auth/callback with a loader', () => { - const routes = auth0Routes(auth0); - const callback = routes.find(r => r.path === '/auth/callback'); - expect(callback).toBeDefined(); - expect(callback?.loader).toBeTypeOf('function'); - expect(callback?.action).toBeUndefined(); - }); - - it('registers /auth/logout with both an action and a loader', () => { - const routes = auth0Routes(auth0); - const logout = routes.find(r => r.path === '/auth/logout'); - expect(logout).toBeDefined(); - expect(logout?.action).toBeTypeOf('function'); - expect(logout?.loader).toBeTypeOf('function'); - }); - - it('registers /auth/backchannel-logout with both an action and a loader', () => { - const routes = auth0Routes(auth0); - const backchannelLogout = routes.find( - r => r.path === '/auth/backchannel-logout' - ); - expect(backchannelLogout).toBeDefined(); - expect(backchannelLogout?.action).toBeTypeOf('function'); - expect(backchannelLogout?.loader).toBeTypeOf('function'); - }); - - it('login loader delegates to handleLogin', async () => { - const startInteractiveLogin = vi - .fn() - .mockResolvedValue(new URL('https://test.auth0.com/authorize')); - auth0 = makeAuth0({ startInteractiveLogin }); - - const routes = auth0Routes(auth0); - const login = routes.find(r => r.path === '/auth/login')!; - - const response = await login.loader!({ - request: makeRequest('http://localhost:3000/auth/login') - }); - - expect(startInteractiveLogin).toHaveBeenCalled(); - expect(response.status).toBe(302); - }); - - it('callback loader delegates to handleCallback', async () => { - const completeInteractiveLogin = vi - .fn() - .mockResolvedValue({ appState: { returnTo: '/home' } }); - auth0 = makeAuth0({ completeInteractiveLogin }); - - const routes = auth0Routes(auth0); - const callback = routes.find(r => r.path === '/auth/callback')!; - - const response = await callback.loader!({ - request: makeRequest( - 'http://localhost:3000/auth/callback?code=abc&state=xyz' - ) - }); - - expect(completeInteractiveLogin).toHaveBeenCalled(); - expect(response.status).toBe(302); - expect(response.headers.get('Location')).toBe('/home'); - }); - - it('logout loader returns 405 for GET', async () => { - const routes = auth0Routes(auth0); - const logout = routes.find(r => r.path === '/auth/logout')!; - - const response = await logout.loader!({ - request: makeRequest('http://localhost:3000/auth/logout') - }); - - expect(response.status).toBe(405); - expect(response.headers.get('Allow')).toBe('POST'); - }); - - it('logout action rejects GET via handleLogout', async () => { - const routes = auth0Routes(auth0); - const logout = routes.find(r => r.path === '/auth/logout')!; - - const response = await logout.action!({ - request: makeRequest('http://localhost:3000/auth/logout', { - method: 'GET' - }) - }); - - expect(response.status).toBe(405); - }); -}); - // ─── handleAuth ─────────────────────────────────────────────────────────────── describe('handleAuth', () => { diff --git a/package.json b/package.json index 2ebd5ee..2778555 100644 --- a/package.json +++ b/package.json @@ -50,12 +50,6 @@ "import": "./dist/server/index.js", "require": "./dist/server/index.cjs" }, - "./routes": { - "browser": null, - "types": "./dist/routes/index.d.ts", - "import": "./dist/routes/index.js", - "require": "./dist/routes/index.cjs" - }, "./errors": { "types": "./dist/errors/index.d.ts", "import": "./dist/errors/index.js", diff --git a/src/routes/index.ts b/src/routes/index.ts index fab0993..9c120db 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -1 +1,23 @@ -export { auth0Routes } from '../server/handlers.js'; +// auth0Routes() has been removed. +// +// React Router's routes.ts only accepts file-based route configs ({ path, file }). +// Inline route objects with loader/action functions are rejected at build time. +// +// To register the Auth0 auth endpoints, create a splat route in your app: +// +// app/routes/auth.$.tsx +// ───────────────────── +// import { Auth0Server, handleAuth } from '@auth0/auth0-react-router/server'; +// +// const auth0 = new Auth0Server(); +// +// export const loader = ({ request }) => handleAuth(auth0, request); +// export const action = ({ request }) => handleAuth(auth0, request); +// +// app/routes.ts +// ───────────── +// import { route } from '@react-router/dev/routes'; +// export default [ +// route('auth/*', 'routes/auth.$.tsx'), +// ... +// ]; diff --git a/src/server/handlers.ts b/src/server/handlers.ts index d700add..57be389 100644 --- a/src/server/handlers.ts +++ b/src/server/handlers.ts @@ -352,55 +352,3 @@ export function stripIdTokenClaims(user: Auth0User): Auth0User { ) as Auth0User; } -// ─── auth0Routes ────────────────────────────────────────────────────────────── - -/** - * Returns a set of route objects for React Router that wire up the Auth0 - * endpoints automatically. Spread these into your routes config so you don't - * need to create individual route files for each auth endpoint. - * - * Routes registered: - * GET /auth/login → handleLogin - * GET /auth/callback → handleCallback - * POST /auth/logout → handleLogout - * POST /auth/backchannel-logout → handleBackchannelLogout - * - * @example - * // app/routes.ts - * import { auth0Routes } from '@auth0/auth0-react-router/server'; - * export default [...auth0Routes(auth0), ...appRoutes]; - */ -export function auth0Routes(auth0: Auth0Server): Array<{ - path: string; - loader?: (args: { request: Request }) => Promise; - action?: (args: { request: Request }) => Promise; -}> { - return [ - { - path: '/auth/login', - loader: ({ request }) => handleLogin(auth0, request) - }, - { - path: '/auth/callback', - loader: ({ request }) => handleCallback(auth0, request) - }, - { - path: '/auth/logout', - loader: async () => - new Response('Method Not Allowed', { - status: 405, - headers: { Allow: 'POST' } - }), - action: ({ request }) => handleLogout(auth0, request) - }, - { - path: '/auth/backchannel-logout', - loader: async () => - new Response('Method Not Allowed', { - status: 405, - headers: { Allow: 'POST' } - }), - action: ({ request }) => handleBackchannelLogout(auth0, request) - } - ]; -} diff --git a/src/server/index.ts b/src/server/index.ts index d92b268..8e48df7 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -13,7 +13,6 @@ export { handleLogout, handleBackchannelLogout, handleAuth, - auth0Routes, stripIdTokenClaims } from './handlers.js'; export type { diff --git a/tsup.config.ts b/tsup.config.ts index 50b5581..8c218d1 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -4,7 +4,6 @@ export default defineConfig({ entry: { index: 'src/client/index.ts', 'server/index': 'src/server/index.ts', - 'routes/index': 'src/routes/index.ts', 'errors/index': 'src/errors/index.ts', 'types/index': 'src/types/index.ts', 'testing/index': 'src/testing/index.ts'