> **Building with AI coding agents?** Install the authstack plugin with one command. This equips your agent with accurate Scalekit implementation patterns.
>
> **Recommended**:
> ```bash
> npx @scalekit-inc/cli setup
> ```
>
> Global:
> ```bash
> npm install -g @scalekit-inc/cli
> scalekit setup
> ```
>
> Supports Claude Code, Cursor, GitHub Copilot, Codex + skills for 40+ agents.
> Skills: integrating-agentkit, implementing-saaskit, adding-mcp-oauth, implementing-modular-sso, implementing-scim-provisioning.
> [Full setup guide](https://docs.scalekit.com/dev-kit/build-with-ai/)

---

# Next.js session middleware

Add hosted login and an encrypted session cookie to the Next.js App Router
Use `ScalekitAuthNext` from `@scalekit-sdk/node/next` to add hosted login, an encrypted `sk_session` cookie, token refresh, and logout to the App Router.

Typical flow: create one `auth` instance, export login/callback/logout Route Handlers, and wrap a protected handler with `withAuth`. Use `createMiddleware()` to fail closed on every other path. For Edge Runtime, pass `ScalekitEdgeClient` instead of `ScalekitClient`.

Requires `@scalekit-sdk/node` 2.12.0 or later.

Register these URLs in the Scalekit Dashboard under **Authentication > Redirects** before you test:

| Dashboard field | Must match |
| --- | --- |
| **Redirect URI** | `redirectUri` exactly, for example `http://localhost:3000/callback` |
| **Post Logout Redirect URI** | Absolute URL after full logout, for example `http://localhost:3000/` |
| **Initiate Login URL** | Login path, for example `http://localhost:3000/login` |

Store credentials in environment variables. Never hard-code secrets.

```bash title=".env" showLineNumbers=false
SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.com
SCALEKIT_CLIENT_ID=skc_...
SCALEKIT_CLIENT_SECRET=...
COOKIE_ENCRYPTION_SECRET=   # openssl rand -base64 32
REDIRECT_URI=http://localhost:3000/callback
```

Keep `COOKIE_ENCRYPTION_SECRET` identical on every server instance.

## Install the package

```bash title="Terminal" showLineNumbers=false
npm install @scalekit-sdk/node
```

## Protect a route

`ScalekitAuthNext` requires a `client`. It does not accept `envUrl` alone.

```typescript title="lib/auth.ts" wrap showLineNumbers=false

const scalekit = new ScalekitClient(
  process.env.SCALEKIT_ENVIRONMENT_URL!,
  process.env.SCALEKIT_CLIENT_ID!,
  process.env.SCALEKIT_CLIENT_SECRET!
);

export const auth = new ScalekitAuthNext({
  client: scalekit,
  redirectUri: process.env.REDIRECT_URI!,
  cookieEncryptionSecret: process.env.COOKIE_ENCRYPTION_SECRET!,
});
```

```typescript title="app/login/route.ts" wrap showLineNumbers=false

export const GET = auth.createLoginHandler();
```

```typescript title="app/callback/route.ts" wrap showLineNumbers=false

export const GET = auth.createCallbackHandler();
```

```typescript title="app/logout/route.ts" wrap showLineNumbers=false

export const GET = auth.createLogoutHandler();
```

```typescript title="app/account/route.ts" wrap showLineNumbers=false

export const GET = auth.withAuth(async (request, { user }) => {
  return Response.json({ sub: user?.sub });
});
```

Open `http://localhost:3000/account`. A missing session returns **302** to `/login`, not a JSON 401.

`user` is access-token claims. `sub` is always present. `email` appears only when you add it as a custom access-token claim.

> caution: Pin runtime: 'nodejs' unless you use ScalekitEdgeClient
>
> `createMiddleware()` uses `ScalekitClient` by default. That client is Node-only. Export `runtime: 'nodejs'` in `middleware.ts`, or the Next.js build fails. See [`ScalekitEdgeClient`](#scalekitedgeclient) for Edge Runtime.

<div class="sdk-client-page">

### constructor
<div class="sdk-method-section">
  
    

      Creates the App Router session helper. Pass a `ScalekitClient` or `ScalekitEdgeClient`.

      
        Auth client. Required. `ScalekitClient` is Node-only; use `ScalekitEdgeClient` on Edge Runtime.
      
      
        Exact **Redirect URI** registered in the dashboard.
      
      
        Secret used to encrypt `sk_session`. Generate with `openssl rand -base64 32`.
      
      
        Session cookie name.
      
      
        Login route path.
      
      
        Callback route path. Also excluded from `createMiddleware()` gating.
      
      
        Logout route path. Also excluded from `createMiddleware()` gating.
      
      
        Fallback path after login when `returnTo` is absent.
      
      
        Where logout lands. Defaults to `postLoginRedirect`. Register the absolute URL as **Post Logout Redirect URI**.
      
      
        When `true`, logout ends the Scalekit session with `id_token_hint`.
      
      
        Helper used by Route Handlers and middleware.
      

```typescript wrap showLineNumbers=false
export const auth = new ScalekitAuthNext({
  client: scalekit,
  redirectUri: process.env.REDIRECT_URI!,
  cookieEncryptionSecret: process.env.COOKIE_ENCRYPTION_SECRET!,
});
```

    
  
</div>

### createLoginHandler
<div class="sdk-method-section">
  
    

      Returns a `GET` Route Handler that starts hosted login and sets the CSRF state cookie.

       Promise">
        Handler to re-export from `app/login/route.ts`.
      

```typescript wrap showLineNumbers=false
export const GET = auth.createLoginHandler();
```

    
  
</div>

### createCallbackHandler
<div class="sdk-method-section">
  
    

      Returns a `GET` Route Handler that exchanges the authorization code and sets `sk_session`.

       Promise">
        Handler to re-export from `app/callback/route.ts`.
      

```typescript wrap showLineNumbers=false
export const GET = auth.createCallbackHandler();
```

    
  
</div>

### createLogoutHandler
<div class="sdk-method-section">
  
    

      Returns a `GET` Route Handler that clears `sk_session`. With `fullLogout: true`, also ends the Scalekit session.

       Promise">
        Handler to re-export from `app/logout/route.ts`.
      

```typescript wrap showLineNumbers=false
export const GET = auth.createLogoutHandler();
```

    
  
</div>

### withAuth
<div class="sdk-method-section">
  
    
      

      Wraps a Route Handler so it runs only with a valid session. Refreshes the cookie about 10 seconds before expiry. Redirects to `loginPath` when the session is missing.

       NextResponse">
        Route Handler. `context.user` is access-token claims.
      
       Promise">
        Wrapped handler. Missing session → 302, never JSON 401.
      

```typescript wrap showLineNumbers=false
export const GET = auth.withAuth(async (request, { user }) => {
  return Response.json({ sub: user?.sub });
});
```

    
  
</div>

### createMiddleware
<div class="sdk-method-section">
  
    

      Fail-closed middleware. Every matched path redirects to login unless it is listed in `publicRoutes` or is `loginPath`, `callbackPath`, or `logoutPath`.

      Next.js reads `export const config` as a static export. This method cannot generate that object.

      
        Paths that stay public, for example `['/', '/pricing']`.
      
       Promise">
        Middleware function to export as the default from `middleware.ts`.
      

```typescript wrap showLineNumbers=false
export default auth.createMiddleware({
  publicRoutes: ['/', '/pricing'],
});

export const config = {
  runtime: 'nodejs',
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};
```

    
  
</div>

### getSession
<div class="sdk-method-section">
  
    
      

      Read-only session lookup for Server Components, Route Handlers, and Server Actions. Does not refresh or write a cookie. Only `createMiddleware()` and `withAuth()` write a new cookie.

      
        Access-token claims and expiry, or `null`. Never includes `accessToken` or `refreshToken`.
      

```typescript wrap showLineNumbers=false
const session = await auth.getSession();
if (session) {
  console.log(session.user.sub, session.expiresAt);
}
```

    
  
</div>

### currentUser
<div class="sdk-method-section">
  
    
      

      Shortcut for `getSession()` when only claims are needed.

       | undefined">
        Access-token claims, or `undefined` when there is no valid session.
      

```typescript wrap showLineNumbers=false
const user = await auth.currentUser();
```

    
  
</div>

> note: Same-request expiry after a refresh
>
> If middleware refreshes the cookie on the current request, `getSession()` on that same request can still read the old `expiresAt`. The next request sees the new cookie.

### ScalekitEdgeClient
<div class="sdk-method-section">
  
    

      Fetch + `jose` client for the auth methods `ScalekitAuthNext` needs on Edge Runtime. Not a full `ScalekitClient`. Use [Create client](/saaskit/sdks/node/scalekit-client/) for Organizations, Users, and other API clients.

      
        Scalekit environment URL.
      
      
        Application client ID.
      
      
        Application client secret.
      
      
        Drop-in `client` for `ScalekitAuthNext`.
      

```typescript wrap showLineNumbers=false

const scalekit = new ScalekitEdgeClient(
  process.env.SCALEKIT_ENVIRONMENT_URL!,
  process.env.SCALEKIT_CLIENT_ID!,
  process.env.SCALEKIT_CLIENT_SECRET!
);

export const auth = new ScalekitAuthNext({
  client: scalekit,
  redirectUri: process.env.REDIRECT_URI!,
  cookieEncryptionSecret: process.env.COOKIE_ENCRYPTION_SECRET!,
});
```

```typescript title="middleware.ts" wrap showLineNumbers=false

export default auth.createMiddleware({
  publicRoutes: ['/', '/pricing'],
});

export const config = {
  runtime: 'experimental-edge',
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};
```

    
  
</div>

> note: Use experimental-edge, not edge
>
> This Next.js version rejects the string `'edge'` at build time. Set `runtime: 'experimental-edge'`.

</div>


---

## More Scalekit documentation

| Resource | What it contains | When to use it |
|----------|-----------------|----------------|
| [/llms.txt](/llms.txt) | Structured index with routing hints per product area | Start here — find which documentation set covers your topic before loading full content |
| [/llms-full.txt](/llms-full.txt) | Complete documentation for all Scalekit products in one file | Use when you need exhaustive context across multiple products or when the topic spans several areas |
| [sitemap-0.xml](https://docs.scalekit.com/sitemap-0.xml) | Full URL list of every documentation page | Use to discover specific page URLs you can fetch for targeted, page-level answers |
