Skip to content

Commit

Permalink
feat: bootstrap nextjs app example project
Browse files Browse the repository at this point in the history
  • Loading branch information
emmanuelgautier committed Aug 7, 2024
1 parent 57d9b30 commit 69a3639
Show file tree
Hide file tree
Showing 24 changed files with 9,998 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ jobs:
- workingDirectory: ./examples/angular-spa
projectName: cerberauth-angular-spa-oidc
directory: ./dist/angular-spa/browser
- workingDirectory: ./examples/nextjs-app
projectName: cerberauth-nextjs-app-oidc
directory: ./

permissions:
contents: read
Expand Down
5 changes: 5 additions & 0 deletions examples/nextjs-app/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Use openssl rand -base64 33 to generate a secret
AUTH_SECRET=secret

AUTH_CLIENT_ID=
AUTH_CLIENT_SECRET=
3 changes: 3 additions & 0 deletions examples/nextjs-app/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
36 changes: 36 additions & 0 deletions examples/nextjs-app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# 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
82 changes: 82 additions & 0 deletions examples/nextjs-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# React SPA (Single Page App) using Authorization Code Flow with PKCE

This project demonstrates how to implement the Authorization Code Flow with PKCE for a Next.js App.

Disclaimer: This project is for educational purposes only and should not be used in production without proper security review and testing.

## Deployment

This project is deployed on Cloudflare Pages. You can access the live demo [here](https://cerberauth-nextjs-app-oidc.pages.dev/).

## Prerequisites

Before getting started, make sure you have the following:

- Node.js installed on your machine
- An OpenID Connect provider that supports the Authorization Code Flow with PKCE

## Getting Started

1. Clone the repository:

```bash
git clone https://github.com/cerberauth/openid-connect-examples.git
```

2. Install the dependencies:

```bash
cd openid-connect-examples/nextjs-app
npm ci
```

3. Configure the OpenID Connect provider:

If you don't have an OpenID Connect provider, you can use [TestID OpenID Connect Provider](https://testid.cerberauth.com/).

- Obtain the client ID and client secret from your OpenID Connect provider.
- Register the redirect URI for your React SPA in the provider's developer console.

4. Update the configuration:

- Create a `.env.local` file in the root directory of your project or copy `.env.example` file.
- Add the necessary environment variables to the `.env.local` file. For example:

```plaintext
AUTH_SECRET=secret
AUTH_CLIENT_ID=your-client-id
AUTH_CLIENT_SECRET=your-client-secret
```
Generate a random secret using the following command:
```bash
openssl rand -base64 33
```
Replace `secret` with the generated secret.
Replace `your-client-id` and `your-client-secret` with the actual values provided by your OpenID Connect provider.
- Save the `.env.local` file.
5. Start the development server:
```bash
npm run dev
```

6. Open your browser and navigate to `http://localhost:5173/`.

7. Click on the "Login" button to initiate the authorization code flow.

8. After successful authentication, you will be redirected back to the React SPA and the user information will be displayed.

## Additional Resources

- [Auth.js](https://authjs.dev/)
- [OpenID Connect](https://openid.net/)
- [OAuth 2.0 Authorization Code Flow](https://oauth.net/2/grant-types/authorization-code/)
- [PKCE](https://oauth.net/2/pkce/)
- [Awesome OpenID Connect](https://github.com/cerberauth/awesome-openid-connect)
- [React](https://reactjs.org/)
4 changes: 4 additions & 0 deletions examples/nextjs-app/app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { handlers } from '@/auth'

export const runtime = 'edge'
export const { GET, POST } = handlers
Binary file added examples/nextjs-app/app/favicon.ico
Binary file not shown.
69 changes: 69 additions & 0 deletions examples/nextjs-app/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
}

.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}

@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
35 changes: 35 additions & 0 deletions examples/nextjs-app/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { Metadata } from 'next'
import { Inter as FontSans } from 'next/font/google'
import { SessionProvider } from 'next-auth/react'
import './globals.css'

import { cn } from '@/lib/utils'

const fontSans = FontSans({
subsets: ['latin'],
variable: '--font-sans',
})

export const metadata: Metadata = {
title: 'Next.js Application Example using OpenID Connect',
description: 'This example demonstrates how to authenticate users in a Next.js Application using OpenID Connect Protocol.',
}

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={cn(
"bg-slate-100 min-h-screen font-sans antialiased",
fontSans.variable,
)}>
<SessionProvider>
{children}
</SessionProvider>
</body>
</html>
)
}
41 changes: 41 additions & 0 deletions examples/nextjs-app/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use client'

import { useSession, signOut, signIn } from 'next-auth/react'
import { Button } from '@/components/ui/button'

export default function Home() {
const session = useSession()
const user = session.data?.user
const isAuthenticated = !!user

return (
<div className="bg-slate-100 min-h-screen">
<main role="main" className="flex flex-col items-center justify-center h-5/6 space-y-8 text-center px-4 py-16 lg:pt-32 md:pt-16 sm:pt-8">
<h1 className="mt-4 text-2xl font-extrabold tracking-tight text-slate-900 sm:text-5xl xl:max-w-[43.5rem]">
Next.js Application Example using OpenID Connect
</h1>
<p className="mt-4 max-w-lg text-lg text-slate-700">
This example demonstrates how to authenticate users in a Next.js Application using OpenID Connect Protocol.
</p>
{isAuthenticated ? (
<Button onClick={() => signOut()}>
Logout
</Button>
) : (
<Button onClick={() => signIn('testid')}>
Login with TestID
</Button>
)}
<p className="mt-4 max-w-lg text-slate-700">
If you want to checkout out how to implement OpenID Connect in your Next.js app, take a look at the <a className="text-indigo-600" href="https://github.com/cerberauth/openid-connect-examples/tree/main/examples/nextjs-app">source code</a>.
</p>
</main>

<footer className="text-center py-4">
<p className="text-sm text-gray-500">
Proudly part of <a className="text-indigo-600" href="https://www.cerberauth.com">CerberAuth</a> community.
</p>
</footer>
</div>
)
}
22 changes: 22 additions & 0 deletions examples/nextjs-app/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import NextAuth from 'next-auth'

export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [{
id: 'testid',
name: 'TestID',
issuer: 'https://testid.cerberauth.com',
type: 'oidc',
clientId: process.env.AUTH_CLIENT_ID,
clientSecret: process.env.AUTH_CLIENT_SECRET,
checks: ['pkce', 'state'],
authorization: {
params: { scope: 'openid profile email' }
},
}],
session: { strategy: 'jwt' },
callbacks: {
session: async ({ session }) => {
return session
},
}
})
17 changes: 17 additions & 0 deletions examples/nextjs-app/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
56 changes: 56 additions & 0 deletions examples/nextjs-app/components/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)

export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"

export { Button, buttonVariants }
6 changes: 6 additions & 0 deletions examples/nextjs-app/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
Loading

0 comments on commit 69a3639

Please sign in to comment.