使い方
- インストールコマンドをコピーし、自分の環境へ導入する前にソースを確認します。
- 必要なツール、認証情報、対応環境を原文で確認します。
- 入力と成果物を指定し、出力を資料と照合します。
npx skills add https://github.com/devhive1/devhive-cli --skill clerk-auth開始前の確認
- 導入にはモデル契約、API枠、ファイルへのアクセス許可は含まれません。
- スター数や導入数は活動の指標であり、独立した品質・安全性試験ではありません。
見出し、例、コード、表、リンク、参照画像を含む原文を表示しています。
Clerk Auth
Set up Clerk authentication with proxy support. Keys are automatically provisioned.
When to Use
- User wants authentication, login, signup, or user accounts (this is the default)
- User wants custom branding on login/signup screens
- User wants their own user database
- Any generic auth request that does not explicitly mention "Replit Auth", "Sign in with Replit", or "Replit SSO"
Setup
Step 1: Provision Clerk App
In the JavaScript sandbox, call the setupClerkWhitelabelAuth function to provision your Clerk app and set secrets:
const result = await setupClerkWhitelabelAuth();
console.log(result);Step 2: Copy Proxy Middleware Template
mkdir -p artifacts/api-server/src/middlewares
cp .local/skills/clerk-auth/templates/api-server/src/middlewares/clerkProxyMiddleware.ts artifacts/api-server/src/middlewares/clerkProxyMiddleware.tsStep 3: Install Server Dependencies
pnpm --filter @workspace/api-server add http-proxy-middleware @clerk/expressStep 4: Wire Up app.ts
In artifacts/api-server/src/app.ts, mount the proxy middleware before body parsers (the proxy streams raw bytes). Ensure pino structured logging is set up first — see the Logging section in the pnpm-workspace skill and references/server.md for setup instructions.
import express from "express";
import cors from "cors";
import { clerkMiddleware } from "@clerk/express";
import { CLERK_PROXY_PATH, clerkProxyMiddleware } from "./middlewares/clerkProxyMiddleware";
import router from "./routes";
const app = express();
// pinoHttp structured logging middleware should already be mounted here
// (see pnpm-workspace skill / references/server.md for setup)
app.use(CLERK_PROXY_PATH, clerkProxyMiddleware());
app.use(cors({ credentials: true, origin: true }));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(clerkMiddleware());
app.use("/api", router);
export default app;Step 5: Install Client Dependencies
pnpm --filter @workspace/<artifact-name> add @clerk/reactExpo Setup (optional)
When the mobile app uses Expo, follow these additional steps to integrate Clerk authentication.
Step 1: Install Expo Dependencies
Expo ecosystem packages (expo-*) must be version-pinned to match the project's Expo SDK. Running pnpm add without a version resolves to the latest on npm, which may belong to a newer SDK and break the app.
For SDK 54 projects:
pnpm --filter @workspace/<expo-app-artifact-name> add -D @clerk/expo expo-auth-session@~7.0.10 expo-secure-store@~15.0.8 expo-web-browser@~15.0.10 expo-crypto@~15.0.8If the project already has any of these packages installed at compatible versions, they can be omitted from the command. If @clerk/expo peer dependency warnings mention other missing expo-* packages not listed above, use this SDK 54 version reference:
| Package | SDK 54 version |
|---|---|
expo-auth-session | ~7.0.10 |
expo-secure-store | ~15.0.8 |
expo-crypto | ~15.0.8 |
expo-web-browser | ~15.0.10 |
expo-constants | ~18.0.11 |
Step 2: Pass the Publishable Key to the Dev Script
In the Expo app's package.json, prepend the Clerk publishable key as an environment variable to the existing dev script (keep all existing env vars and flags intact):
{
"scripts": {
"dev": "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=$CLERK_PUBLISHABLE_KEY <existing dev command>"
}
}Step 3: Configure Environment Variables in build.js
In build.js, construct the Clerk proxy URL from the deployment domain and forward all Clerk env vars to the Expo build:
const clerkProxyUrl = process.env.CLERK_PROXY_URL
? `https://${expoPublicDomain}${process.env.CLERK_PROXY_URL}`
: "";
const env = {
...process.env,
// ...other env vars...
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.CLERK_PUBLISHABLE_KEY || "",
EXPO_PUBLIC_CLERK_PROXY_URL: clerkProxyUrl,
};IMPORTANT
- The server-side proxy code is production only and is not used in development. Do not ask for or set any env vars related to the proxy. All production setup is handled by a separate system.
- When delegating to the design subagent, be sure to include all of the code instructions/guidance below in the task description so it follows them closely
Home Route Behavior
The artifact's base path (import.meta.env.BASE_URL) must always be a publicly accessible landing page for unauthenticated users — never redirect them to sign-in or sign-up. Dropping users onto a sign-in screen with no context about the app causes confusion and hurts conversion. Avoid <RedirectToSignIn>, wrapping the homepage in auth-only gates, or any explicit redirect from the base path to /sign-in or /sign-up.
For authenticated users, always redirect the base path to a user-portal view so they land directly in the app without an extra click. After the user signed-out, redirects to the home route rather than the sign-in route.
Example code:
import { Show } from "@clerk/react";
import { Switch, Route, Redirect } from "wouter";
// Inside <WouterRouter base={basePath}>, all route paths and navigation
// targets are base-relative. "/" matches {basePath}/, "/user-portal"
// navigates to {basePath}/user-portal, etc.
function HomeRedirect() {
return (
<>
<Show when="signed-in">
<Redirect to="/user-portal" />
</Show>
<Show when="signed-out">
<Home />
</Show>
</>
);
}
function UserPortal() {
return (
<>
<Show when="signed-in">
<UserPortalPage />
</Show>
<Show when="signed-out">
<Redirect to="/" />
</Show>
</>
);
}
function Router() {
return (
<Switch>
<Route path="/" component={HomeRedirect} />
<Route path="/user-portal" component={UserPortal} />
{/* Other routes */}
</Switch>
);
}After Setup - Web App
1. Set up client routing with Wouter base path
This is vital. Setting <WouterRouter base={basePath}> makes every location change via wouter's components and hooks (e.g. <Route>, <Redirect to>, setLocation) relative to the base URL. However, Clerk's <SignIn path> and <SignUp path> props read window.location.pathname directly and must be full paths — use ` ${basePath}/sign-in and ${basePath}/sign-up `.
Important: The proxy setup does not work with Clerk's hosted pages. You must create dedicated /sign-in and /sign-up routes in your app to handle OAuth callbacks.
import { useEffect, useRef } from "react";
import { ClerkProvider, SignIn, SignUp, Show, useClerk } from '@clerk/react';
import { Switch, Route, useLocation, Router as WouterRouter } from 'wouter';
import { queryClient } from "./lib/queryClient";
import { QueryClientProvider, useQueryClient } from "@tanstack/react-query";
const clerkPubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY;
// NOTE: in dev this env var will be empty, in prod it will be automatically set
const clerkProxyUrl = import.meta.env.VITE_CLERK_PROXY_URL;
const basePath = import.meta.env.BASE_URL.replace(/\/$/, "");
// Clerk passes full paths to routerPush/routerReplace, but wouter's
// setLocation prepends the base — strip it to avoid doubling.
function stripBase(path: string): string {
return basePath && path.startsWith(basePath)
? path.slice(basePath.length) || "/"
: path;
}
if (!clerkPubKey) {
throw new Error('Missing VITE_CLERK_PUBLISHABLE_KEY in .env file');
}
function SignInPage() {
return (
<div style={{ display: 'flex', justifyContent: 'center', marginTop: '2rem' }}>
{/* path must be the full browser path — Clerk reads window.location.pathname directly */}
<SignIn routing="path" path={`${basePath}/sign-in`} signUpUrl={`${basePath}/sign-up`} />
</div>
);
}
function SignUpPage() {
return (
<div style={{ display: 'flex', justifyContent: 'center', marginTop: '2rem' }}>
<SignUp routing="path" path={`${basePath}/sign-up`} signInUrl={`${basePath}/sign-in`} />
</div>
);
}
// Helps user's webview stay up-to-date when the signed-in user changes by invalidating the QueryClient cache.
function ClerkQueryClientCacheInvalidator() {
const { addListener } = useClerk();
const queryClient = useQueryClient();
const prevUserIdRef = useRef<string | null | undefined>(undefined);
useEffect(() => {
const unsubscribe = addListener(({ user }) => {
const userId = user?.id ?? null;
if (
prevUserIdRef.current !== undefined &&
prevUserIdRef.current !== userId
) {
queryClient.clear();
}
prevUserIdRef.current = userId;
});
return unsubscribe;
}, [addListener, queryClient]);
return null;
}
function ClerkProviderWithRoutes() {
const [, setLocation] = useLocation();
return (
<ClerkProvider
publishableKey={clerkPubKey}
proxyUrl={clerkProxyUrl}
routerPush={(to) => setLocation(stripBase(to))}
routerReplace={(to) => setLocation(stripBase(to), { replace: true })}
>
<QueryClientProvider client={queryClient}>
<ClerkQueryClientCacheInvalidator />
<Switch>
{/* HomeRedirect renders homepage or user portal based on signed-in status. */}
<Route path="/" component={HomeRedirect} />
<Route path="/sign-in/*?" component={SignInPage} />
<Route path="/sign-up/*?" component={SignUpPage} />
{/* Add other routes here */}
</Switch>
</QueryClientProvider>
</ClerkProvider>
);
}
function App() {
return (
<WouterRouter base={basePath}>
<ClerkProviderWithRoutes />
</WouterRouter>
);
}
export default App;2. Protect API routes
Use getAuth from @clerk/express to check for an authenticated user:
import { getAuth } from "@clerk/express";
const requireAuth = (req: any, res: any, next: any) => {
const auth = getAuth(req);
const userId = auth?.sessionClaims?.userId || auth?.userId;
if (!userId) {
return res.status(401).json({ error: "Unauthorized" });
}
req.userId = userId;
next();
};
app.get("/api/protected", requireAuth, handler);3. Use auth state in components
Use the <Show> component to conditionally render based on authentication state:
import { Show } from "@clerk/react";
function MyComponent() {
return (
<>
{/* Show content only to signed-in users */}
<Show when="signed-in">
{/* Protected content */}
</Show>
{/* Show content only to signed-out users */}
<Show when="signed-out">
{/* Login prompt or redirect */}
</Show>
</>
);
}4. Render user profile
Use useUser hook to get current authenticated user. Important: Do not use <UserButton /> by default — the built-in component is not customizable and may expose confusing Clerk-level user management options to end users.
import { useUser } from "@clerk/react";
// Render component with user profile
const { user, isLoaded } = useUser();After Setup - Mobile App
1. Set Up ClerkProvider in the Root Layout
In the top-level _layout.tsx, wrap the app with ClerkProvider and ClerkLoaded:
import { ClerkProvider, ClerkLoaded } from "@clerk/expo";
import { tokenCache } from "@clerk/expo/token-cache";
import { setBaseUrl } from "@workspace/api-client-react";
const domain = process.env.EXPO_PUBLIC_DOMAIN;
if (domain) setBaseUrl(`https://${domain}`);
const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY!;
const proxyUrl = process.env.EXPO_PUBLIC_CLERK_PROXY_URL || undefined;
export default function RootLayout() {
return (
<ClerkProvider
publishableKey={publishableKey}
tokenCache={tokenCache}
proxyUrl={proxyUrl}
>
<ClerkLoaded>
{/* ...rest of app... */}
</ClerkLoaded>
</ClerkProvider>
);
}2. Wire Up the Auth Token for API Calls
In a layout that requires authentication (e.g. (home)/_layout.tsx), use setAuthTokenGetter from @workspace/api-client-react so the generated API client attaches the bearer token to every request:
import { useEffect } from "react";
import { Redirect, Stack } from "expo-router";
import { useAuth } from "@clerk/expo";
import { setAuthTokenGetter } from "@workspace/api-client-react";
export default function HomeLayout() {
const { isSignedIn, getToken } = useAuth();
useEffect(() => {
setAuthTokenGetter(() => getToken());
}, [getToken]);
if (!isSignedIn) return <Redirect href="/(auth)/sign-in" />;
return <Stack screenOptions={{ headerShown: false }} />;
}3. Build Custom Authentication Screens
By default, implement Email + Password and Google as sign-in / sign-up options unless the user asks otherwise.
You must build custom authentication screens — native Clerk components are incompatible with Expo Go, so a custom layout is the only viable approach.
All authentication code must follow the Clerk Core v3 SDK APIs documented in the references below. Do not rely on prior knowledge of the Clerk SDK — the Core v3 API has breaking changes from v2, and using outdated patterns will produce runtime errors. Read the relevant reference in full before writing any authentication code.
- For email/password sign-in and sign-up flows, follow
.local/skills/clerk-auth/references/custom-ui/expo-sdk-email-password.md. - For Google OAuth (and other OAuth provider) sign-in and sign-up flows, follow
.local/skills/clerk-auth/references/custom-ui/expo-sdk-oauth.md.
4. (IMPORTANT) Fix Incompatible Package Versions
ALWAYS fix incompatible package version warnings from the system log. These warnings indicate mismatched peer dependencies that will cause runtime crashes or subtle bugs. Resolve every version conflict before proceeding — do not ignore them.
Environment Variables (Auto-Provisioned)
These are set automatically by setupClerkWhitelabelAuth(). Do not ask the user for these values.
CLERK_SECRET_KEY(server): Auto-provisioned secret keyCLERK_PUBLISHABLE_KEY(server): Auto-provisioned publishable keyVITE_CLERK_PUBLISHABLE_KEY(client): Auto-provisioned publishable key
Migrating from Replit Auth
When migrating an existing app from Replit Auth to Clerk, read the following references:
references/migration.md— General migration guidance: detection, common rules, user identity mapping, and the criticalsessionClaims.userIdrequirement for migrated users.references/web-migration.md— Web app migration (Express API server + React+Vite frontend): what to remove and what to transition.references/expo-migration.md— Expo mobile app migration: what to remove and what to transition.
Troubleshooting
Unauthorized (401) errors in the preview web app
When API requests from the web app return 401 Unauthorized, calling `setAuthTokenGetter` is never the correct fix for web applications. That function exists only for mobile (Expo) apps where cookie-based sessions are unavailable. In the web app, the browser automatically sends session cookies with every API request, so authentication should work without any explicit token handling.
Instead:
- Check middleware setup — Verify that
clerkMiddleware()is mounted inapp.tsbefore the API routes, and that therequireAuthmiddleware is correctly wired on protected endpoints (checkinggetAuth(req)for a valid session). - When not sure, add debugging logs — Add temporary logging inside the
requireAuthmiddleware (e.g. log the output ofgetAuth(req)) to see what Clerk is receiving. Then restart the client and API server workflows and ask the user to test again so you can inspect the logs.
Limitations
- Idempotent: Safe to call multiple times
- Proxy path is hardcoded to
/__clerk



