Skip to content

Commit 484efae

Browse files
examples: Updates with-supertokens example app (#58525)
Co-authored-by: Lee Robinson <me@leerob.io>
1 parent 42b8789 commit 484efae

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+969
-652
lines changed

examples/with-supertokens/.env

-14
This file was deleted.

examples/with-supertokens/.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
/node_modules
55
/.pnp
66
.pnp.js
7-
.yarn/install-state.gz
87

98
# testing
109
/coverage
@@ -34,3 +33,6 @@ yarn-error.log*
3433
# typescript
3534
*.tsbuildinfo
3635
next-env.d.ts
36+
37+
# VSCode
38+
.vscode

examples/with-supertokens/README.md

+18-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
# SuperTokens Example
1+
# SuperTokens App with Next.js app directory
22

3-
This is a simple set up for applications protected by SuperTokens.
3+
This is a simple application that is protected by SuperTokens. This app uses the Next.js app directory.
44

55
## How to use
66

7+
### Using `create-next-app`
8+
79
- 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:
810

911
```bash
@@ -22,7 +24,19 @@ pnpm create next-app --example with-supertokens with-supertokens-app
2224

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

27+
### Using `create-supertokens-app`
28+
29+
- Run the following command
30+
31+
```bash
32+
npx create-supertokens-app@latest --frontend=next
33+
```
34+
35+
- Select the option to use the app directory
36+
37+
Follow the instructions after `create-supertokens-app` has finished
38+
2539
## Notes
2640

27-
- Take a look at [SuperTokens documentation](https://supertokens.io/docs/community/introduction).
28-
- We have provided development OAuth keys for the various in build third party providers in the `.env` file. Feel free to use them for development purposes, but **please create your own keys for production use**.
41+
- 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)
42+
- 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**.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { getAppDirRequestHandler } from 'supertokens-node/nextjs'
2+
import { NextRequest, NextResponse } from 'next/server'
3+
import { ensureSuperTokensInit } from '../../../config/backend'
4+
5+
ensureSuperTokensInit()
6+
7+
const handleCall = getAppDirRequestHandler(NextResponse)
8+
9+
export async function GET(request: NextRequest) {
10+
const res = await handleCall(request)
11+
if (!res.headers.has('Cache-Control')) {
12+
// This is needed for production deployments with Vercel
13+
res.headers.set(
14+
'Cache-Control',
15+
'no-cache, no-store, max-age=0, must-revalidate'
16+
)
17+
}
18+
return res
19+
}
20+
21+
export async function POST(request: NextRequest) {
22+
return handleCall(request)
23+
}
24+
25+
export async function DELETE(request: NextRequest) {
26+
return handleCall(request)
27+
}
28+
29+
export async function PUT(request: NextRequest) {
30+
return handleCall(request)
31+
}
32+
33+
export async function PATCH(request: NextRequest) {
34+
return handleCall(request)
35+
}
36+
37+
export async function HEAD(request: NextRequest) {
38+
return handleCall(request)
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { NextResponse, NextRequest } from 'next/server'
2+
import { withSession } from '../../sessionUtils'
3+
4+
export function GET(request: NextRequest) {
5+
return withSession(request, async (session) => {
6+
if (!session) {
7+
return new NextResponse('Authentication required', { status: 401 })
8+
}
9+
10+
return NextResponse.json({
11+
note: 'Fetch any data from your application for authenticated user after using verifySession middleware',
12+
userId: session.getUserId(),
13+
sessionHandle: session.getHandle(),
14+
accessTokenPayload: session.getAccessTokenPayload(),
15+
})
16+
})
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use client'
2+
3+
import { useEffect, useState } from 'react'
4+
import { redirectToAuth } from 'supertokens-auth-react'
5+
import SuperTokens from 'supertokens-auth-react/ui'
6+
import { PreBuiltUIList } from '../../config/frontend'
7+
8+
export default function Auth() {
9+
// if the user visits a page that is not handled by us (like /auth/random), then we redirect them back to the auth page.
10+
const [loaded, setLoaded] = useState(false)
11+
useEffect(() => {
12+
if (SuperTokens.canHandleRoute(PreBuiltUIList) === false) {
13+
redirectToAuth({ redirectBack: false })
14+
} else {
15+
setLoaded(true)
16+
}
17+
}, [])
18+
19+
if (loaded) {
20+
return SuperTokens.getRoutingComponent(PreBuiltUIList)
21+
}
22+
23+
return null
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use client'
2+
3+
import Session from 'supertokens-auth-react/recipe/session'
4+
import styles from '../page.module.css'
5+
6+
export const CallAPIButton = () => {
7+
const fetchUserData = async () => {
8+
const accessToken = await Session.getAccessToken()
9+
const userInfoResponse = await fetch('http://localhost:3000/api/user', {
10+
headers: {
11+
Authorization: 'Bearer ' + accessToken,
12+
},
13+
})
14+
15+
alert(JSON.stringify(await userInfoResponse.json()))
16+
}
17+
18+
return (
19+
<div onClick={fetchUserData} className={styles.sessionButton}>
20+
Call API
21+
</div>
22+
)
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { getSSRSession } from '../sessionUtils'
2+
import { TryRefreshComponent } from './tryRefreshClientComponent'
3+
import styles from '../page.module.css'
4+
import { redirect } from 'next/navigation'
5+
import Image from 'next/image'
6+
import { CelebrateIcon, SeparatorLine } from '../../assets/images'
7+
import { CallAPIButton } from './callApiButton'
8+
import { LinksComponent } from './linksComponent'
9+
import { SessionAuthForNextJS } from './sessionAuthForNextJS'
10+
11+
export async function HomePage() {
12+
const { session, hasToken, hasInvalidClaims } = await getSSRSession()
13+
14+
if (!session) {
15+
if (!hasToken) {
16+
/**
17+
* This means that the user is not logged in. If you want to display some other UI in this
18+
* case, you can do so here.
19+
*/
20+
return redirect('/auth')
21+
}
22+
23+
if (hasInvalidClaims) {
24+
return <SessionAuthForNextJS />
25+
} else {
26+
return <TryRefreshComponent />
27+
}
28+
}
29+
30+
return (
31+
<SessionAuthForNextJS>
32+
<div className={styles.homeContainer}>
33+
<div className={styles.mainContainer}>
34+
<div
35+
className={`${styles.topBand} ${styles.successTitle} ${styles.bold500}`}
36+
>
37+
<Image
38+
src={CelebrateIcon}
39+
alt="Login successful"
40+
className={styles.successIcon}
41+
/>{' '}
42+
Login successful
43+
</div>
44+
<div className={styles.innerContent}>
45+
<div>Your userID is:</div>
46+
<div className={`${styles.truncate} ${styles.userId}`}>
47+
{session.getUserId()}
48+
</div>
49+
<CallAPIButton />
50+
</div>
51+
</div>
52+
<LinksComponent />
53+
<Image
54+
className={styles.separatorLine}
55+
src={SeparatorLine}
56+
alt="separator"
57+
/>
58+
</div>
59+
</SessionAuthForNextJS>
60+
)
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
'use client'
2+
import styles from '../page.module.css'
3+
import { BlogsIcon, GuideIcon, SignOutIcon } from '../../assets/images'
4+
import { recipeDetails } from '../config/frontend'
5+
import Link from 'next/link'
6+
import Image from 'next/image'
7+
import Session from 'supertokens-auth-react/recipe/session'
8+
import SuperTokens from 'supertokens-auth-react'
9+
10+
const SignOutLink = (props: { name: string; link: string; icon: string }) => {
11+
return (
12+
<div
13+
className={styles.linksContainerLink}
14+
onClick={async () => {
15+
await Session.signOut()
16+
SuperTokens.redirectToAuth()
17+
}}
18+
>
19+
<Image className={styles.linkIcon} src={props.icon} alt={props.name} />
20+
<div role={'button'}>{props.name}</div>
21+
</div>
22+
)
23+
}
24+
25+
export const LinksComponent = () => {
26+
const links: {
27+
name: string
28+
link: string
29+
icon: string
30+
}[] = [
31+
{
32+
name: 'Blogs',
33+
link: 'https://supertokens.com/blog',
34+
icon: BlogsIcon,
35+
},
36+
{
37+
name: 'Guides',
38+
link: recipeDetails.docsLink,
39+
icon: GuideIcon,
40+
},
41+
{
42+
name: 'Sign Out',
43+
link: '',
44+
icon: SignOutIcon,
45+
},
46+
]
47+
48+
return (
49+
<div className={styles.bottomLinksContainer}>
50+
{links.map((link) => {
51+
if (link.name === 'Sign Out') {
52+
return (
53+
<SignOutLink
54+
name={link.name}
55+
link={link.link}
56+
icon={link.icon}
57+
key={link.name}
58+
/>
59+
)
60+
}
61+
62+
return (
63+
<Link
64+
href={link.link}
65+
className={styles.linksContainerLink}
66+
key={link.name}
67+
target="_blank"
68+
>
69+
<Image
70+
className={styles.linkIcon}
71+
src={link.icon}
72+
alt={link.name}
73+
/>
74+
<div role={'button'}>{link.name}</div>
75+
</Link>
76+
)
77+
})}
78+
</div>
79+
)
80+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use client'
2+
3+
import React, { useState, useEffect } from 'react'
4+
import { SessionAuth } from 'supertokens-auth-react/recipe/session'
5+
6+
type Props = Parameters<typeof SessionAuth>[0] & {
7+
children?: React.ReactNode | undefined
8+
}
9+
10+
export const SessionAuthForNextJS = (props: Props) => {
11+
const [loaded, setLoaded] = useState(false)
12+
useEffect(() => {
13+
setLoaded(true)
14+
}, [])
15+
if (!loaded) {
16+
return props.children
17+
}
18+
return <SessionAuth {...props}>{props.children}</SessionAuth>
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use client'
2+
import React from 'react'
3+
import { SuperTokensWrapper } from 'supertokens-auth-react'
4+
import SuperTokensReact from 'supertokens-auth-react'
5+
import { frontendConfig, setRouter } from '../config/frontend'
6+
import { usePathname, useRouter } from 'next/navigation'
7+
8+
if (typeof window !== 'undefined') {
9+
// we only want to call this init function on the frontend, so we check typeof window !== 'undefined'
10+
SuperTokensReact.init(frontendConfig())
11+
}
12+
13+
export const SuperTokensProvider: React.FC<React.PropsWithChildren<{}>> = ({
14+
children,
15+
}) => {
16+
setRouter(useRouter(), usePathname() || window.location.pathname)
17+
18+
return <SuperTokensWrapper>{children}</SuperTokensWrapper>
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
'use client'
2+
3+
import { useEffect, useState } from 'react'
4+
import { useRouter } from 'next/navigation'
5+
import Session from 'supertokens-auth-react/recipe/session'
6+
import SuperTokens from 'supertokens-auth-react'
7+
8+
export const TryRefreshComponent = () => {
9+
const router = useRouter()
10+
const [didError, setDidError] = useState(false)
11+
12+
useEffect(() => {
13+
void Session.attemptRefreshingSession()
14+
.then((hasSession) => {
15+
if (hasSession) {
16+
router.refresh()
17+
} else {
18+
SuperTokens.redirectToAuth()
19+
}
20+
})
21+
.catch(() => {
22+
setDidError(true)
23+
})
24+
}, [router])
25+
26+
if (didError) {
27+
return <div>Something went wrong, please reload the page</div>
28+
}
29+
30+
return <div>Loading...</div>
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const appInfo = {
2+
appName: 'SuperTokens Next.js demo app',
3+
apiDomain: 'http://localhost:3000',
4+
websiteDomain: 'http://localhost:3000',
5+
apiBasePath: '/api/auth',
6+
websiteBasePath: '/auth',
7+
}

0 commit comments

Comments
 (0)