From 36eef24a749a50e7c95907ac0401b6a0f473a86a Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Fri, 15 Dec 2023 16:49:54 -0500 Subject: [PATCH 1/4] feat(remix): Support handshake flow for remix --- packages/nextjs/src/server/authMiddleware.ts | 2 +- packages/remix/src/ssr/rootAuthLoader.ts | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/nextjs/src/server/authMiddleware.ts b/packages/nextjs/src/server/authMiddleware.ts index ddabf813a4..faaf0e4ec1 100644 --- a/packages/nextjs/src/server/authMiddleware.ts +++ b/packages/nextjs/src/server/authMiddleware.ts @@ -208,7 +208,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/rootAuthLoader.ts b/packages/remix/src/ssr/rootAuthLoader.ts index 0e2753a2e0..aecb728e2b 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 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'; @@ -50,9 +49,14 @@ export const rootAuthLoader: RootAuthLoader = async ( const requestState = await authenticateRequest(args, opts); - // TODO handle handshake + const hasLocationHeader = requestState.headers.get('location'); + if (hasLocationHeader) { + // triggering a handshake redirect + return new Response(null, { status: 307, headers: requestState.headers }); + } + if (requestState.status === AuthStatus.Handshake) { - throw redirect(''); + throw new Error('Clerk: unexpected handshake without redirect'); } if (!handler) { From 47c18e902d80d399406691742182c6c5172c5a4c Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Fri, 15 Dec 2023 16:54:30 -0500 Subject: [PATCH 2/4] chore(repo): Add changeset --- .changeset/itchy-chairs-call.md | 5 +++++ 1 file changed, 5 insertions(+) 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..a996fa337a --- /dev/null +++ b/.changeset/itchy-chairs-call.md @@ -0,0 +1,5 @@ +--- +'@clerk/remix': minor +--- + +Update the Remix rootAuthLoader to handle handshake auth status, this replaces the previous interstitial flow. From 92cbd742bb4d7c174cc6fffd2b89f249b060674b Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Fri, 15 Dec 2023 17:38:09 -0500 Subject: [PATCH 3/4] feat(remix): Support getAuth as well, consolidate handshake handling into authenticateRequest --- .changeset/itchy-chairs-call.md | 15 ++++++++-- packages/backend/src/internal.ts | 2 +- packages/remix/src/ssr/authenticateRequest.ts | 30 ++++++++++++++++--- packages/remix/src/ssr/getAuth.ts | 9 +----- packages/remix/src/ssr/rootAuthLoader.ts | 12 +------- 5 files changed, 42 insertions(+), 26 deletions(-) diff --git a/.changeset/itchy-chairs-call.md b/.changeset/itchy-chairs-call.md index a996fa337a..dc5160a764 100644 --- a/.changeset/itchy-chairs-call.md +++ b/.changeset/itchy-chairs-call.md @@ -1,5 +1,16 @@ --- -'@clerk/remix': minor +'@clerk/remix': major --- -Update the Remix rootAuthLoader to handle handshake auth status, this replaces the previous interstitial flow. +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/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..27d9b30717 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'; @@ -13,11 +12,5 @@ export async function getAuth(args: LoaderFunctionArgs, opts?: GetAuthOptions): } 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(''); - } - return sanitizeAuthObject(requestState.toAuth()); } diff --git a/packages/remix/src/ssr/rootAuthLoader.ts b/packages/remix/src/ssr/rootAuthLoader.ts index aecb728e2b..13edf053c7 100644 --- a/packages/remix/src/ssr/rootAuthLoader.ts +++ b/packages/remix/src/ssr/rootAuthLoader.ts @@ -1,4 +1,4 @@ -import { AuthStatus, sanitizeAuthObject } from '@clerk/backend/internal'; +import { sanitizeAuthObject } from '@clerk/backend/internal'; import type { defer } from '@remix-run/server-runtime'; import { isDeferredData } from '@remix-run/server-runtime/dist/responses'; @@ -49,16 +49,6 @@ export const rootAuthLoader: RootAuthLoader = async ( const requestState = await authenticateRequest(args, opts); - const hasLocationHeader = requestState.headers.get('location'); - if (hasLocationHeader) { - // triggering a handshake redirect - return new Response(null, { status: 307, headers: requestState.headers }); - } - - if (requestState.status === AuthStatus.Handshake) { - throw new Error('Clerk: unexpected handshake without 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); From 6325c0e095e1e7588727d486714f366e2c4b63fb Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Fri, 15 Dec 2023 17:51:01 -0500 Subject: [PATCH 4/4] chore(remix): Add comment --- packages/remix/src/ssr/getAuth.ts | 2 ++ packages/remix/src/ssr/rootAuthLoader.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/packages/remix/src/ssr/getAuth.ts b/packages/remix/src/ssr/getAuth.ts index 27d9b30717..8558e0722e 100644 --- a/packages/remix/src/ssr/getAuth.ts +++ b/packages/remix/src/ssr/getAuth.ts @@ -10,6 +10,8 @@ export async function getAuth(args: LoaderFunctionArgs, opts?: GetAuthOptions): if (!args || (args && (!args.request || !args.context))) { throw new Error(noLoaderArgsPassedInGetAuth); } + + // 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 13edf053c7..bbb38597f3 100644 --- a/packages/remix/src/ssr/rootAuthLoader.ts +++ b/packages/remix/src/ssr/rootAuthLoader.ts @@ -47,6 +47,7 @@ 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); if (!handler) {