Skip to content

Commit

Permalink
examples: Updates with-supertokens example app (#58525)
Browse files Browse the repository at this point in the history
Co-authored-by: Lee Robinson <me@leerob.io>
  • Loading branch information
rishabhpoddar and leerob authored Dec 4, 2023
1 parent 42b8789 commit 484efae
Show file tree
Hide file tree
Showing 49 changed files with 969 additions and 652 deletions.
14 changes: 0 additions & 14 deletions examples/with-supertokens/.env

This file was deleted.

4 changes: 3 additions & 1 deletion examples/with-supertokens/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage
Expand Down Expand Up @@ -34,3 +33,6 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

# VSCode
.vscode
22 changes: 18 additions & 4 deletions examples/with-supertokens/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# SuperTokens Example
# SuperTokens App with Next.js app directory

This is a simple set up for applications protected by SuperTokens.
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
Expand All @@ -22,7 +24,19 @@ pnpm create next-app --example with-supertokens with-supertokens-app

- 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

- Take a look at [SuperTokens documentation](https://supertokens.io/docs/community/introduction).
- 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**.
- 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**.
39 changes: 39 additions & 0 deletions examples/with-supertokens/app/api/auth/[...path]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { getAppDirRequestHandler } from 'supertokens-node/nextjs'
import { NextRequest, NextResponse } from 'next/server'
import { ensureSuperTokensInit } from '../../../config/backend'

ensureSuperTokensInit()

const handleCall = getAppDirRequestHandler(NextResponse)

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)
}
17 changes: 17 additions & 0 deletions examples/with-supertokens/app/api/user/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { NextResponse, NextRequest } from 'next/server'
import { withSession } from '../../sessionUtils'

export function GET(request: NextRequest) {
return withSession(request, async (session) => {
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(),
})
})
}
24 changes: 24 additions & 0 deletions examples/with-supertokens/app/auth/[[...path]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'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
}
23 changes: 23 additions & 0 deletions examples/with-supertokens/app/components/callApiButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use client'

import Session from 'supertokens-auth-react/recipe/session'
import styles from '../page.module.css'

export const CallAPIButton = () => {
const fetchUserData = async () => {
const accessToken = await Session.getAccessToken()
const userInfoResponse = await fetch('http://localhost:3000/api/user', {
headers: {
Authorization: 'Bearer ' + accessToken,
},
})

alert(JSON.stringify(await userInfoResponse.json()))
}

return (
<div onClick={fetchUserData} className={styles.sessionButton}>
Call API
</div>
)
}
61 changes: 61 additions & 0 deletions examples/with-supertokens/app/components/home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { getSSRSession } from '../sessionUtils'
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'

export async function HomePage() {
const { session, hasToken, hasInvalidClaims } = await getSSRSession()

if (!session) {
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')
}

if (hasInvalidClaims) {
return <SessionAuthForNextJS />
} else {
return <TryRefreshComponent />
}
}

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}`}>
{session.getUserId()}
</div>
<CallAPIButton />
</div>
</div>
<LinksComponent />
<Image
className={styles.separatorLine}
src={SeparatorLine}
alt="separator"
/>
</div>
</SessionAuthForNextJS>
)
}
80 changes: 80 additions & 0 deletions examples/with-supertokens/app/components/linksComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'use client'
import styles from '../page.module.css'
import { BlogsIcon, GuideIcon, SignOutIcon } from '../../assets/images'
import { recipeDetails } from '../config/frontend'
import Link from 'next/link'
import Image from 'next/image'
import Session from 'supertokens-auth-react/recipe/session'
import SuperTokens from 'supertokens-auth-react'

const SignOutLink = (props: { name: string; link: string; icon: string }) => {
return (
<div
className={styles.linksContainerLink}
onClick={async () => {
await Session.signOut()
SuperTokens.redirectToAuth()
}}
>
<Image className={styles.linkIcon} src={props.icon} alt={props.name} />
<div role={'button'}>{props.name}</div>
</div>
)
}

export const LinksComponent = () => {
const links: {
name: string
link: string
icon: string
}[] = [
{
name: 'Blogs',
link: 'https://supertokens.com/blog',
icon: BlogsIcon,
},
{
name: 'Guides',
link: recipeDetails.docsLink,
icon: GuideIcon,
},
{
name: 'Sign Out',
link: '',
icon: SignOutIcon,
},
]

return (
<div className={styles.bottomLinksContainer}>
{links.map((link) => {
if (link.name === 'Sign Out') {
return (
<SignOutLink
name={link.name}
link={link.link}
icon={link.icon}
key={link.name}
/>
)
}

return (
<Link
href={link.link}
className={styles.linksContainerLink}
key={link.name}
target="_blank"
>
<Image
className={styles.linkIcon}
src={link.icon}
alt={link.name}
/>
<div role={'button'}>{link.name}</div>
</Link>
)
})}
</div>
)
}
19 changes: 19 additions & 0 deletions examples/with-supertokens/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>
}
19 changes: 19 additions & 0 deletions examples/with-supertokens/app/components/supertokensProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'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())
}

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

return <SuperTokensWrapper>{children}</SuperTokensWrapper>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use client'

import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
import Session from 'supertokens-auth-react/recipe/session'
import SuperTokens from 'supertokens-auth-react'

export const TryRefreshComponent = () => {
const router = useRouter()
const [didError, setDidError] = useState(false)

useEffect(() => {
void Session.attemptRefreshingSession()
.then((hasSession) => {
if (hasSession) {
router.refresh()
} else {
SuperTokens.redirectToAuth()
}
})
.catch(() => {
setDidError(true)
})
}, [router])

if (didError) {
return <div>Something went wrong, please reload the page</div>
}

return <div>Loading...</div>
}
7 changes: 7 additions & 0 deletions examples/with-supertokens/app/config/appInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const appInfo = {
appName: 'SuperTokens Next.js demo app',
apiDomain: 'http://localhost:3000',
websiteDomain: 'http://localhost:3000',
apiBasePath: '/api/auth',
websiteBasePath: '/auth',
}
Loading

0 comments on commit 484efae

Please sign in to comment.