diff --git a/README.md b/README.md index 49cd5cd4e..9f4dcb9a1 100644 --- a/README.md +++ b/README.md @@ -390,16 +390,28 @@ export const auth0 = new Auth0Client({ async delete(id) { // delete the session using its ID }, + async deleteByLogoutToken({ sid, sub }: { sid: string; sub: string }) { + // optional method to be implemented when using Back-Channel Logout + }, }, }) ``` +## Back-Channel Logout + +The SDK can be configured to listen to [Back-Channel Logout](https://auth0.com/docs/authenticate/login/logout/back-channel-logout) events. By default, a route will be mounted `/auth/backchannel-logout` which will verify the logout token and call the `deleteByLogoutToken` method of your session store implementation to allow you to remove the session. + +To use Back-Channel Logout, you will need to provide a session store implementation as shown in the [Database sessions](#database-sessions) section above with the `deleteByLogoutToken` implemented. + +A `LogoutToken` object will be passed as the parameter to `deleteByLogoutToken` which will contain either a `sid` claim, a `sub` claim, or both. + ## Routes -The SDK mounts 5 routes: +The SDK mounts 6 routes: 1. `/auth/login`: the login route that the user will be redirected to to start a initiate an authentication transaction 2. `/auth/logout`: the logout route that must be addedto your Auth0 application's Allowed Logout URLs 3. `/auth/callback`: the callback route that must be addedto your Auth0 application's Allowed Callback URLs 4. `/auth/profile`: the route to check the user's session and return their attributes 5. `/auth/access-token`: the route to check the user's session and return an access token (which will be automatically refreshed if a refresh token is available) +6. `/auth/backchannel-logout`: the route that will receive a `logout_token` when a configured Back-Channel Logout initiator occurs diff --git a/examples/with-shadcn/.env.example b/examples/with-shadcn/.env.example new file mode 100644 index 000000000..cdb42dc8a --- /dev/null +++ b/examples/with-shadcn/.env.example @@ -0,0 +1,5 @@ +AUTH0_DOMAIN= +AUTH0_CLIENT_ID= +AUTH0_CLIENT_SECRET= +AUTH0_SECRET= +APP_BASE_URL= \ No newline at end of file diff --git a/examples/with-shadcn/.eslintrc.json b/examples/with-shadcn/.eslintrc.json new file mode 100644 index 000000000..372241854 --- /dev/null +++ b/examples/with-shadcn/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["next/core-web-vitals", "next/typescript"] +} diff --git a/examples/with-shadcn/.gitignore b/examples/with-shadcn/.gitignore new file mode 100644 index 000000000..d55ba1773 --- /dev/null +++ b/examples/with-shadcn/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# env files (can opt-in for commiting if needed) +.env* +!.env.example + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/examples/with-shadcn/README.md b/examples/with-shadcn/README.md new file mode 100644 index 000000000..e215bc4cc --- /dev/null +++ b/examples/with-shadcn/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/examples/with-shadcn/app/favicon.ico b/examples/with-shadcn/app/favicon.ico new file mode 100644 index 000000000..718d6fea4 Binary files /dev/null and b/examples/with-shadcn/app/favicon.ico differ diff --git a/examples/with-shadcn/app/fonts/GeistMonoVF.woff b/examples/with-shadcn/app/fonts/GeistMonoVF.woff new file mode 100644 index 000000000..f2ae185cb Binary files /dev/null and b/examples/with-shadcn/app/fonts/GeistMonoVF.woff differ diff --git a/examples/with-shadcn/app/fonts/GeistVF.woff b/examples/with-shadcn/app/fonts/GeistVF.woff new file mode 100644 index 000000000..1b62daacf Binary files /dev/null and b/examples/with-shadcn/app/fonts/GeistVF.woff differ diff --git a/examples/with-shadcn/app/globals.css b/examples/with-shadcn/app/globals.css new file mode 100644 index 000000000..a8144b699 --- /dev/null +++ b/examples/with-shadcn/app/globals.css @@ -0,0 +1,88 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + font-family: Arial, Helvetica, sans-serif; +} + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --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%; + --radius: 0.5rem; + --sidebar-background: 0 0% 98%; + --sidebar-foreground: 240 5.3% 26.1%; + --sidebar-primary: 240 5.9% 10%; + --sidebar-primary-foreground: 0 0% 98%; + --sidebar-accent: 240 4.8% 95.9%; + --sidebar-accent-foreground: 240 5.9% 10%; + --sidebar-border: 220 13% 91%; + --sidebar-ring: 217.2 91.2% 59.8%; + } + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --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%; + --sidebar-background: 240 5.9% 10%; + --sidebar-foreground: 240 4.8% 95.9%; + --sidebar-primary: 224.3 76.3% 48%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 240 3.7% 15.9%; + --sidebar-accent-foreground: 240 4.8% 95.9%; + --sidebar-border: 240 3.7% 15.9%; + --sidebar-ring: 217.2 91.2% 59.8%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/examples/with-shadcn/app/layout.tsx b/examples/with-shadcn/app/layout.tsx new file mode 100644 index 000000000..a36cde01c --- /dev/null +++ b/examples/with-shadcn/app/layout.tsx @@ -0,0 +1,35 @@ +import type { Metadata } from "next"; +import localFont from "next/font/local"; +import "./globals.css"; + +const geistSans = localFont({ + src: "./fonts/GeistVF.woff", + variable: "--font-geist-sans", + weight: "100 900", +}); +const geistMono = localFont({ + src: "./fonts/GeistMonoVF.woff", + variable: "--font-geist-mono", + weight: "100 900", +}); + +export const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + ); +} diff --git a/examples/with-shadcn/app/page.tsx b/examples/with-shadcn/app/page.tsx new file mode 100644 index 000000000..caa888963 --- /dev/null +++ b/examples/with-shadcn/app/page.tsx @@ -0,0 +1,52 @@ +import { AppSidebar } from "@/components/app-sidebar" +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator, +} from "@/components/ui/breadcrumb" +import { Separator } from "@/components/ui/separator" +import { + SidebarInset, + SidebarProvider, + SidebarTrigger, +} from "@/components/ui/sidebar" + +export default function Page() { + return ( + + + +
+
+ + + + + + + Building Your Application + + + + + Data Fetching + + + +
+
+
+
+
+
+
+
+
+
+ + + ) +} diff --git a/examples/with-shadcn/components.json b/examples/with-shadcn/components.json new file mode 100644 index 000000000..bcec1f91f --- /dev/null +++ b/examples/with-shadcn/components.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + } +} \ No newline at end of file diff --git a/examples/with-shadcn/components/app-sidebar.tsx b/examples/with-shadcn/components/app-sidebar.tsx new file mode 100644 index 000000000..0b9b4d29c --- /dev/null +++ b/examples/with-shadcn/components/app-sidebar.tsx @@ -0,0 +1,180 @@ +"use client" + +import * as React from "react" +import { + BookOpen, + Bot, + Command, + Frame, + LifeBuoy, + Map, + PieChart, + Send, + Settings2, + SquareTerminal, +} from "lucide-react" + +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarHeader, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, +} from "@/components/ui/sidebar" +import { NavMain } from "@/components/nav-main" +import { NavProjects } from "@/components/nav-projects" +import { NavSecondary } from "@/components/nav-secondary" +import { NavUser } from "@/components/nav-user" + +const data = { + navMain: [ + { + title: "Playground", + url: "#", + icon: SquareTerminal, + isActive: true, + items: [ + { + title: "History", + url: "#", + }, + { + title: "Starred", + url: "#", + }, + { + title: "Settings", + url: "#", + }, + ], + }, + { + title: "Models", + url: "#", + icon: Bot, + items: [ + { + title: "Genesis", + url: "#", + }, + { + title: "Explorer", + url: "#", + }, + { + title: "Quantum", + url: "#", + }, + ], + }, + { + title: "Documentation", + url: "#", + icon: BookOpen, + items: [ + { + title: "Introduction", + url: "#", + }, + { + title: "Get Started", + url: "#", + }, + { + title: "Tutorials", + url: "#", + }, + { + title: "Changelog", + url: "#", + }, + ], + }, + { + title: "Settings", + url: "#", + icon: Settings2, + items: [ + { + title: "General", + url: "#", + }, + { + title: "Team", + url: "#", + }, + { + title: "Billing", + url: "#", + }, + { + title: "Limits", + url: "#", + }, + ], + }, + ], + navSecondary: [ + { + title: "Support", + url: "#", + icon: LifeBuoy, + }, + { + title: "Feedback", + url: "#", + icon: Send, + }, + ], + projects: [ + { + name: "Design Engineering", + url: "#", + icon: Frame, + }, + { + name: "Sales & Marketing", + url: "#", + icon: PieChart, + }, + { + name: "Travel", + url: "#", + icon: Map, + }, + ], +} + +export function AppSidebar({ ...props }: React.ComponentProps) { + return ( + + + + + + +
+ +
+
+ Acme Inc + Enterprise +
+
+
+
+
+
+ + + + + + + + +
+ ) +} diff --git a/examples/with-shadcn/components/nav-main.tsx b/examples/with-shadcn/components/nav-main.tsx new file mode 100644 index 000000000..3481f96e7 --- /dev/null +++ b/examples/with-shadcn/components/nav-main.tsx @@ -0,0 +1,78 @@ +"use client" + +import { ChevronRight, type LucideIcon } from "lucide-react" + +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible" +import { + SidebarGroup, + SidebarGroupLabel, + SidebarMenu, + SidebarMenuAction, + SidebarMenuButton, + SidebarMenuItem, + SidebarMenuSub, + SidebarMenuSubButton, + SidebarMenuSubItem, +} from "@/components/ui/sidebar" + +export function NavMain({ + items, +}: { + items: { + title: string + url: string + icon: LucideIcon + isActive?: boolean + items?: { + title: string + url: string + }[] + }[] +}) { + return ( + + Platform + + {items.map((item) => ( + + + + + + {item.title} + + + {item.items?.length ? ( + <> + + + + Toggle + + + + + {item.items?.map((subItem) => ( + + + + {subItem.title} + + + + ))} + + + + ) : null} + + + ))} + + + ) +} diff --git a/examples/with-shadcn/components/nav-projects.tsx b/examples/with-shadcn/components/nav-projects.tsx new file mode 100644 index 000000000..81d580537 --- /dev/null +++ b/examples/with-shadcn/components/nav-projects.tsx @@ -0,0 +1,89 @@ +"use client" + +import { + Folder, + MoreHorizontal, + Share, + Trash2, + type LucideIcon, +} from "lucide-react" + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { + SidebarGroup, + SidebarGroupLabel, + SidebarMenu, + SidebarMenuAction, + SidebarMenuButton, + SidebarMenuItem, + useSidebar, +} from "@/components/ui/sidebar" + +export function NavProjects({ + projects, +}: { + projects: { + name: string + url: string + icon: LucideIcon + }[] +}) { + const { isMobile } = useSidebar() + + return ( + + Projects + + {projects.map((item) => ( + + + + + {item.name} + + + + + + + More + + + + + + View Project + + + + Share Project + + + + + Delete Project + + + + + ))} + + + + More + + + + + ) +} diff --git a/examples/with-shadcn/components/nav-secondary.tsx b/examples/with-shadcn/components/nav-secondary.tsx new file mode 100644 index 000000000..a931a7e90 --- /dev/null +++ b/examples/with-shadcn/components/nav-secondary.tsx @@ -0,0 +1,40 @@ +import * as React from "react" +import { type LucideIcon } from "lucide-react" + +import { + SidebarGroup, + SidebarGroupContent, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, +} from "@/components/ui/sidebar" + +export function NavSecondary({ + items, + ...props +}: { + items: { + title: string + url: string + icon: LucideIcon + }[] +} & React.ComponentPropsWithoutRef) { + return ( + + + + {items.map((item) => ( + + + + + {item.title} + + + + ))} + + + + ) +} diff --git a/examples/with-shadcn/components/nav-user.tsx b/examples/with-shadcn/components/nav-user.tsx new file mode 100644 index 000000000..7b0b6eac0 --- /dev/null +++ b/examples/with-shadcn/components/nav-user.tsx @@ -0,0 +1,140 @@ +"use client" + +import { useUser } from "@auth0/nextjs-auth0" +import { + BadgeCheck, + Bell, + ChevronsUpDown, + CreditCard, + LogIn, + LogOut, + Sparkles, +} from "lucide-react" + +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + useSidebar, +} from "@/components/ui/sidebar" + +import { Skeleton } from "./ui/skeleton" + +export function NavUser() { + const { isMobile } = useSidebar() + const { user, isLoading } = useUser() + + if (isLoading) { + return ( + + + + + + + + ) + } + + if (!user) { + return ( + + + + + + Log in to your account + + + + + ) + } + + return ( + + + + + + + + + {user.name?.charAt(0).toUpperCase()} + + +
+ {user.name} + {user.email} +
+ +
+
+ + +
+ + + + {user.name?.charAt(0).toUpperCase()} + + +
+ {user.name} + {user.email} +
+
+
+ + + + + Upgrade to Pro + + + + + + + Account + + + + Billing + + + + Notifications + + + + + + + Log out + + +
+
+
+
+ ) +} diff --git a/examples/with-shadcn/components/ui/avatar.tsx b/examples/with-shadcn/components/ui/avatar.tsx new file mode 100644 index 000000000..51e507ba9 --- /dev/null +++ b/examples/with-shadcn/components/ui/avatar.tsx @@ -0,0 +1,50 @@ +"use client" + +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Avatar.displayName = AvatarPrimitive.Root.displayName + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarImage.displayName = AvatarPrimitive.Image.displayName + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/examples/with-shadcn/components/ui/breadcrumb.tsx b/examples/with-shadcn/components/ui/breadcrumb.tsx new file mode 100644 index 000000000..cfdc9ac2e --- /dev/null +++ b/examples/with-shadcn/components/ui/breadcrumb.tsx @@ -0,0 +1,115 @@ +import * as React from "react" +import { ChevronRightIcon, DotsHorizontalIcon } from "@radix-ui/react-icons" +import { Slot } from "@radix-ui/react-slot" + +import { cn } from "@/lib/utils" + +const Breadcrumb = React.forwardRef< + HTMLElement, + React.ComponentPropsWithoutRef<"nav"> & { + separator?: React.ReactNode + } +>(({ ...props }, ref) =>