Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0797deb
improv: add an improved way of refreshing the session during SSR
bcbogdan Feb 3, 2025
5bfbbd1
check server actions
bcbogdan Mar 6, 2025
3b1973c
Add authenticate HOF for server actions
bcbogdan May 1, 2025
0c1ecca
Update examples
bcbogdan May 7, 2025
deccb9c
Code review fixes
bcbogdan May 20, 2025
f781088
Merge branch 'master' into feat/nextjs-ssr
bcbogdan May 21, 2025
ddcc439
Merge branch '0.50' into feat/nextjs-ssr
bcbogdan Aug 18, 2025
29685a6
fix: Fix token management during ssr in header based setups
bcbogdan Aug 19, 2025
bd3c991
test: Add nextjs e2e tests
bcbogdan Aug 28, 2025
77ee188
test: Extend e2e tests
bcbogdan Sep 2, 2025
bfa3607
fix: Linting fixes
bcbogdan Sep 2, 2025
4b0cb5d
chore: Update changelog
bcbogdan Sep 2, 2025
955e4cd
fix: Move jwtVerify to a separate file and remove jose
bcbogdan Sep 3, 2025
f7dddce
fix: Fix prune errors
bcbogdan Sep 3, 2025
19115da
ci: Update core docker image
bcbogdan Sep 3, 2025
fde0f49
chore: Remove comments
bcbogdan Sep 3, 2025
f3411f1
fix: Fix exports to make tests work
bcbogdan Sep 4, 2025
7a5630a
test: Update CI testing
bcbogdan Sep 10, 2025
3eaa59d
fix: Code review fixes
bcbogdan Oct 2, 2025
8ae42df
fix: Use the correct path for the refresh token cookie
bcbogdan Oct 16, 2025
1ce9d0b
fix: Update naming to underline lack of claim validation
bcbogdan Oct 21, 2025
99543fb
fix: Include requireAuth in the getServerActionSession function
bcbogdan Oct 21, 2025
bbd2287
test: Fix flaky tests
bcbogdan Oct 21, 2025
4d83718
fix: Fix function names
bcbogdan Oct 22, 2025
75048f3
fix: review fixes
porcellus Oct 22, 2025
c503a3c
chore: update size limit
porcellus Oct 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [unreleased]

- Add utilities for session management during server side rendering

## [0.50] - 2025-08-15

- Add WebAuthn credential management methods: `listCredentials`, `removeCredential`
Expand Down
2 changes: 1 addition & 1 deletion compose.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
services:
core:
# Uses `$SUPERTOKENS_CORE_VERSION` when available, else latest
image: supertokens/supertokens-core:dev-branch-${SUPERTOKENS_CORE_VERSION:-master}
image: supertokens/supertokens-dev-postgresql:${SUPERTOKENS_CORE_VERSION:-master}
ports:
# Uses `$SUPERTOKENS_CORE_PORT` when available, else 3567 for local port
- ${SUPERTOKENS_CORE_PORT:-3567}:3567
Expand Down
5 changes: 5 additions & 0 deletions examples/for-tests-nextjs/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
EXT_PUBLIC_SUPERTOKENS_APP_NAME=test
NEXT_PUBLIC_SUPERTOKENS_API_DOMAIN=http://localhost:3031
NEXT_PUBLIC_SUPERTOKENS_WEBSITE_DOMAIN=http://localhost:3031
NEXT_PUBLIC_SUPERTOKENS_API_BASE_PATH=/api/auth
NEXT_PUBLIC_SUPERTOKENS_WEBSITE_BASE_PATH=/auth
3 changes: 3 additions & 0 deletions examples/for-tests-nextjs/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
38 changes: 38 additions & 0 deletions examples/for-tests-nextjs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

# VSCode
.vscode
42 changes: 42 additions & 0 deletions examples/for-tests-nextjs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# SuperTokens App with Next.js app directory

This is a simple application that is protected by SuperTokens. This app uses the Next.js app directory.

## How to use

### Using `create-next-app`

- Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), or [pnpm](https://pnpm.io) to bootstrap the example:

```bash
npx create-next-app --example with-supertokens with-supertokens-app
```

```bash
yarn create next-app --example with-supertokens with-supertokens-app
```

```bash
pnpm create next-app --example with-supertokens with-supertokens-app
```

- Run `yarn install`

- Run `npm run dev` to start the application on `http://localhost:3000`.

### Using `create-supertokens-app`

- Run the following command

```bash
npx create-supertokens-app@latest --frontend=next
```

- Select the option to use the app directory

Follow the instructions after `create-supertokens-app` has finished

## Notes

- To know more about how this app works and to learn how to customise it based on your use cases refer to the [SuperTokens Documentation](https://supertokens.com/docs/guides)
- We have provided development OAuth keys for the various built-in third party providers in the `/app/config/backend.ts` file. Feel free to use them for development purposes, but **please create your own keys for production use**.
18 changes: 18 additions & 0 deletions examples/for-tests-nextjs/app/actions/protectedAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"use server";

import { cookies } from "next/headers";
import { getServerActionSessionWithoutClaims, init } from "supertokens-auth-react/nextjs/ssr";
import { ssrConfig } from "../config/ssr";

init(ssrConfig());

export async function protectedAction() {
const cookiesStore = await cookies();
const { status, session } = await getServerActionSessionWithoutClaims(cookiesStore);

if (status !== "valid") {
return { status, userId: undefined };
}

return { status, userId: session.userId };
}
8 changes: 8 additions & 0 deletions examples/for-tests-nextjs/app/actions/unprotectedAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"use server";

import { cookies } from "next/headers";
import { ssrConfig } from "../config/ssr";

export async function unprotectedAction() {
return Promise.resolve("success");
}
38 changes: 38 additions & 0 deletions examples/for-tests-nextjs/app/api/auth/[...path]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { getAppDirRequestHandler } from "supertokens-node/nextjs";
import Session, { refreshSessionWithoutRequestResponse } from "supertokens-node/recipe/session";
import { NextRequest, NextResponse } from "next/server";
import { ensureSuperTokensInit } from "../../../config/backend";
import { cookies } from "next/headers";

ensureSuperTokensInit();

const handleCall = getAppDirRequestHandler();

export async function GET(request: NextRequest) {
const res = await handleCall(request);
if (!res.headers.has("Cache-Control")) {
// This is needed for production deployments with Vercel
res.headers.set("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate");
}
return res;
}

export async function POST(request: NextRequest) {
return handleCall(request);
}

export async function DELETE(request: NextRequest) {
return handleCall(request);
}

export async function PUT(request: NextRequest) {
return handleCall(request);
}

export async function PATCH(request: NextRequest) {
return handleCall(request);
}

export async function HEAD(request: NextRequest) {
return handleCall(request);
}
18 changes: 18 additions & 0 deletions examples/for-tests-nextjs/app/api/update-core/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { getCoreUrl } from "@/app/config/backend";
import { NextResponse, NextRequest } from "next/server";
import { withSession } from "supertokens-node/nextjs";

export async function POST(request: NextRequest) {
const updatePayload: Record<string, unknown> = {};
const coreUrl = getCoreUrl();

await fetch(`${coreUrl}/recipe/multitenancy/connectionuridomain/v2`, {
method: "PUT",
body: JSON.stringify(updatePayload),
headers: {
"Content-Type": "application/json",
},
});

return NextResponse.json({});
}
23 changes: 23 additions & 0 deletions examples/for-tests-nextjs/app/api/user/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ensureSuperTokensInit } from "@/app/config/backend";
import { NextResponse, NextRequest } from "next/server";
import { withSession } from "supertokens-node/nextjs";

ensureSuperTokensInit();

export function GET(request: NextRequest) {
return withSession(request, async (err, session) => {
if (err) {
return NextResponse.json(err, { status: 500 });
}
if (!session) {
return new NextResponse("Authentication required", { status: 401 });
}

return NextResponse.json({
note: "Fetch any data from your application for authenticated user after using verifySession middleware",
userId: session.getUserId(),
sessionHandle: session.getHandle(),
accessTokenPayload: session.getAccessTokenPayload(),
});
});
}
25 changes: 25 additions & 0 deletions examples/for-tests-nextjs/app/auth/[[...path]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"use client";

import { useEffect, useState } from "react";
import { redirectToAuth } from "supertokens-auth-react";
import SuperTokens from "supertokens-auth-react/ui";
import { PreBuiltUIList } from "../../config/frontend";

export default function Auth() {
// if the user visits a page that is not handled by us (like /auth/random), then we redirect them back to the auth page.
const [loaded, setLoaded] = useState(false);

useEffect(() => {
if (SuperTokens.canHandleRoute(PreBuiltUIList) === false) {
redirectToAuth({ redirectBack: false });
} else {
setLoaded(true);
}
}, []);

if (loaded) {
return SuperTokens.getRoutingComponent(PreBuiltUIList);
}

return null;
}
36 changes: 36 additions & 0 deletions examples/for-tests-nextjs/app/components/home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { cookies, headers } from "next/headers";
import { redirect } from "next/navigation";
import Image from "next/image";
import { CelebrateIcon, SeparatorLine } from "../../assets/images";
import { CallAPIButton } from "./callApiButton";
import { LinksComponent } from "./linksComponent";
import { SessionAuthForNextJS } from "./sessionAuthForNextJS";

import { getServerComponentSessionWithoutClaims, init } from "supertokens-auth-react/nextjs/ssr";
import { ssrConfig } from "../config/ssr";
import { useState } from "react";
import { MiddlewareServerActionButton } from "./middlewareServerActionButton";
import { ProtectedActionButton } from "./protectedActionButton";
import { UnprotectedActionButton } from "./unprotectedActionButton";
import { SignOutButton } from "./signOutButton";

init(ssrConfig());

export async function HomePage() {
const cookiesStore = await cookies();
const session = await getServerComponentSessionWithoutClaims(cookiesStore);

return (
<SessionAuthForNextJS>
<div id="supertokens-root" data-testid="home-page">
<div style={{ display: "flex", gap: "10px" }}>
<div>getServerComponentSession:</div>
<div data-testid="getServerComponentSession-userId">{session.userId}</div>
</div>
<ProtectedActionButton />
<UnprotectedActionButton />
<SignOutButton />
</div>
</SessionAuthForNextJS>
);
}
30 changes: 30 additions & 0 deletions examples/for-tests-nextjs/app/components/protectedActionButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"use client";

import { useState } from "react";
import { protectedAction } from "../actions/protectedAction";
import { init } from "supertokens-auth-react/nextjs/ssr";
import { ssrConfig } from "../config/ssr";

init(ssrConfig());

export const ProtectedActionButton = () => {
const [actionResult, setActionResult] = useState<{ status: string; userId?: string } | undefined>();
return (
<div style={{ display: "flex", gap: "10px" }}>
<button
data-testid="getServerActionSession-button"
onClick={async () => {
const result = await protectedAction();
setActionResult(result);
}}>
getServerActionSession
</button>
<div data-testid="getServerActionSession-result">
{actionResult?.status}:{actionResult?.userId}
</div>
<button onClick={() => setActionResult(undefined)} data-testid="getServerActionSession-reset">
Reset
</button>
</div>
);
};
19 changes: 19 additions & 0 deletions examples/for-tests-nextjs/app/components/sessionAuthForNextJS.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"use client";

import React, { useState, useEffect } from "react";
import { SessionAuth } from "supertokens-auth-react/recipe/session";

type Props = Parameters<typeof SessionAuth>[0] & {
children?: React.ReactNode | undefined;
};

export const SessionAuthForNextJS = (props: Props) => {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
setLoaded(true);
}, []);
if (!loaded) {
return props.children;
}
return <SessionAuth {...props}>{props.children}</SessionAuth>;
};
18 changes: 18 additions & 0 deletions examples/for-tests-nextjs/app/components/signOutButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"use client";

import { useState } from "react";
import { signOut } from "supertokens-auth-react/recipe/session";
import { unprotectedAction } from "../actions/unprotectedAction";
import { ssrConfig } from "../config/ssr";

export const SignOutButton = () => {
const onClick = async () => {
await signOut();
};

return (
<button data-testid="signOut" onClick={onClick}>
Sing Out
</button>
);
};
18 changes: 18 additions & 0 deletions examples/for-tests-nextjs/app/components/supertokensProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"use client";
import React from "react";
import { SuperTokensWrapper } from "supertokens-auth-react";
import SuperTokensReact from "supertokens-auth-react";
import { frontendConfig, setRouter } from "../config/frontend";
import { usePathname, useRouter } from "next/navigation";

if (typeof window !== "undefined") {
// we only want to call this init function on the frontend, so we check typeof window !== 'undefined'
SuperTokensReact.init(frontendConfig());
} else {
}

export const SuperTokensProvider: React.FC<React.PropsWithChildren<{}>> = ({ children }) => {
setRouter(useRouter(), usePathname() || window.location.pathname);

return <SuperTokensWrapper>{children}</SuperTokensWrapper>;
};
Loading
Loading