From fa687468779ad939be2f984f120678488bd8a858 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Fri, 15 Dec 2023 16:57:56 -0600 Subject: [PATCH] feat(remix): Support handshake flow for remix (#2380) * feat(remix): Support handshake flow for remix * chore(repo): Add changeset * feat(remix): Support getAuth as well, consolidate handshake handling into authenticateRequest * chore(remix): Add comment --- .changeset/itchy-chairs-call.md | 16 ++++++++++ packages/backend/src/internal.ts | 2 +- packages/nextjs/src/server/authMiddleware.ts | 2 +- packages/remix/src/ssr/authenticateRequest.ts | 30 ++++++++++++++++--- packages/remix/src/ssr/getAuth.ts | 11 ++----- packages/remix/src/ssr/rootAuthLoader.ts | 9 ++---- 6 files changed, 49 insertions(+), 21 deletions(-) create mode 100644 .changeset/itchy-chairs-call.md diff --git a/.changeset/itchy-chairs-call.md b/.changeset/itchy-chairs-call.md new file mode 100644 index 0000000000..dc5160a764 --- /dev/null +++ b/.changeset/itchy-chairs-call.md @@ -0,0 +1,16 @@ +--- +'@clerk/remix': major +--- + +Update `@clerk/remix`'s `rootAuthLoader` and `getAuth` helpers to handle handshake auth status, this replaces the previous interstitial flow. As a result of this, the `ClerkErrorBoundary` is no longer necessary and has been removed. + +To migrate, remove usage of `ClerkErrorBoundary`: + +```diff +- import { ClerkApp, ClerkErrorBoundary } from "@clerk/remix"; ++ import { ClerkApp } from "@clerk/remix"; + +... + +- export const ErrorBoundary = ClerkErrorBoundary(); +``` diff --git a/packages/backend/src/internal.ts b/packages/backend/src/internal.ts index 7327bfe5a2..883e87d4d4 100644 --- a/packages/backend/src/internal.ts +++ b/packages/backend/src/internal.ts @@ -25,4 +25,4 @@ export { export { createIsomorphicRequest } from './util/IsomorphicRequest'; export { AuthStatus } from './tokens/authStatus'; -export type { RequestState } from './tokens/authStatus'; +export type { RequestState, SignedInState, SignedOutState } from './tokens/authStatus'; diff --git a/packages/nextjs/src/server/authMiddleware.ts b/packages/nextjs/src/server/authMiddleware.ts index ff075b3240..d29b71f22a 100644 --- a/packages/nextjs/src/server/authMiddleware.ts +++ b/packages/nextjs/src/server/authMiddleware.ts @@ -207,7 +207,7 @@ const authMiddleware: AuthMiddleware = (...args: unknown[]) => { } if (requestState.status === AuthStatus.Handshake) { - throw new Error('Unexpected handshake without redirect'); + throw new Error('Clerk: unexpected handshake without redirect'); } const auth = Object.assign(requestState.toAuth(), { diff --git a/packages/remix/src/ssr/authenticateRequest.ts b/packages/remix/src/ssr/authenticateRequest.ts index 1bf35a9624..48efe37a9e 100644 --- a/packages/remix/src/ssr/authenticateRequest.ts +++ b/packages/remix/src/ssr/authenticateRequest.ts @@ -1,6 +1,6 @@ import { createClerkClient } from '@clerk/backend'; -import type { RequestState } from '@clerk/backend/internal'; -import { buildRequestUrl } from '@clerk/backend/internal'; +import type { SignedInState, SignedOutState } from '@clerk/backend/internal'; +import { AuthStatus, buildRequestUrl } from '@clerk/backend/internal'; import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey'; import { handleValueOrFn } from '@clerk/shared/handleValueOrFn'; import { isDevelopmentFromSecretKey } from '@clerk/shared/keys'; @@ -11,7 +11,10 @@ import { noSecretKeyError, satelliteAndMissingProxyUrlAndDomain, satelliteAndMis import { getEnvVariable } from '../utils'; import type { LoaderFunctionArgs, RootAuthLoaderOptions } from './types'; -export function authenticateRequest(args: LoaderFunctionArgs, opts: RootAuthLoaderOptions = {}): Promise { +export async function authenticateRequest( + args: LoaderFunctionArgs, + opts: RootAuthLoaderOptions = {}, +): Promise { const { request, context } = args; const { loadSession, loadUser, loadOrganization } = opts; const { audience, authorizedParties } = opts; @@ -71,7 +74,14 @@ export function authenticateRequest(args: LoaderFunctionArgs, opts: RootAuthLoad throw new Error(satelliteAndMissingSignInUrl); } - return createClerkClient({ apiUrl, secretKey, jwtKey, proxyUrl, isSatellite, domain }).authenticateRequest(request, { + const requestState = await createClerkClient({ + apiUrl, + secretKey, + jwtKey, + proxyUrl, + isSatellite, + domain, + }).authenticateRequest(request, { audience, secretKey, jwtKey, @@ -88,4 +98,16 @@ export function authenticateRequest(args: LoaderFunctionArgs, opts: RootAuthLoad afterSignInUrl, afterSignUpUrl, }); + + const hasLocationHeader = requestState.headers.get('location'); + if (hasLocationHeader) { + // triggering a handshake redirect + throw new Response(null, { status: 307, headers: requestState.headers }); + } + + if (requestState.status === AuthStatus.Handshake) { + throw new Error('Clerk: unexpected handshake without redirect'); + } + + return requestState; } diff --git a/packages/remix/src/ssr/getAuth.ts b/packages/remix/src/ssr/getAuth.ts index c13836b8c3..8558e0722e 100644 --- a/packages/remix/src/ssr/getAuth.ts +++ b/packages/remix/src/ssr/getAuth.ts @@ -1,5 +1,4 @@ -import { AuthStatus, sanitizeAuthObject } from '@clerk/backend/internal'; -import { redirect } from '@remix-run/server-runtime'; +import { sanitizeAuthObject } from '@clerk/backend/internal'; import { noLoaderArgsPassedInGetAuth } from '../errors'; import { authenticateRequest } from './authenticateRequest'; @@ -11,13 +10,9 @@ export async function getAuth(args: LoaderFunctionArgs, opts?: GetAuthOptions): if (!args || (args && (!args.request || !args.context))) { throw new Error(noLoaderArgsPassedInGetAuth); } - const requestState = await authenticateRequest(args, opts); - // TODO handle handshake - // this halts the execution of all nested loaders using getAuth - if (requestState.status === AuthStatus.Handshake) { - throw redirect(''); - } + // Note: authenticateRequest() will throw a redirect if the auth state is determined to be handshake + const requestState = await authenticateRequest(args, opts); return sanitizeAuthObject(requestState.toAuth()); } diff --git a/packages/remix/src/ssr/rootAuthLoader.ts b/packages/remix/src/ssr/rootAuthLoader.ts index 0e2753a2e0..bbb38597f3 100644 --- a/packages/remix/src/ssr/rootAuthLoader.ts +++ b/packages/remix/src/ssr/rootAuthLoader.ts @@ -1,6 +1,5 @@ -import { AuthStatus, sanitizeAuthObject } from '@clerk/backend/internal'; +import { sanitizeAuthObject } from '@clerk/backend/internal'; import type { defer } from '@remix-run/server-runtime'; -import { redirect } from '@remix-run/server-runtime'; import { isDeferredData } from '@remix-run/server-runtime/dist/responses'; import { invalidRootLoaderCallbackReturn } from '../errors'; @@ -48,13 +47,9 @@ export const rootAuthLoader: RootAuthLoader = async ( ? handlerOrOptions : {}; + // Note: authenticateRequest() will throw a redirect if the auth state is determined to be handshake const requestState = await authenticateRequest(args, opts); - // TODO handle handshake - if (requestState.status === AuthStatus.Handshake) { - throw redirect(''); - } - if (!handler) { // if the user did not provide a handler, simply inject requestState into an empty response return injectRequestStateIntoResponse(new Response(JSON.stringify({})), requestState, args.context);