diff --git a/.gitignore b/.gitignore index 31ee7d3..2712d89 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ node_modules dist .DS_Store coverage -playground +playground/.next diff --git a/playground/.env.local b/playground/.env.local new file mode 100644 index 0000000..a63e370 --- /dev/null +++ b/playground/.env.local @@ -0,0 +1,6 @@ +KINDE_CLIENT_ID= +KINDE_CLIENT_SECRET= +KINDE_ISSUER_URL=https://.kinde.com +KINDE_SITE_URL=http://localhost:3000 +KINDE_POST_LOGOUT_REDIRECT_URL=http://localhost:3000 +KINDE_POST_LOGIN_REDIRECT_URL=http://localhost:3000/dashboard diff --git a/playground/next-env.d.ts b/playground/next-env.d.ts new file mode 100644 index 0000000..4f11a03 --- /dev/null +++ b/playground/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/playground/next.config.js b/playground/next.config.js new file mode 100644 index 0000000..658404a --- /dev/null +++ b/playground/next.config.js @@ -0,0 +1,4 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = {}; + +module.exports = nextConfig; diff --git a/playground/package.json b/playground/package.json new file mode 100644 index 0000000..03ece14 --- /dev/null +++ b/playground/package.json @@ -0,0 +1,18 @@ +{ + "name": "kinde-nextjs-app-router-starter-kit", + "version": "0.1.0", + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@kinde-oss/kinde-auth-nextjs": "latest", + "@types/node": "latest", + "eslint-config-next": "latest", + "next": "latest", + "react": "latest", + "react-dom": "latest" + } +} diff --git a/playground/src/app/api/auth/[...kindeAuth]/route.ts b/playground/src/app/api/auth/[...kindeAuth]/route.ts new file mode 100644 index 0000000..c4f7298 --- /dev/null +++ b/playground/src/app/api/auth/[...kindeAuth]/route.ts @@ -0,0 +1,3 @@ +import { handleAuth } from "@kinde-oss/kinde-auth-nextjs/server"; + +export const GET = handleAuth(); diff --git a/playground/src/app/api/protected/route.ts b/playground/src/app/api/protected/route.ts new file mode 100644 index 0000000..ac2939f --- /dev/null +++ b/playground/src/app/api/protected/route.ts @@ -0,0 +1,16 @@ +import { getKindeServerSession } from "@kinde-oss/kinde-auth-nextjs/server"; +import { NextResponse } from "next/server"; + +export async function GET() { + const { getUser, isAuthenticated } = getKindeServerSession(); + + if (!(await isAuthenticated())) { + return new Response("Unauthorized", { status: 401 }); + } + + const user = await getUser(); + const data = { message: "Hello User", id: user?.given_name }; + + return NextResponse.json({ data }); +} + diff --git a/playground/src/app/api/public/route.ts b/playground/src/app/api/public/route.ts new file mode 100644 index 0000000..e187d63 --- /dev/null +++ b/playground/src/app/api/public/route.ts @@ -0,0 +1,7 @@ +import { NextResponse } from "next/server"; + +export async function GET() { + const data = { message: "Hello world" }; + + return NextResponse.json({ data }); +} diff --git a/playground/src/app/dashboard/page.tsx b/playground/src/app/dashboard/page.tsx new file mode 100644 index 0000000..e11416d --- /dev/null +++ b/playground/src/app/dashboard/page.tsx @@ -0,0 +1,17 @@ +export default function Dashboard() { + return ( +
+
+

Woohoo!

+

+ Your authentication is all sorted. +
+ Build the important stuff. +

+
+
+

Next steps for you

+
+
+ ); +} diff --git a/playground/src/app/favicon.ico b/playground/src/app/favicon.ico new file mode 100644 index 0000000..718d6fe Binary files /dev/null and b/playground/src/app/favicon.ico differ diff --git a/playground/src/app/globals.css b/playground/src/app/globals.css new file mode 100644 index 0000000..a5bbdad --- /dev/null +++ b/playground/src/app/globals.css @@ -0,0 +1,226 @@ +:root { + --g-color-black: #000; + --g-color-white: #fff; + + --g-color-grey-100: #f1f2f4; + --g-color-grey-600: #5d636f; + --g-color-grey-700: #434851; + --g-color-grey-900: #121417; + + --g-box-shadow: 0px 6px 12px rgba(18, 20, 23, 0.06), + 0px 15px 24px rgba(18, 20, 23, 0.07), 0px -4px 12px rgba(18, 20, 23, 0.05); + + --g-font-family: "Helvetica"; + + --g-font-size-x-small: 0.75rem; /* 12px */ + --g-font-size-small: 0.875rem; /* 14px */ + --g-font-size-base: 1rem; /* 16px */ + --g-font-size-large: 1.25rem; /* 20x */ + --g-font-size-x-large: 1.5rem; /* 24px */ + --g-font-size-2x-large: 2rem; /* 32px */ + --g-font-size-3x-large: 2.5rem; /* 40px */ + --g-font-size-4x-large: 4rem; /* 64px */ + + --g-font-weight-base: 400; + --g-font-weight-semi-bold: 500; + --g-font-weight-bold: 600; + --g-font-weight-black: 700; + + --g-border-radius-small: 0.5rem; + --g-border-radius-base: 1rem; + --g-border-radius-large: 1.5rem; + + --g-spacing-small: 0.5rem; /* 8px */ + --g-spacing-base: 1rem; /* 16px */ + --g-spacing-large: 1.5rem; /* 24px */ + --g-spacing-x-large: 2rem; /* 32px */ + --g-spacing-2x-large: 2.5rem; /* 40px */ + --g-spacing-3x-large: 3rem; /* 48px */ + --g-spacing-6x-large: 6rem; /* 96px */ +} + +* { + padding: 0; + margin: 0; + box-sizing: border-box; +} + +html, +body { + padding: 0; + margin: 0; + font-family: var(--g-font-family); +} + +a { + color: inherit; + text-decoration: none; +} + +.text-subtle { + color: var(--g-color-grey-600); + font-size: var(--g-font-size-x-small); + font-weight: var(--g-font-weight-base); +} + +.text-body-1 { + font-size: var(--g-font-size-2x-large); + font-weight: var(--g-font-weight-base); +} + +.text-body-2 { + font-size: var(--g-font-size-x-large); + font-weight: var(--g-font-weight-base); +} + +.text-body-3 { + color: var(--g-color-grey-900); + font-size: var(--g-font-size-small); + font-weight: var(--g-font-weight-base); +} + +.text-display-1 { + font-size: var(--g-font-size-4x-large); + font-weight: var(--g-font-weight-black); + line-height: 1.2; +} + +.text-display-2 { + font-size: var(--g-font-size-3x-large); + font-weight: var(--g-font-weight-black); + line-height: 1.4; +} + +.text-display-3 { + font-size: var(--g-font-size-x-large); + font-weight: var(--g-font-weight-black); +} + +.text-heading-1 { + font-size: var(--g-font-size-large); + font-weight: var(--g-font-weight-semi-bold); +} + +.text-heading-2 { + font-size: var(--g-font-size-base); + font-weight: var(--g-font-weight-semi-bold); +} + +.container { + padding: 0 var(--g-spacing-6x-large); + margin: auto; +} + +.nav { + align-items: center; + display: flex; + justify-content: space-between; + padding-bottom: var(--g-spacing-x-large); + padding-top: var(--g-spacing-x-large); + width: 100%; +} + +.sign-in-btn { + margin-right: var(--g-spacing-small); +} + +.btn { + border-radius: var(--g-border-radius-small); + display: inline-block; + font-weight: var(--g-font-weight-bold); + padding: var(--g-spacing-base); +} + +.btn-ghost { + color: var(--g-color-grey-700); +} + +.btn-dark { + background-color: var(--g-color-black); + color: var(--g-color-white); +} + +.btn-light { + background: var(--g-color-white); + color: var(--g-color-black); + font-weight: 600; +} + +.btn-big { + font-size: var(--g-font-size-large); + padding: var(--g-font-size-large) var(--g-font-size-x-large); +} + +.hero { + align-items: center; + display: flex; + flex-direction: column; + height: 45rem; + justify-content: center; + text-align: center; +} + +.hero-title { + margin-bottom: var(--g-spacing-x-large); +} + +.hero-tagline { + margin-bottom: var(--g-spacing-3x-large); +} + +.card { + background: var(--g-color-black); + border-radius: var(--g-border-radius-large); + box-shadow: var(--g-box-shadow); + color: var(--g-color-white); +} + +.link { + text-decoration: underline; + text-underline-offset: 0.2rem; +} + +.link:hover { + background: #f1f2f4; +} + +.footer { + padding-bottom: var(--g-spacing-x-large); + padding-top: var(--g-spacing-x-large); +} + +.footer-tagline { + margin-bottom: var(--g-font-size-x-small); + margin-top: var(--g-font-size-x-small); +} + +.start-hero { + padding: var(--g-spacing-2x-large); + text-align: center; +} + +.start-hero-intro { + margin-bottom: var(--g-spacing-base); +} + +.avatar { + align-items: center; + background-color: var(--g-color-grey-100); + border-radius: var(--g-border-radius-large); + display: flex; + height: var(--g-spacing-3x-large); + justify-content: center; + text-align: center; + width: var(--g-spacing-3x-large); +} + +.profile-blob { + align-items: center; + display: grid; + gap: var(--g-spacing-base); + grid-template-columns: auto 1fr; +} + +.next-steps-section { + margin-top: var(--g-spacing-2x-large); +} diff --git a/playground/src/app/layout.tsx b/playground/src/app/layout.tsx new file mode 100644 index 0000000..56f64a6 --- /dev/null +++ b/playground/src/app/layout.tsx @@ -0,0 +1,82 @@ +import "./globals.css"; +import { + RegisterLink, + LoginLink, + LogoutLink, +} from "@kinde-oss/kinde-auth-nextjs/components"; +import { getKindeServerSession } from "@kinde-oss/kinde-auth-nextjs/server"; +import Link from "next/link"; + +export const metadata = { + title: "Kinde Auth", + description: "Kinde with NextJS App Router", +}; + +export default async function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + const { isAuthenticated, getUser } = getKindeServerSession(); + const user = await getUser(); + return ( + + +
+ +
+
{children}
+
+
+ KindeAuth +

+ Visit our{" "} + + help center + +

+ + + © 2023 KindeAuth, Inc. All rights reserved + +
+
+ + + ); +} diff --git a/playground/src/app/page.module.css b/playground/src/app/page.module.css new file mode 100644 index 0000000..9411a5e --- /dev/null +++ b/playground/src/app/page.module.css @@ -0,0 +1,229 @@ +.main { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + padding: 6rem; + min-height: 100vh; +} + +.description { + display: inherit; + justify-content: inherit; + align-items: inherit; + font-size: 0.85rem; + max-width: var(--max-width); + width: 100%; + z-index: 2; + font-family: var(--font-mono); +} + +.description a { + display: flex; + justify-content: center; + align-items: center; + gap: 0.5rem; +} + +.description p { + position: relative; + margin: 0; + padding: 1rem; + background-color: rgba(var(--callout-rgb), 0.5); + border: 1px solid rgba(var(--callout-border-rgb), 0.3); + border-radius: var(--border-radius); +} + +.code { + font-weight: 700; + font-family: var(--font-mono); +} + +.grid { + display: grid; + grid-template-columns: repeat(4, minmax(25%, auto)); + width: var(--max-width); + max-width: 100%; +} + +.card { + padding: 1rem 1.2rem; + border-radius: var(--border-radius); + background: rgba(var(--card-rgb), 0); + border: 1px solid rgba(var(--card-border-rgb), 0); + transition: background 200ms, border 200ms; +} + +.card span { + display: inline-block; + transition: transform 200ms; +} + +.card h2 { + font-weight: 600; + margin-bottom: 0.7rem; +} + +.card p { + margin: 0; + opacity: 0.6; + font-size: 0.9rem; + line-height: 1.5; + max-width: 30ch; +} + +.center { + display: flex; + justify-content: center; + align-items: center; + position: relative; + padding: 4rem 0; +} + +.center::before { + background: var(--secondary-glow); + border-radius: 50%; + width: 480px; + height: 360px; + margin-left: -400px; +} + +.center::after { + background: var(--primary-glow); + width: 240px; + height: 180px; + z-index: -1; +} + +.center::before, +.center::after { + content: ''; + left: 50%; + position: absolute; + filter: blur(45px); + transform: translateZ(0); +} + +.logo { + position: relative; +} +/* Enable hover only on non-touch devices */ +@media (hover: hover) and (pointer: fine) { + .card:hover { + background: rgba(var(--card-rgb), 0.1); + border: 1px solid rgba(var(--card-border-rgb), 0.15); + } + + .card:hover span { + transform: translateX(4px); + } +} + +@media (prefers-reduced-motion) { + .card:hover span { + transform: none; + } +} + +/* Mobile */ +@media (max-width: 700px) { + .content { + padding: 4rem; + } + + .grid { + grid-template-columns: 1fr; + margin-bottom: 120px; + max-width: 320px; + text-align: center; + } + + .card { + padding: 1rem 2.5rem; + } + + .card h2 { + margin-bottom: 0.5rem; + } + + .center { + padding: 8rem 0 6rem; + } + + .center::before { + transform: none; + height: 300px; + } + + .description { + font-size: 0.8rem; + } + + .description a { + padding: 1rem; + } + + .description p, + .description div { + display: flex; + justify-content: center; + position: fixed; + width: 100%; + } + + .description p { + align-items: center; + inset: 0 0 auto; + padding: 2rem 1rem 1.4rem; + border-radius: 0; + border: none; + border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25); + background: linear-gradient( + to bottom, + rgba(var(--background-start-rgb), 1), + rgba(var(--callout-rgb), 0.5) + ); + background-clip: padding-box; + backdrop-filter: blur(24px); + } + + .description div { + align-items: flex-end; + pointer-events: none; + inset: auto 0 0; + padding: 2rem; + height: 200px; + background: linear-gradient( + to bottom, + transparent 0%, + rgb(var(--background-end-rgb)) 40% + ); + z-index: 1; + } +} + +/* Tablet and Smaller Desktop */ +@media (min-width: 701px) and (max-width: 1120px) { + .grid { + grid-template-columns: repeat(2, 50%); + } +} + +@media (prefers-color-scheme: dark) { + .vercelLogo { + filter: invert(1); + } + + .logo { + filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70); + } +} + +@keyframes rotate { + from { + transform: rotate(360deg); + } + to { + transform: rotate(0deg); + } +} diff --git a/playground/src/app/page.tsx b/playground/src/app/page.tsx new file mode 100644 index 0000000..bff74e0 --- /dev/null +++ b/playground/src/app/page.tsx @@ -0,0 +1,23 @@ +import Link from "next/link"; + +export default function Home() { + return ( +
+
+

+ Let’s start authenticating
with KindeAuth +

+

Configure your app

+ + + Go to docs + +
+
+ ); +} diff --git a/playground/src/middleware.ts b/playground/src/middleware.ts new file mode 100644 index 0000000..4815803 --- /dev/null +++ b/playground/src/middleware.ts @@ -0,0 +1,12 @@ +import { + authMiddleware, + withAuth, +} from "@kinde-oss/kinde-auth-nextjs/middleware"; + +export default function middleware(req: Request) { + return withAuth(req); +} + +export const config = { + matcher: ["/dashboard"], +}; diff --git a/playground/tsconfig.json b/playground/tsconfig.json new file mode 100644 index 0000000..14bd9ea --- /dev/null +++ b/playground/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "noEmit": true, + "incremental": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "plugins": [ + { + "name": "next" + } + ] + }, + "include": [ + "next-env.d.ts", + ".next/types/**/*.ts", + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "node_modules" + ] +}