Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 { getServerActionSession, 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 getServerActionSession(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 { getServerComponentSession, 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 getServerComponentSession(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