Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/CLI/next-app-dir #139

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 98 additions & 22 deletions boilerplate/fullstack/next-app-dir/README.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,118 @@
# SuperTokens App with Next.js app directory
# SuperTokens + Next

This is a simple application that is protected by SuperTokens. This app uses the Next.js app directory.
A demo implementation of [SuperTokens](https://supertokens.com/) with [Next.js](https://nextjs.org/).

## How to use
## General Info

### Using `create-next-app`
This project aims to demonstrate how to integrate SuperTokens into a Nexjs application. Its primary purpose is to serve as an educational tool, but it can also be used as a starting point for your own project.

- 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:
## Repo Structure

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

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

```bash
pnpm create next-app --example with-supertokens with-supertokens-app
📦
┣ 📂app
┃ ┣ 📂api
┃ ┃ ┣ 📂auth
┃ ┃ ┃ ┗ 📂[...path]
┃ ┃ ┃ ┗ 📜route.ts
┃ ┃ ┗ 📂user
┃ ┃ ┗ 📜route.ts
┃ ┣ 📂auth
┃ ┃ ┗ 📂[[...path]]
┃ ┃ ┗ 📜page.tsx
┃ ┣ 📂components
┃ ┃ ┣ 📜dashboard.tsx
┃ ┃ ┣ 📜footer.tsx
┃ ┃ ┣ 📜home.tsx
┃ ┃ ┣ 📜sessionAuthForNextJS.tsx
┃ ┃ ┣ 📜sessionInfo.tsx
┃ ┃ ┣ 📜supertokensProvider.tsx
┃ ┃ ┗ 📜tryRefreshClientComponent.tsx
┃ ┣ 📂config
┃ ┃ ┣ 📜appInfo.ts
┃ ┃ ┣ 📜backend.ts
┃ ┃ ┗ 📜frontend.tsx
┃ ┣ 📂dashboard
┃ ┃ ┗ 📜page.tsx
┃ ┣ 📜favicon.ico
┃ ┣ 📜globals.css
┃ ┣ 📜layout.tsx
┃ ┣ 📜page.tsx
┃ ┗ 📜util.ts
┣ 📂assets
┣ 📜middleware.ts
┣ 📜next-env.d.ts
┣ 📜next.config.js
┣ 📜package-lock.json
┣ 📜package.json
┣ 📜README.md
┣ 📂public
┗ 📜tsconfig.json
```

- Run `yarn install`
### Config

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

### Using `create-supertokens-app`
The full configuration needed for SuperTokens (the frontend part) to work is in the `src/config.tsx` file. This file will differ based on the [auth recipe](https://supertokens.com/docs/guides) you choose.

- Run the following command
If you choose to use this as a starting point for your own project, you can further customize the options and config in the `src/config.tsx` file. Refer to our [docs](https://supertokens.com/docs) (and make sure to choose the correct recipe) for more details.

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

1. **Root Component (`layout.tsx`)**

- Initializes SuperTokens with the provided configuration (generated by the CLI)
- Wraps the application with necessary providers:
- `SuperTokensWrapper`: Manages auth state and session
- `ComponentWrapper`: Provides auth UI customization for specific auth recipes. For example, if you choose to use the `ThirdPartyPasswordless` recipe, the `ComponentWrapper` will provide the UI customization for the passwordless login flow (showing a disclaimer about the SMS delivery in demo apps).

2. **Home Component (`/` route, `/page.tsx` component)**

- Public landing page accessible to all users
- Provides navigation to authentication and dashboard
- Displays basic application information and links

3. **Dashboard Component (`/dashboard` route, `/Dashboard/page.tsx` component)**
- Protected route only accessible to authenticated users
- Protected by `SessionAuth` component
- Displays user information and session details
- Provides functionality to:
- View user ID
- Call test API endpoints
- Access documentation
- Sign out

When a user visits the application, they start at the home page (`/`). They can choose to authenticate through the `/auth` route, and once authenticated, they gain access to the protected dashboard. The session state is managed throughout the application using SuperTokens' session management.

- Select the option to use the app directory
## Customizations

Follow the instructions after `create-supertokens-app` has finished
If you want to customize the default auth UI, you have two options:

1. Refer to the [docs](https://supertokens.com/docs/thirdpartyemailpassword/advanced-customizations/react-component-override/usage) on how to customize the pre-built UI.
2. Roll your own UI by choosing "Custom UI" in the right sidebar in the [docs](https://supertokens.com/docs/thirdpartyemailpassword/quickstart/frontend-setup).

## Additional resources

- Custom UI Example: https://github.com/supertokens/supertokens-web-js/tree/master/examples/react/with-thirdpartyemailpassword
- Custom UI Blog post: https://supertokens.medium.com/adding-social-login-to-your-website-with-supertokens-custom-ui-only-5fa4d7ab6402
- Awesome SuperTokens: https://github.com/kohasummons/awesome-supertokens

## Contributing

Please refer to the [CONTRIBUTING.md](https://github.com/supertokens/create-supertokens-app/blob/master/CONTRIBUTING.md) file in the root of the [`create-supertokens-app`](https://github.com/supertokens/create-supertokens-app) repo.

## Contact us

For any questions, or support requests, please email us at team@supertokens.io, or join our [Discord](https://supertokens.io/discord) server.

## 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**.

## Authors

Created with :heart: by the folks at SuperTokens.io.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ export default function Auth() {
}, []);

if (loaded) {
return SuperTokens.getRoutingComponent(PreBuiltUIList);
return (
<div id="home-container" className="fill">
{SuperTokens.getRoutingComponent(PreBuiltUIList)}
</div>
);
}

return null;
Expand Down
68 changes: 68 additions & 0 deletions boilerplate/fullstack/next-app-dir/app/components/dashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { cookies } from "next/headers";
import { TryRefreshComponent } from "./tryRefreshClientComponent";
import { redirect } from "next/navigation";
import { CelebrateIcon } from "../../assets/images";
import Image from "next/image";
import { SessionAuthForNextJS } from "./sessionAuthForNextJS";
import { getSessionForSSR } from "../util";

import SessionInfo from "./sessionInfo";
import Footer from "./footer";

export async function DashboardPage() {
const cookiesFromReq = await cookies();
const cookiesArray: Array<{ name: string; value: string }> = Array.from(cookiesFromReq.getAll()).map(
({ name, value }) => ({
name,
value,
})
);
const { accessTokenPayload, hasToken, error } = await getSessionForSSR(cookiesArray);

if (error) {
return <div>Something went wrong while trying to get the session. Error - {error.message}</div>;
}

// `accessTokenPayload` will be undefined if it the session does not exist or has expired
if (accessTokenPayload === undefined) {
if (!hasToken) {
/**
* This means that the user is not logged in. If you want to display some other UI in this
* case, you can do so here.
*/
return redirect("/auth");
}

/**
* This means that the session does not exist but we have session tokens for the user. In this case
* the `TryRefreshComponent` will try to refresh the session.
*
* To learn about why the 'key' attribute is required refer to: https://github.com/supertokens/supertokens-node/issues/826#issuecomment-2092144048
*/
return <TryRefreshComponent key={Date.now()} />;
}

/**
* SessionAuthForNextJS will handle proper redirection for the user based on the different session states.
* It will redirect to the login page if the session does not exist etc.
*/
return (
<SessionAuthForNextJS>
<div className="fill" id="home-container">
<div className="main-container">
<div className="top-band success-title bold-500">
<Image src={CelebrateIcon} alt="Login successful" className="success-icon" /> Login successful
</div>
<div className="inner-content">
<div>Your userID is:</div>
<div className="truncate" id="user-id">
{accessTokenPayload.sub}
</div>
<SessionInfo />
</div>
</div>
<Footer />
</div>
</SessionAuthForNextJS>
);
}
58 changes: 58 additions & 0 deletions boilerplate/fullstack/next-app-dir/app/components/footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"use client";
kohasummons marked this conversation as resolved.
Show resolved Hide resolved

import { BlogsIcon, GuideIcon, SeparatorLine, SignOutIcon } from "../../assets/images";
import Image from "next/image";
import { recipeDetails } from "../config/frontend";
import Session from "supertokens-auth-react/recipe/session/index.js";
import SuperTokens from "supertokens-auth-react";

export interface Link {
name: string;
onClick: () => void;
icon: string;
}

function openLink(url: string) {
window.open(url, "_blank");
}

async function logoutClicked() {
await Session.signOut();
SuperTokens.redirectToAuth();
}

const links: Link[] = [
{
name: "Blogs",
onClick: () => openLink("https://supertokens.com/blog"),
icon: BlogsIcon,
},
{
name: "Documentation",
onClick: () => openLink(recipeDetails.docsLink),
icon: GuideIcon,
},
{
name: "Sign Out",
onClick: logoutClicked,
icon: SignOutIcon,
},
];

export default function Footer() {
return (
<>
<div className="bottom-links-container">
{links.map((link) => (
<div className="link" key={link.name}>
<Image className="link-icon" src={link.icon} alt={link.name} width={20} height={20} />
<div role={"button"} onClick={link.onClick}>
{link.name}
</div>
</div>
))}
</div>
<Image className="separator-line" src={SeparatorLine} alt="separator" />
</>
);
}
82 changes: 21 additions & 61 deletions boilerplate/fullstack/next-app-dir/app/components/home.tsx
Original file line number Diff line number Diff line change
@@ -1,68 +1,28 @@
import { cookies } from "next/headers";
import { TryRefreshComponent } from "./tryRefreshClientComponent";
import styles from "../page.module.css";
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 { getSessionForSSR } from "../util";
import Link from "next/link";

export async function HomePage() {
const cookiesFromReq = await cookies();
const cookiesArray: Array<{ name: string; value: string }> = Array.from(cookiesFromReq.getAll()).map(
({ name, value }) => ({
name,
value,
})
);
const { accessTokenPayload, hasToken, error } = await getSessionForSSR(cookiesArray);

if (error) {
return <div>Something went wrong while trying to get the session. Error - {error.message}</div>;
}

// `accessTokenPayload` will be undefined if it the session does not exist or has expired
if (accessTokenPayload === undefined) {
if (!hasToken) {
/**
* This means that the user is not logged in. If you want to display some other UI in this
* case, you can do so here.
*/
return redirect("/auth");
}

/**
* This means that the session does not exist but we have session tokens for the user. In this case
* the `TryRefreshComponent` will try to refresh the session.
*
* To learn about why the 'key' attribute is required refer to: https://github.com/supertokens/supertokens-node/issues/826#issuecomment-2092144048
*/
return <TryRefreshComponent key={Date.now()} />;
}

/**
* SessionAuthForNextJS will handle proper redirection for the user based on the different session states.
* It will redirect to the login page if the session does not exist etc.
*/
export function HomePage() {
return (
<SessionAuthForNextJS>
<div className={styles.homeContainer}>
<div className={styles.mainContainer}>
<div className={`${styles.topBand} ${styles.successTitle} ${styles.bold500}`}>
<Image src={CelebrateIcon} alt="Login successful" className={styles.successIcon} /> Login
successful
</div>
<div className={styles.innerContent}>
<div>Your userID is:</div>
<div className={`${styles.truncate} ${styles.userId}`}>{accessTokenPayload.sub}</div>
<CallAPIButton />
<div className="fill" id="home-container">
<div className="logos">
<img src="/ST.svg" alt="SuperTokens" />
<span>x</span>
<img src="/next.svg" alt="Next.js" />
</div>
<div className="main-container">
<div className="inner-content">
<p>
<strong>SuperTokens</strong> x <strong>Nextjs</strong> <br /> example project
</p>
<div className="buttons">
<Link href="/auth" className="sessionButton">
kohasummons marked this conversation as resolved.
Show resolved Hide resolved
Sign-up / Login
</Link>
<Link href="/dashboard" className="sessionButton">
Dashboard
</Link>
</div>
</div>
<LinksComponent />
<Image className={styles.separatorLine} src={SeparatorLine} alt="separator" />
</div>
</SessionAuthForNextJS>
</div>
);
}
Loading