This guide connects the existing Clerk session to FlowPanel and restricts the admin to users whose public metadata role is `admin`.

## Prerequisites

Clerk must already work in the host Next.js application. Install its Next.js SDK if needed:

```bash
pnpm add @clerk/nextjs
```

## Protect the route with Clerk

Configure Clerk middleware using the same admin and API paths generated by FlowPanel. The middleware is the first authentication boundary; FlowPanel's server handlers remain the authorization boundary.

Wrap the root application in `ClerkProvider` according to the installed Clerk version. The complete maintained example is [`examples/with-clerk`](https://github.com/getflowpanel/flowpanel/tree/main/examples/with-clerk).

## Configure FlowPanel auth

```ts excerpt
import { defineAdmin } from "@flowpanel/kit";
import { withClerk } from "@flowpanel/kit/auth";

export default defineAdmin({
  adapter,
  auth: withClerk({
    requireRole: "admin",
    signInUrl: "/sign-in",
    forbiddenUrl: "/",
  }),
  resources,
});
```

By default, `withClerk` reads `publicMetadata.role` and falls back to `guest`. It uses the Clerk user ID as the actor ID for audit and per-user rate limiting. Supply `role` or `userId` only when your claims use a different shape.

## Put the role in the session

Set `publicMetadata.role` for the user and include `public_metadata` in the Clerk session token as `publicMetadata`. A database role that is not present in the session claims cannot be read by `withClerk` without a custom resolver.

Treat metadata changes as authentication state changes. Sign out and back in, or otherwise refresh the Clerk session, before testing a new role.

## Verify the boundary

Test all three cases:

1. no Clerk session: the admin page follows `signInUrl`, while API requests are rejected;
2. signed in without `admin`: the page follows `forbiddenUrl`, while API requests return forbidden;
3. signed in as `admin`: the page and resource API succeed.

Also call one action route as a non-admin. A hidden button is not proof; the server response is.

## Production notes and failure modes

- `withClerk` throws a clear startup/runtime error when `@clerk/nextjs` is absent.
- Missing or misspelled public metadata resolves to `guest` and should fail the role gate.
- Keep API and stream paths inside the Clerk middleware matcher.
- Do not use `devAuth()` or the generated session stub in production.

Continue with [Roles and permissions](/docs/guides/permissions). Exact options are generated in [Configuration reference](/docs/reference/define-config#authconfig).
