Adds oauth providers, fixes bottom page navigation with mobile support, adds apple client generator

This commit is contained in:
Madison 2025-06-24 14:44:00 -05:00
parent 52ab56f118
commit a73e4d0b38
24 changed files with 1491 additions and 92 deletions

View File

@ -22,6 +22,50 @@ pages:
- path: getting-started/example-pages.mdx
platforms: ["js"] # Only vanilla JS
# Auth Providers - Available for all platforms since OAuth is universal
- path: getting-started/auth-providers/index.mdx
platforms: ["next", "react", "js", "python"]
- path: getting-started/auth-providers/github.mdx
platforms: ["next", "react", "js", "python"]
- path: getting-started/auth-providers/google.mdx
platforms: ["next", "react", "js", "python"]
- path: getting-started/auth-providers/facebook.mdx
platforms: ["next", "react", "js", "python"]
- path: getting-started/auth-providers/microsoft.mdx
platforms: ["next", "react", "js", "python"]
- path: getting-started/auth-providers/spotify.mdx
platforms: ["next", "react", "js", "python"]
- path: getting-started/auth-providers/discord.mdx
platforms: ["next", "react", "js", "python"]
- path: getting-started/auth-providers/gitlab.mdx
platforms: ["next", "react", "js", "python"]
- path: getting-started/auth-providers/apple.mdx
platforms: ["next", "react", "js", "python"]
- path: getting-started/auth-providers/bitbucket.mdx
platforms: ["next", "react", "js", "python"]
- path: getting-started/auth-providers/linkedin.mdx
platforms: ["next", "react", "js", "python"]
- path: getting-started/auth-providers/x-twitter.mdx
platforms: ["next", "react", "js", "python"]
# Advanced auth methods - More frontend-focused
- path: getting-started/auth-providers/passkey.mdx
platforms: ["next", "react", "js"] # No Python (frontend feature)
- path: getting-started/auth-providers/two-factor-auth.mdx
platforms: ["next", "react", "js"] # No Python (frontend feature)
- path: getting-started/production.mdx
platforms: ["next", "react", "js"] # No Python

View File

@ -219,9 +219,28 @@ function generateMetaFiles() {
}
// Regular page
else {
const pagePath = `${page}.mdx`;
if (shouldIncludeFileForPlatform(platform, pagePath)) {
currentSectionPages.push(page);
// Check if this is actually a folder reference vs a page reference
// A folder reference should have a corresponding directory in templates
const folderPath = path.join(TEMPLATE_DIR, page);
const isActualFolder = fs.existsSync(folderPath) && fs.statSync(folderPath).isDirectory();
if (isActualFolder) {
// This is a folder reference - check if folder has content for this platform
const hasContentInFolder = platformConfig.pages.some(configPage =>
configPage.path.startsWith(`${page}/`) &&
configPage.platforms.includes(platform)
);
if (hasContentInFolder) {
currentSectionPages.push(page);
}
} else {
// This is a regular page reference
const pagePath = `${page}.mdx`;
const shouldInclude = shouldIncludeFileForPlatform(platform, pagePath);
if (shouldInclude) {
currentSectionPages.push(page);
}
}
}
}

View File

@ -0,0 +1,321 @@
'use client';
import { useState } from 'react';
import { cn } from '../lib/cn';
import { Info } from './mdx/info';
import { buttonVariants } from './ui/button';
// Simple Button component using existing buttonVariants
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
variant?: 'primary' | 'outline' | 'ghost' | 'secondary',
size?: 'sm' | 'icon' | 'icon-sm',
children: React.ReactNode,
};
const Button = ({ variant = 'primary', size, className, children, ...props }: ButtonProps) => {
return (
<button
className={cn(buttonVariants({ color: variant, size }), className)}
{...props}
>
{children}
</button>
);
};
// Input component that matches the docs theme
type InputProps = React.InputHTMLAttributes<HTMLInputElement> & {
label?: string,
labelOptional?: string,
reveal?: boolean,
copy?: boolean,
};
const Input = ({
label,
labelOptional,
reveal,
copy,
className,
...props
}: InputProps) => {
const [isRevealed, setIsRevealed] = useState(false);
const [copied, setCopied] = useState(false);
const handleCopy = async () => {
if (props.value && typeof props.value === 'string') {
await navigator.clipboard.writeText(props.value);
setCopied(true);
void setTimeout(() => setCopied(false), 2000);
}
};
return (
<div className="space-y-1">
{label && (
<div className="flex items-center gap-1.5">
<label className="text-xs font-medium leading-none text-gray-600 dark:text-gray-400">
{label}
</label>
{labelOptional && (
<span className="text-xs text-muted-foreground">
{labelOptional}
</span>
)}
</div>
)}
<div className="relative">
<input
type={reveal && !isRevealed ? 'password' : 'text'}
className={cn(
'flex h-7 w-full rounded border border-input bg-transparent px-2 py-1 text-xs',
'file:border-0 file:bg-transparent file:text-xs file:font-medium',
'placeholder:text-muted-foreground',
'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
'disabled:cursor-not-allowed disabled:opacity-50',
className
)}
{...props}
/>
<div className="absolute right-1.5 top-1/2 -translate-y-1/2 flex items-center gap-0.5">
{reveal && (
<button
type="button"
onClick={() => setIsRevealed(!isRevealed)}
className="p-0.5 text-muted-foreground hover:text-foreground transition-colors"
>
{isRevealed ? (
<svg className="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L3 3m6.878 6.878L21 21" />
</svg>
) : (
<svg className="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
)}
</button>
)}
{copy && (
<button
type="button"
onClick={() => {
handleCopy().catch((error) => {
console.error('Failed to copy:', error);
});
}}
className="p-0.5 text-muted-foreground hover:text-foreground transition-colors"
>
{copied ? (
<svg className="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
) : (
<svg className="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg>
)}
</button>
)}
</div>
</div>
</div>
);
};
function base64URL(value: string) {
return globalThis.btoa(value).replace(/[=]/g, '').replace(/[+]/g, '-').replace(/[\/]/g, '_');
}
/*
Convert a string into an ArrayBuffer
from https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
*/
function stringToArrayBuffer(value: string) {
const buf = new ArrayBuffer(value.length);
const bufView = new Uint8Array(buf);
for (let i = 0; i < value.length; i++) {
bufView[i] = value.charCodeAt(i);
}
return buf;
}
function arrayBufferToString(buf: ArrayBuffer) {
return String.fromCharCode.apply(null, Array.from(new Uint8Array(buf)));
}
const generateAppleSecretKey = async (
kid: string,
iss: string,
sub: string,
file: File
): Promise<{ kid: string, jwt: string, exp: number }> => {
if (!kid) {
const match = file.name.match(/AuthKey_([^.]+)[.].*$/i);
if (match && match[1]) {
kid = match[1];
}
}
if (!kid) {
throw new Error(
`No Key ID provided. The file "${file.name}" does not follow the AuthKey_XXXXXXXXXX.p8 pattern. Please provide a Key ID manually.`
);
}
const contents = await file.text();
if (!contents.match(/^\s*-+BEGIN PRIVATE KEY-+[^-]+-+END PRIVATE KEY-+\s*$/i)) {
throw new Error(`Chosen file does not appear to be a PEM encoded PKCS8 private key file.`);
}
// remove PEM headers and spaces
const pkcs8 = stringToArrayBuffer(
globalThis.atob(contents.replace(/-+[^-]+-+/g, '').replace(/\s+/g, ''))
);
const privateKey = await globalThis.crypto.subtle.importKey(
'pkcs8',
pkcs8,
{
name: 'ECDSA',
namedCurve: 'P-256',
},
true,
['sign']
);
const iat = Math.floor(Date.now() / 1000);
const exp = iat + 180 * 24 * 60 * 60;
const jwt = [
base64URL(JSON.stringify({ typ: 'JWT', kid, alg: 'ES256' })),
base64URL(
JSON.stringify({
iss,
sub,
iat,
exp,
aud: 'https://appleid.apple.com',
})
),
];
const signature = await globalThis.crypto.subtle.sign(
{
name: 'ECDSA',
hash: 'SHA-256',
},
privateKey,
stringToArrayBuffer(jwt.join('.'))
);
jwt.push(base64URL(arrayBufferToString(signature)));
return { kid, jwt: jwt.join('.'), exp };
};
const AppleSecretGenerator = () => {
const [file, setFile] = useState<File | null>(null);
const [teamID, setTeamID] = useState('');
const [serviceID, setServiceID] = useState('');
const [keyID, setKeyID] = useState('');
const [secretKey, setSecretKey] = useState('');
const [expiresAt, setExpiresAt] = useState('');
const [error, setError] = useState('');
return (
<div className="border border-input rounded-lg p-3 bg-background/50 space-y-3 max-w-xl">
<Input
label="Account ID"
labelOptional="Found in the upper-right corner of Apple Developer Center."
placeholder="Apple Developer account ID, 10 alphanumeric digits"
value={teamID}
onChange={(e) => setTeamID(e.target.value.trim())}
/>
<Input
label="Service ID"
labelOptional="Found under Certificates, Identifiers & Profiles in Apple Developer Center."
placeholder="ID of the service, example: com.example.app.service"
value={serviceID}
onChange={(e) => setServiceID(e.target.value.trim())}
/>
<Input
label="Key ID"
labelOptional="Optional - extracted from filename or enter manually."
placeholder="Extracted from filename, AuthKey_XXXXXXXXXX.p8"
value={keyID}
onChange={(e) => setKeyID(e.target.value.trim())}
/>
<div className="space-y-1">
<label className="text-xs font-medium leading-none text-gray-600 dark:text-gray-400">
Private Key File (.p8)
</label>
<input
type="file"
accept=".p8"
onChange={(e) => {
setFile(e.target.files?.[0] || null);
}}
className={cn(
'flex h-7 w-full rounded border border-input bg-transparent px-2 py-1 text-xs',
'file:border-0 file:bg-transparent file:text-xs file:font-medium file:text-foreground',
'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
'disabled:cursor-not-allowed disabled:opacity-50'
)}
/>
</div>
<Button
variant="primary"
disabled={!(teamID.length === 10 && serviceID && file)}
onClick={() => {
(async () => {
setError('');
try {
const { kid, jwt, exp } = await generateAppleSecretKey(
keyID,
teamID,
serviceID,
file!
);
setKeyID(kid);
setSecretKey(jwt);
setExpiresAt(new Date(exp * 1000).toString());
setError('');
} catch (e: unknown) {
const errorMessage = e instanceof Error ? e.message : 'An unknown error occurred';
setError(errorMessage);
console.error(e);
}
})().catch((error) => {
console.error('Failed to generate secret:', error);
});
}}
className="text-xs px-3 py-1.5"
>
Generate Secret Key
</Button>
{error && (
<Info type="warning">
{error}
</Info>
)}
{secretKey && (
<Input
label="Secret Key"
labelOptional={`Valid until: ${expiresAt}. Make sure you generate a new one before then!`}
value={secretKey}
reveal
copy
readOnly
/>
)}
</div>
);
};
export default AppleSecretGenerator;

View File

@ -1,11 +1,10 @@
'use client';
import { usePathname } from 'fumadocs-core/framework';
import Link from 'fumadocs-core/link';
import { ChevronsUpDown } from 'lucide-react';
import { type ComponentProps, type ReactNode, useMemo, useState } from 'react';
import { ChevronDown } from 'lucide-react';
import { type ComponentProps, type ReactNode, useEffect, useMemo, useRef, useState } from 'react';
import { cn } from '../../lib/cn';
import { isActive } from '../../lib/is-active';
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
export type Option = {
/**
@ -25,6 +24,23 @@ export type Option = {
props?: ComponentProps<'a'>,
}
// Platform-specific colors matching homepage
const platformColors: Record<string, string> = {
'Next.js': 'rgb(59, 130, 246)', // Blue
'React': 'rgb(16, 185, 129)', // Green
'JavaScript': 'rgb(245, 158, 11)', // Yellow
'Python': 'rgb(168, 85, 247)', // Purple
'Stack Auth Next.js': 'rgb(59, 130, 246)',
'Stack Auth React': 'rgb(16, 185, 129)',
'Stack Auth JavaScript': 'rgb(245, 158, 11)',
'Stack Auth Python': 'rgb(168, 85, 247)',
};
function getPlatformColor(title: ReactNode): string {
const titleStr = String(title);
return platformColors[titleStr] || 'rgb(100, 116, 139)'; // fallback color
}
export function RootToggle({
options,
...props
@ -32,7 +48,9 @@ export function RootToggle({
options: Option[],
} & ComponentProps<'button'>) {
const [open, setOpen] = useState(false);
const [hoveredOption, setHoveredOption] = useState<Option | null>(null);
const pathname = usePathname();
const dropdownRef = useRef<HTMLDivElement>(null);
const selected = useMemo(() => {
return options.findLast((item) =>
@ -44,78 +62,163 @@ export function RootToggle({
);
}, [options, pathname]);
const onClick = () => {
setOpen(false);
};
// Click outside to close dropdown
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setOpen(false);
setHoveredOption(null);
}
};
if (open) {
document.addEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [open]);
const selectedColor = selected ? getPlatformColor(selected.title) : 'rgb(100, 116, 139)';
return (
<div className="w-full">
{/* Platform selector label */}
<div className="mb-2">
<div className="mb-3">
<span className="text-xs font-bold text-fd-foreground uppercase tracking-wider">
Platform
</span>
</div>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger
<div className="relative" ref={dropdownRef}>
<button
{...props}
onClick={() => setOpen(!open)}
className={cn(
'w-full flex items-center justify-between gap-2 px-3 py-2.5 rounded-lg border border-fd-border bg-fd-background hover:bg-fd-muted/50 transition-colors',
'w-full flex items-center justify-between px-4 py-3 rounded-lg border transition-all duration-300',
'bg-fd-background border-fd-border hover:shadow-xl hover:-translate-y-0.5',
props.className,
)}
style={{
...(open && {
borderColor: selectedColor,
boxShadow: `0 4px 20px ${selectedColor}20`,
}),
}}
onMouseEnter={(e) => {
e.currentTarget.style.borderColor = selectedColor;
e.currentTarget.style.boxShadow = `0 4px 20px ${selectedColor}20`;
}}
onMouseLeave={(e) => {
if (!open) {
e.currentTarget.style.borderColor = '';
e.currentTarget.style.boxShadow = '';
}
}}
>
<div className="flex items-center gap-2 flex-1 min-w-0">
<div className="flex items-center gap-3 flex-1 min-w-0">
{selected ? (
<Item {...selected} compact />
<>
{selected.icon}
<div className="flex-1 text-start min-w-0">
<p
className="font-semibold text-sm truncate"
style={{ color: selectedColor }}
>
{selected.title}
</p>
{selected.description && (
<p className="text-xs text-fd-muted-foreground truncate">
{selected.description}
</p>
)}
</div>
</>
) : (
<span className="text-sm text-fd-muted-foreground">Select platform</span>
)}
</div>
<ChevronsUpDown className="size-4 text-fd-muted-foreground flex-shrink-0" />
</PopoverTrigger>
<PopoverContent className="w-(--radix-popover-trigger-width) overflow-hidden p-1">
{options.map((item) => (
<Link
key={item.url}
href={item.url}
onClick={onClick}
{...item.props}
className={cn(
'flex w-full flex-row items-center gap-2 px-3 py-2.5 rounded-md transition-colors',
selected === item
? 'bg-fd-primary/10 text-fd-primary'
: 'hover:bg-fd-muted/50',
item.props?.className,
<ChevronDown
className={cn(
'size-5 flex-shrink-0 transition-transform duration-300',
open && 'rotate-180'
)}
>
<Item {...item} />
</Link>
))}
</PopoverContent>
</Popover>
style={{ color: selectedColor }}
/>
</button>
{open && (
<div className="absolute top-full left-0 right-0 mt-2 bg-fd-background/95 backdrop-blur-lg border-2 border-fd-border rounded-lg shadow-2xl z-50 overflow-hidden">
{options.map((item) => {
const isSelected = selected === item;
const isHovered = hoveredOption === item;
const isHighlighted = isSelected || isHovered;
const itemColor = getPlatformColor(item.title);
return (
<Link
key={item.url}
href={item.url}
onClick={() => {
setOpen(false);
setHoveredOption(null);
}}
onMouseEnter={() => setHoveredOption(item)}
onMouseLeave={() => setHoveredOption(null)}
{...item.props}
className={cn(
'w-full px-4 py-3 text-left transition-all duration-200 border-l-4 border-transparent block',
isHighlighted ? 'bg-fd-muted/70' : 'hover:bg-fd-muted/30',
item.props?.className,
)}
style={{
borderLeftColor: isHighlighted ? itemColor : 'transparent',
backgroundColor: isHighlighted ? `${itemColor}15` : undefined,
}}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3 flex-1 min-w-0">
{item.icon}
<div className="flex-1 text-start min-w-0">
<p
className={cn(
'font-medium text-sm truncate transition-all duration-200',
isHighlighted && 'font-semibold'
)}
style={{ color: isHighlighted ? itemColor : undefined }}
>
{item.title}
</p>
{item.description && (
<p className={cn(
'text-xs mt-0.5 truncate transition-all duration-200',
isHighlighted ? 'text-fd-muted-foreground' : 'text-fd-muted-foreground/70'
)}>
{item.description}
</p>
)}
</div>
</div>
{isSelected && (
<div
className="w-2 h-2 rounded-full transition-all duration-200"
style={{ backgroundColor: itemColor }}
/>
)}
{isHovered && !isSelected && (
<div
className="w-1.5 h-1.5 rounded-full transition-all duration-200"
style={{ backgroundColor: itemColor, opacity: 0.6 }}
/>
)}
</div>
</Link>
);
})}
</div>
)}
</div>
</div>
);
}
function Item({ compact = false, ...props }: Option & { compact?: boolean }) {
return (
<>
<>{props.icon}</>
<div className="flex-1 text-start min-w-0">
<p className={cn(
"font-medium truncate",
compact ? "text-sm" : "text-[15px] md:text-sm"
)}>
{props.title}
</p>
{props.description && !compact ? (
<p className="text-sm text-fd-muted-foreground md:text-xs truncate">
{props.description}
</p>
) : null}
</div>
</>
);
}

View File

@ -2,7 +2,7 @@
import type { PageTree } from 'fumadocs-core/server';
import { ChevronDown, ChevronRight } from 'lucide-react';
import { usePathname } from 'next/navigation';
import { useMemo, useState } from 'react';
import React, { useMemo, useState } from 'react';
import { getCurrentPlatform } from '../../lib/platform-utils';
import { ApiSidebarContent } from './api/api-sidebar';
import { SdkSidebarContent } from './docs';
@ -101,6 +101,75 @@ function MobileCollapsibleSection({
);
}
// Clickable collapsible section component for mobile - for folders with index pages
function MobileClickableCollapsibleSection({
title,
href,
children,
defaultOpen = false
}: {
title: string,
href: string,
children: React.ReactNode,
defaultOpen?: boolean,
}) {
const pathname = usePathname();
const isActive = pathname === href || pathname.startsWith(href + '/');
const [isOpen, setIsOpen] = useState(defaultOpen);
const containerRef = React.useRef<HTMLDivElement>(null);
// Close when clicking outside
React.useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
}
if (isOpen) {
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}
}, [isOpen]);
return (
<div className="space-y-1" ref={containerRef}>
<div className="group">
<a
href={href}
className={`flex items-center justify-between w-full px-2 py-1.5 rounded-md text-xs transition-colors ${
isActive
? 'bg-fd-primary/10 text-fd-primary font-medium'
: 'text-fd-muted-foreground hover:text-fd-foreground hover:bg-fd-muted/50'
}`}
onClick={() => setIsOpen(true)}
>
<span className="flex-1">{title}</span>
<button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setIsOpen(!isOpen);
}}
className="opacity-0 group-hover:opacity-100 transition-opacity p-0.5 rounded hover:bg-fd-muted/30"
>
{isOpen ? (
<ChevronDown className="h-3 w-3" />
) : (
<ChevronRight className="h-3 w-3" />
)}
</button>
</a>
</div>
{isOpen && (
<div className="ml-4 space-y-1">
{children}
</div>
)}
</div>
);
}
// Recursive component to render page tree items for mobile
function MobilePageTreeItem({ item, currentPlatform }: { item: PageTree.Node, currentPlatform?: string }) {
const pathname = usePathname();
@ -115,16 +184,27 @@ function MobilePageTreeItem({ item, currentPlatform }: { item: PageTree.Node, cu
const isCurrentPath = folderUrl && pathname.startsWith(folderUrl);
const itemName = typeof item.name === 'string' ? item.name : '';
// If folder has an index page, make the title clickable
if (hasIndexPage) {
return (
<MobileClickableCollapsibleSection
title={itemName || 'Folder'}
href={item.index!.url}
defaultOpen={!!isCurrentPath}
>
{item.children.map((child, index) => (
<MobilePageTreeItem key={child.type === 'page' ? child.url : index} item={child} currentPlatform={currentPlatform} />
))}
</MobileClickableCollapsibleSection>
);
}
// If no index page, use regular accordion trigger
return (
<MobileCollapsibleSection
title={itemName || 'Folder'}
defaultOpen={!!isCurrentPath}
>
{hasIndexPage && (
<MobileSidebarLink href={item.index!.url} external={item.index!.external}>
Overview
</MobileSidebarLink>
)}
{item.children.map((child, index) => (
<MobilePageTreeItem key={child.type === 'page' ? child.url : index} item={child} currentPlatform={currentPlatform} />
))}

View File

@ -43,7 +43,7 @@ import {
import { TreeContextProvider } from 'fumadocs-ui/contexts/tree';
import { ArrowLeft, ChevronDown, ChevronRight, Languages } from 'lucide-react';
import { usePathname } from 'next/navigation';
import { type HTMLAttributes, type ReactNode, useMemo, useState } from 'react';
import { type HTMLAttributes, type ReactNode, useEffect, useMemo, useRef, useState } from 'react';
import { cn } from '../../lib/cn';
import { getCurrentPlatform } from '../../lib/platform-utils';
import {
@ -155,6 +155,75 @@ function CollapsibleSection({
);
}
// Clickable collapsible section component - for folders with index pages
function ClickableCollapsibleSection({
title,
href,
children,
defaultOpen = false
}: {
title: string,
href: string,
children: ReactNode,
defaultOpen?: boolean,
}) {
const pathname = usePathname();
const isActive = pathname === href || pathname.startsWith(href + '/');
const [isOpen, setIsOpen] = useState(defaultOpen);
const containerRef = useRef<HTMLDivElement>(null);
// Close when clicking outside
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
}
if (isOpen) {
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}
}, [isOpen]);
return (
<div className="space-y-1" ref={containerRef}>
<div className="group">
<Link
href={href}
className={`flex items-center justify-between w-full px-2 py-1.5 rounded-md text-xs transition-colors ${
isActive
? 'bg-fd-primary/10 text-fd-primary font-medium'
: 'text-fd-muted-foreground hover:text-fd-foreground hover:bg-fd-muted/50'
}`}
onClick={() => setIsOpen(true)}
>
<span className="flex-1">{title}</span>
<button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setIsOpen(!isOpen);
}}
className="opacity-0 group-hover:opacity-100 transition-opacity p-0.5 rounded hover:bg-fd-muted/30"
>
{isOpen ? (
<ChevronDown className="h-3 w-3" />
) : (
<ChevronRight className="h-3 w-3" />
)}
</button>
</Link>
</div>
{isOpen && (
<div className="ml-4 space-y-1">
{children}
</div>
)}
</div>
);
}
// SDK-specific sidebar content
export function SdkSidebarContent() {
const pathname = usePathname();
@ -261,16 +330,27 @@ function PageTreeItem({ item, currentPlatform }: { item: PageTree.Node, currentP
const isCurrentPath = folderUrl && pathname.startsWith(folderUrl);
const itemName = typeof item.name === 'string' ? item.name : '';
// If folder has an index page, make the title clickable
if (hasIndexPage) {
return (
<ClickableCollapsibleSection
title={itemName || 'Folder'}
href={item.index!.url}
defaultOpen={!!isCurrentPath}
>
{item.children.map((child, index) => (
<PageTreeItem key={child.type === 'page' ? child.url : index} item={child} currentPlatform={currentPlatform} />
))}
</ClickableCollapsibleSection>
);
}
// If no index page, use regular accordion trigger
return (
<CollapsibleSection
title={itemName || 'Folder'}
defaultOpen={!!isCurrentPath}
>
{hasIndexPage && (
<DocsSidebarLink href={item.index!.url} external={item.index!.external}>
Overview
</DocsSidebarLink>
)}
{item.children.map((child, index) => (
<PageTreeItem key={child.type === 'page' ? child.url : index} item={child} currentPlatform={currentPlatform} />
))}

View File

@ -317,14 +317,9 @@ export function Footer({ items }: FooterProps) {
}, [items, pathname, root]);
return (
<div
className={cn(
'@container grid gap-4 pb-6',
previous && next ? 'grid-cols-2' : 'grid-cols-1',
)}
>
{previous ? <FooterItem item={previous} index={0} /> : null}
{next ? <FooterItem item={next} index={1} /> : null}
<div className="flex items-center justify-center gap-16 pt-20 mt-20 border-t border-fd-border/40">
{previous ? <FooterItem item={previous} index={0} /> : <div className="w-[260px]" />}
{next ? <FooterItem item={next} index={1} /> : <div className="w-[260px]" />}
</div>
);
}
@ -332,27 +327,31 @@ export function Footer({ items }: FooterProps) {
function FooterItem({ item, index }: { item: Item, index: 0 | 1 }) {
const { text } = useI18n();
const Icon = index === 0 ? ChevronLeft : ChevronRight;
const isNext = index === 1;
return (
<Link
href={item.url}
className={cn(
'flex flex-col gap-2 rounded-lg border p-4 text-sm transition-colors hover:bg-fd-accent/80 hover:text-fd-accent-foreground @max-lg:col-span-full',
index === 1 && 'text-end',
'group flex items-center gap-4 px-6 py-5 rounded-md text-sm transition-all duration-300',
'w-[260px] border border-fd-border/50 bg-fd-background/60 backdrop-blur-sm',
'hover:border-fd-border hover:bg-fd-background shadow-sm hover:shadow-md',
'hover:ring-1 hover:ring-fd-primary/10',
isNext && 'flex-row-reverse text-right'
)}
>
<div
className={cn(
'inline-flex items-center gap-1.5 font-medium',
index === 1 && 'flex-row-reverse',
)}
>
<Icon className="-mx-1 size-4 shrink-0 rtl:rotate-180" />
<p>{item.name}</p>
<Icon className={cn(
'size-5 text-fd-muted-foreground/80 group-hover:text-fd-primary transition-colors duration-300'
)} />
<div className={cn('flex flex-col gap-1.5 min-w-0 flex-1', isNext && 'items-end')}>
<span className="text-[11px] text-fd-muted-foreground/60 font-semibold uppercase tracking-[0.1em] group-hover:text-fd-muted-foreground transition-colors duration-300">
{index === 0 ? text.previousPage : text.nextPage}
</span>
<span className="font-medium text-fd-foreground/90 group-hover:text-fd-foreground leading-snug transition-colors duration-300">
{item.name}
</span>
</div>
<p className="text-fd-muted-foreground truncate">
{item.description ?? (index === 0 ? text.previousPage : text.nextPage)}
</p>
</Link>
);
}

View File

@ -8,6 +8,7 @@ import { APIPage } from 'fumadocs-openapi/ui';
import { EnhancedAPIPage } from './components/api/enhanced-api-page';
import { WebhooksAPIPage } from './components/api/webhooks-api-page';
import AppleSecretGenerator from './components/apple-secret-generator';
import { Card, CardGroup, Info } from './components/mdx';
import ApiSequenceDiagram from './components/mdx/api-sequence-diagram';
import { AuthCard } from './components/mdx/auth-card';
@ -76,6 +77,7 @@ export function getMDXComponents(components?: MDXComponents): MDXComponents {
ClickableTableOfContents,
CollapsibleMethodSection,
CollapsibleTypesSection,
SDKOverview
SDKOverview,
AppleSecretGenerator
} as MDXComponents;
}

View File

@ -0,0 +1,72 @@
---
title: "Apple Authentication"
---
This guide explains how to set up Apple as an authentication provider with Stack Auth. Sign in with Apple allows users to sign in to your application using their Apple ID.
<Info>
You will need to create an Apple Developer account, and generate an Apple Services ID, Apple Private Key, Apple Team ID, and Apple Key ID.
</Info>
## Integration Steps
<Steps>
<Step>
### Create an Apple App ID and Services ID
1. Log in to the [Apple Developer Portal](https://developer.apple.com/).
2. Navigate to **Certificates, IDs & Profiles**.
3. In the sidebar, select **Identifiers** and click the "+" button to register a new identifier.
4. Select **App IDs** and click **Continue**.
5. Select **App** as the type and click **Continue**.
6. Give your app a description and a Bundle ID (e.g., com.yourdomain.app).
7. Scroll down and enable **Sign in with Apple**, then click **Continue**, then **Register**.
8. In the top-right of the Identifiers page, switch to **Services IDs**.
9. Click the "+" button to create a new Service ID and click **Continue**.
10. Give it a description and an identifier (note: this cannot be the same as your App ID's bundle ID).
11. Click **Continue**, then **Register**.
12. From the list, select your new Service ID.
13. Enable **Sign in with Apple** by checking the box.
14. Click **Configure** next to Sign in with Apple.
15. Register your domains (add api.stack-auth.com).
16. Add the return URL: `https://api.stack-auth.com/api/v1/auth/oauth/callback/apple`
17. Click **Done**, then **Continue**, and then **Save**.
</Step>
<Step>
### Create a Private Key
1. In the sidebar, select **Keys** and click the "+" button.
2. Give your key a name and usage description.
3. Scroll down to enable **Sign in with Apple** and click **Configure**.
4. Select your Primary App ID that you created earlier and click **Save**.
5. Click **Continue**, then **Register**.
6. On the next page, **download your key file (.p8)**. This is critical as you won't be able to download it again.
7. Note your **Key ID** displayed on this page.
8. Click **Done**.
9. Find your **Account ID** at the very top-right of the Apple Developer Portal page.
</Step>
<Step>
### Generate Your Client Secret
1. Navigate to the [Supabase Apple Secret Generator](https://supabase.com/docs/guides/auth/social-login/auth-apple#generating-a-client_secret) page.
2. Fill in the required fields:
- **Account ID**: Your Apple Developer account ID found at the top-right of the portal
- **Service ID**: The identifier of your Service ID (found in Identifiers > Service IDs)
- **Key ID**: The ID of the private key you just created
- **Choose File**: Upload the .p8 private key file you downloaded
3. Click **Generate Secret Key**.
4. Copy the generated secret immediately - you'll need it for the next step.
</Step>
<Step>
### Enable Apple OAuth in Stack Auth
1. On the Stack Auth dashboard, select **Auth Methods** in the left sidebar.
2. Click **Add SSO Providers** and select **Apple** as the provider.
3. Set the **Client ID** (your Service ID identifier), **Client Secret** (the generated secret from Supabase), and **Team ID** (your Apple Developer Team ID).
</Step>
</Steps>
### Need More Help?
- Check the [Sign in with Apple Documentation](https://developer.apple.com/sign-in-with-apple/get-started/)
- Visit our [GitHub Support Channel](https://github.stack-auth.com)

View File

@ -0,0 +1,39 @@
---
title: "Bitbucket Authentication"
---
This guide explains how to set up Bitbucket as an authentication provider with Stack Auth. Bitbucket OAuth allows users to sign in to your application using their Bitbucket account.
## Integration Steps
<Steps>
<Step>
### Create a Bitbucket OAuth App
1. Log in to your [Bitbucket Workspaces account](https://bitbucket.org/account/workspaces/).
2. Under **Workspaces**, find your workspace and select **Manage**.
3. In the left sidebar, scroll down and select **OAuth consumers**.
4. Click **Add consumer**.
5. Fill out the form with the following details:
- **Name**: Choose a name for your application
- **Description**: Add a brief description of your application
- **Callback URL**: Enter `https://api.stack-auth.com/api/v1/auth/oauth/callback/bitbucket`
- **Permissions**: Under **Account**, select at minimum **Email** and **Read**
6. Click **Save**.
7. You'll be redirected to the OAuth consumers page. Select your newly created consumer to view its details.
8. Note the **Key** (Client ID) and **Secret** values. Save these somewhere secure as you'll need them for the next steps.
</Step>
<Step>
### Enable Bitbucket OAuth in Stack Auth
1. On the Stack Auth dashboard, select **Auth Methods** in the left sidebar.
2. Click **Add SSO Providers** and select **Bitbucket** as the provider.
3. Set the **Client ID** (the Key from your Bitbucket OAuth consumer) and **Client Secret** you obtained from Bitbucket earlier.
</Step>
</Steps>
### Need More Help?
- Check the [Bitbucket OAuth documentation](https://developer.atlassian.com/cloud/bitbucket/oauth-2/)
- Visit our [GitHub Support Channel](https://github.stack-auth.com)

View File

@ -0,0 +1,45 @@
---
title: "Discord Authentication"
---
This guide explains how to set up Discord as an authentication provider with Stack Auth. Discord OAuth2 allows users to sign in to your application using their Discord account.
## Integration Steps
<Steps>
<Step>
### Create a Discord Developer App
1. Navigate to the [Discord Developer Portal](https://discord.com/developers/applications).
2. Click the **New Application** button in the top-right corner.
3. Enter a name for your application and click **Create**. You will be redirected to the General Information page.
4. Select **OAuth2** in the left sidebar.
5. Under **Redirects** add `https://api.stack-auth.com/api/v1/auth/oauth/callback/discord`
6. In the **OAuth2** section, enable the required scopes: 'identify' and 'email'
7. Click **Save Changes**
8. Save the **Client ID** and **Client Secret**. You may need to select **Reset Secret** to generate a new one.
</Step>
<Step>
### Enable Discord OAuth2 in Stack Auth
1. On the Stack Auth dashboard, select **Auth Methods** in the left sidebar.
2. Click **Add SSO Providers** and select **Discord** as the provider.
3. Set the **Client ID** and **Client Secret** you obtained from the Discord Developer Portal earlier.
</Step>
</Steps>
### User Profile Data
When a user signs in with Discord, Stack Auth will create a user profile with the following data:
- **User ID**: Discord's unique user ID
- **Username**: The user's Discord username
- **Avatar**: The user's Discord avatar (if available)
- **Email**: The user's email (if the 'email' scope is requested)
### Need More Help?
- Check the [Discord OAuth2 Documentation](https://discord.com/developers/docs/topics/oauth2)
- Visit our [Discord Support Channel](https://discord.stack-auth.com)

View File

@ -0,0 +1,43 @@
---
title: "Facebook Authentication"
---
This guide explains how to set up Facebook as an authentication provider with Stack Auth. Facebook OAuth allows users to sign in to your application using their Facebook account.
## Integration Steps
<Steps>
<Step>
### Create a Facebook OAuth App
1. Navigate to the [Facebook Developers Portal](https://developers.facebook.com/).
2. In the top-right, select **My Apps** and then **Create App**.
3. You'll be redirected to the Create an app process.
4. In the **App details** step, select the app type (typically **Consumer** for authentication), fill out the necessary information, and select **Next**.
5. In the **Use Cases** step, select **Authenticate and request data from users with Facebook Login** and then select **Next**.
6. In the **Business** step, select the business portfolio to connect to your app and then select **Next**.
7. In the **Finalize** step, select **Go to dashboard**. You'll be redirected to the app's Dashboard page.
8. In the left sidenav, select **Use cases**.
9. Next to **Authenticate and request data from users with Facebook Login**, select **Customize**.
10. On the Permissions tab, next to **email**, select **Add** to allow Stack Auth to read your user's primary email address.
11. In the left sidenav, under **Facebook Login**, select **Settings**.
12. In the **Client OAuth settings** section, in the **Valid OAuth Redirect URIs** field, add `https://api.stack-auth.com/api/v1/auth/oauth/callback/facebook`
13. Select **Save changes**.
14. In the left sidenav, select **App settings** (hover over the settings icon), and then select **Basic**.
15. Note your **App ID** and **App Secret** for the next steps.
</Step>
<Step>
### Enable Facebook OAuth in Stack Auth
1. On the Stack Auth dashboard, select **Auth Methods** in the left sidebar.
2. Click **Add SSO Providers** and select **Facebook** as the provider.
3. Set the **App ID** and **App Secret** you obtained from the Facebook Developers Portal earlier.
</Step>
</Steps>
### Need More Help?
- Check the [Facebook Login Documentation](https://developers.facebook.com/docs/facebook-login/)
- Visit our [GitHub Support Channel](https://github.stack-auth.com)

View File

@ -0,0 +1,38 @@
---
title: "GitHub Authentication"
---
This guide explains how to set up GitHub as an authentication provider with Stack Auth. GitHub OAuth allows users to sign in to your application using their GitHub account.
<Info>
For Development purposes, Stack Auth uses shared keys for this provider. Shared keys are automatically created by Stack, but show Stack's logo on the OAuth sign-in page.
You should replace these before you go into production.
</Info>
## Integration Steps
<Steps>
<Step>
### Create a GitHub OAuth App
1. Navigate to your [GitHub Developer Settings](https://github.com/settings/developers).
2. Click the **New OAuth App** button.
3. Enter a name for your application, homepage URL, and a description.
4. For **Authorization callback URL**, add `https://api.stack-auth.com/api/v1/auth/oauth/callback/github`
5. Click **Register application**
6. Save the **Client ID** and click **Generate a new client secret** to create your **Client Secret**.
</Step>
<Step>
### Enable GitHub OAuth in Stack Auth
1. On the Stack Auth dashboard, select **Auth Methods** in the left sidebar.
2. Click **Add SSO Providers** and select **GitHub** as the provider.
3. Set the **Client ID** and **Client Secret** you obtained from GitHub earlier.
</Step>
</Steps>
### Need More Help?
- Check the [GitHub OAuth Documentation](https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps)
- Visit our [GitHub Support Channel](https://github.stack-auth.com)

View File

@ -0,0 +1,38 @@
---
title: "GitLab Authentication"
---
This guide explains how to set up GitLab as an authentication provider with Stack Auth. GitLab OAuth allows users to sign in to your application using their GitLab account.
## Integration Steps
<Steps>
<Step>
### Create a GitLab OAuth App
1. Log in to your GitLab account.
2. In the top-right corner, click on your profile picture and select **Preferences**.
3. In the left sidebar, select **Applications** > **Add new application**.
4. Fill out the form with the following details:
- **Name**: Choose a name for your application
- **Redirect URI**: Enter `https://api.stack-auth.com/api/v1/auth/oauth/callback/gitlab`
- **Scopes**: Select at minimum the `profile` and `email` scopes
5. Click **Save application**.
6. GitLab will display your **Application ID** and **Secret**. Make note of these values as you'll need them for the next steps.
7. If you're using a self-hosted GitLab instance, you'll also need the URL of your GitLab instance.
</Step>
<Step>
### Enable GitLab OAuth in Stack Auth
1. On the Stack Auth dashboard, select **Auth Methods** in the left sidebar.
2. Click **Add SSO Providers** and select **GitLab** as the provider.
3. Set the **Application ID** and **Secret** you obtained from GitLab earlier.
4. If you're using a self-hosted GitLab instance, you'll also need to provide the URL for your instance. For gitlab.com, you can leave this field blank or use the default value.
</Step>
</Steps>
### Need More Help?
- Check the [GitLab OAuth 2.0 documentation](https://docs.gitlab.com/ee/api/oauth2.html)
- Visit our [GitHub Support Channel](https://github.stack-auth.com)

View File

@ -0,0 +1,41 @@
---
title: "Google Authentication"
---
This guide explains how to set up Google as an authentication provider with Stack Auth. Google OAuth2 allows users to sign in to your application using their Google account.
<Info>
For Development purposes, Stack Auth uses shared keys for this provider. Shared keys are automatically created by Stack, but show Stack's logo on the OAuth sign-in page.
You should replace these before you go into production.
</Info>
## Integration Steps
<Steps>
<Step>
### Create a Google OAuth2 App
1. Navigate to the [Google Cloud Console](https://console.cloud.google.com/).
2. Create a new project or select an existing one.
3. In the sidebar, navigate to **APIs & Services** > **Credentials**.
4. Click **Create Credentials** and select **OAuth client ID**.
5. Select **Web application** as the application type.
6. Enter a name for your OAuth client.
7. Under **Authorized redirect URIs**, add `https://api.stack-auth.com/api/v1/auth/oauth/callback/google`
8. Click **Create**.
9. Save the **Client ID** and **Client Secret** that are displayed.
</Step>
<Step>
### Enable Google OAuth2 in Stack Auth
1. On the Stack Auth dashboard, select **Auth Methods** in the left sidebar.
2. Click **Add SSO Providers** and select **Google** as the provider.
3. Set the **Client ID** and **Client Secret** you obtained from Google Cloud Console earlier.
</Step>
</Steps>
### Need More Help?
- Check the [Google OAuth2 Documentation](https://developers.google.com/identity/protocols/oauth2)
- Visit our [GitHub Support Channel](https://github.stack-auth.com)

View File

@ -0,0 +1,134 @@
---
title: "Auth Providers Overview"
---
Stack Auth supports a wide range of authentication providers to help you add secure authentication to your application. This documentation covers all the supported providers and how to configure them.
## OAuth Providers
<CardGroup cols={3}>
<Card
title="GitHub"
href="./auth-providers/github"
>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginTop: '0.5rem' }}>
<img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/github/github-original.svg" alt="GitHub" width="40" height="40" />
</div>
</Card>
<Card
title="Google"
href="./auth-providers/google"
>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginTop: '0.5rem' }}>
<img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/google/google-original.svg" alt="Google" width="40" height="40" />
</div>
</Card>
<Card
title="Facebook"
href="./auth-providers/facebook"
>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginTop: '0.5rem' }}>
<img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/facebook/facebook-original.svg" alt="Facebook" width="40" height="40" />
</div>
</Card>
<Card
title="Microsoft"
href="./auth-providers/microsoft"
>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginTop: '0.5rem' }}>
<img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/windows8/windows8-original.svg" alt="Microsoft" width="40" height="40" />
</div>
</Card>
<Card
title="Spotify"
href="./auth-providers/spotify"
>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginTop: '0.5rem' }}>
<svg viewBox="0 0 24 24" width="40" height="40"><path fill="currentColor" d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.66 0 12 0zm5.521 17.34c-.24.359-.66.48-1.021.24-2.82-1.74-6.36-2.101-10.561-1.141-.418.122-.779-.179-.899-.539-.12-.421.18-.78.54-.9 4.56-1.021 8.52-.6 11.64 1.32.42.18.48.659.301 1.02zm1.44-3.3c-.301.42-.841.6-1.262.3-3.239-1.98-8.159-2.58-11.939-1.38-.479.12-1.02-.12-1.14-.6-.12-.48.12-1.021.6-1.141C9.6 9.9 15 10.561 18.72 12.84c.361.181.54.78.241 1.2zm.12-3.36C15.24 8.4 8.82 8.16 5.16 9.301c-.6.179-1.2-.181-1.38-.721-.18-.601.18-1.2.72-1.381 4.26-1.26 11.28-1.02 15.721 1.621.539.3.719 1.02.419 1.56-.299.421-1.02.599-1.559.3z"/></svg>
</div>
</Card>
<Card
title="Discord"
href="./auth-providers/discord"
>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginTop: '0.5rem' }}>
<svg viewBox="0 -28.5 256 256" width="40" height="40"><path fill="currentColor" d="M216.856 16.597A208.502 208.502 0 0 0 164.042 0c-2.275 4.113-4.933 9.645-6.766 14.046-19.692-2.961-39.203-2.961-58.533 0-1.832-4.4-4.55-9.933-6.846-14.046a207.809 207.809 0 0 0-52.855 16.638C5.618 67.147-3.443 116.4 1.087 164.956c22.169 16.555 43.653 26.612 64.775 33.193A161.094 161.094 0 0 0 79.735 175.3a136.413 136.413 0 0 1-21.846-10.632 108.636 108.636 0 0 0 5.356-4.237c42.122 19.702 87.89 19.702 129.51 0a131.66 131.66 0 0 0 5.355 4.237 136.07 136.07 0 0 1-21.886 10.653c4.006 8.02 8.638 15.67 13.873 22.848 21.142-6.58 42.646-16.637 64.815-33.213 5.316-56.288-9.08-105.09-38.056-148.36ZM85.474 135.095c-12.645 0-23.015-11.805-23.015-26.18s10.149-26.2 23.015-26.2c12.867 0 23.236 11.804 23.015 26.2.02 14.375-10.148 26.18-23.015 26.18Zm85.051 0c-12.645 0-23.014-11.805-23.014-26.18s10.148-26.2 23.014-26.2c12.867 0 23.236 11.804 23.015 26.2 0 14.375-10.148 26.18-23.015 26.18Z"/></svg>
</div>
</Card>
<Card
title="GitLab"
href="./auth-providers/gitlab"
>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginTop: '0.5rem' }}>
<img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/gitlab/gitlab-original.svg" alt="GitLab" width="40" height="40" />
</div>
</Card>
<Card
title="Apple"
href="./auth-providers/apple"
>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginTop: '0.5rem' }}>
<svg viewBox="0 0 384 512" width="40" height="40">
<path fill="currentColor" d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z"/>
</svg>
</div>
</Card>
<Card
title="Bitbucket"
href="./auth-providers/bitbucket"
>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginTop: '0.5rem' }}>
<img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/bitbucket/bitbucket-original.svg" alt="BitBucket" width="40" height="40" />
</div>
</Card>
<Card
title="LinkedIn"
href="./auth-providers/linkedin"
>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginTop: '0.5rem' }}>
<img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/linkedin/linkedin-original.svg" alt="LinkedIn" width="40" height="40" />
</div>
</Card>
<Card
title="X (Twitter)"
href="./auth-providers/x-twitter"
>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginTop: '0.5rem' }}>
<svg viewBox="0 0 24 24" width="40" height="40"><path fill="currentColor" d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
</div>
</Card>
</CardGroup>
## Other Authentication Methods
<CardGroup cols={2}>
<Card
title="Passkey"
icon="fa-regular fa-key"
href="./auth-providers/passkey"
>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginTop: '0.5rem' }}>
</div>
</Card>
<Card
title="Two-Factor Auth (2FA)"
icon="fa-regular fa-shield-check"
href="./auth-providers/two-factor-auth"
>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginTop: '0.5rem' }}>
</div>
</Card>
</CardGroup>
Each provider has its own setup process and configuration requirements. Select a provider from the navigation to learn more about how to configure it with Stack Auth.

View File

@ -0,0 +1,40 @@
---
title: "LinkedIn Authentication"
---
This guide explains how to set up LinkedIn as an authentication provider with Stack Auth. LinkedIn OAuth2 allows users to sign in to your application using their LinkedIn account.
## Integration Steps
<Steps>
<Step>
### Create a LinkedIn OAuth App
1. Log in to the [LinkedIn Developer Portal](https://www.linkedin.com/developers/apps).
2. Click **Create app** to create a new application.
3. Enter your **App name** and select a **LinkedIn Page** to associate with your app (or create a new one).
4. Upload an **App logo** (required for production apps).
5. Enter the **App description** and your **Business email**.
6. Check the **Legal agreement** box and click **Create app**.
7. On your app's dashboard, click **Auth** tab from the left sidebar.
8. Under **OAuth 2.0 settings**, add the following redirect URL: `https://api.stack-auth.com/api/v1/auth/oauth/callback/linkedin`
9. Under **Products**, request access to **Sign In with LinkedIn** by clicking **Request access**. Complete any required information.
10. Under **OAuth 2.0 scopes**, make sure at least the following scopes are selected:
- `r_emailaddress`
- `r_liteprofile`
11. Once approved, navigate to the **Auth** tab again to find your **Client ID** and **Client Secret**.
</Step>
<Step>
### Enable LinkedIn OAuth in Stack Auth
1. On the Stack Auth dashboard, select **Auth Methods** in the left sidebar.
2. Click **Add SSO Providers** and select **LinkedIn** as the provider.
3. Set the **Client ID** and **Client Secret** you obtained from the LinkedIn Developer Portal earlier.
</Step>
</Steps>
### Need More Help?
- Check the [LinkedIn OAuth2 Documentation](https://learn.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow)
- Visit our [GitHub Support Channel](https://github.stack-auth.com)

View File

@ -0,0 +1,19 @@
{
"title": "OAuth Providers",
"defaultOpen": false,
"pages": [
"github",
"google",
"facebook",
"microsoft",
"spotify",
"discord",
"gitlab",
"apple",
"bitbucket",
"linkedin",
"x-twitter",
"passkey",
"two-factor-auth"
]
}

View File

@ -0,0 +1,43 @@
---
title: "Microsoft Authentication"
---
This guide explains how to set up Microsoft as an authentication provider with Stack Auth. Microsoft OAuth allows users to sign in to your application using their Microsoft account.
<Info>
For Development purposes, Stack Auth uses shared keys for this provider. Shared keys are automatically created by Stack, but show Stack's logo on the OAuth sign-in page.
You should replace these before you go into production.
</Info>
## Integration Steps
<Steps>
<Step>
### Create a Microsoft OAuth App
1. Navigate to the [Microsoft Entra admin center](https://entra.microsoft.com/) (formerly Azure AD).
2. In the left sidebar, go to **Applications** > **App registrations**.
3. Click **New registration** at the top of the page.
4. Enter a name for your application.
5. Under **Supported account types**, select the option that best suits your needs (typically **Accounts in any organizational directory and personal Microsoft accounts**).
6. In the **Redirect URI** section, select **Web** as the platform and enter `https://api.stack-auth.com/api/v1/auth/oauth/callback/microsoft`
7. Click **Register** to create the application.
8. You'll be redirected to the app's Overview page. Note the **Application (client) ID** displayed at the top.
9. In the left sidebar, click **Certificates & secrets**.
10. Under **Client secrets**, click **New client secret**.
11. Add a description, select an expiration period, and click **Add**.
12. Copy the **Value** of the client secret immediately (you won't be able to see it again).
</Step>
<Step>
### Enable Microsoft OAuth in Stack Auth
1. On the Stack Auth dashboard, select **Auth Methods** in the left sidebar.
2. Click **Add SSO Providers** and select **Microsoft** as the provider.
3. Set the **Client ID** (Application ID) and **Client Secret** you obtained from the Microsoft Entra admin center.
</Step>
</Steps>
### Need More Help?
- Check the [Microsoft identity platform Documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/)
- Visit our [GitHub Support Channel](https://github.stack-auth.com)

View File

@ -0,0 +1,55 @@
---
title: "Passkey Authentication"
---
This guide explains how to set up Passkey authentication with Stack Auth. Passkeys allow users to sign in to your application securely using biometrics, mobile devices, or security keys.
<Info>
Passkeys provide a more secure and convenient authentication method compared to traditional passwords by using WebAuthn standard.
</Info>
## Integration Steps
<Steps>
<Step>
### Enable Passkey Authentication in Stack Auth
1. Log in to the [Stack Auth dashboard](https://app.stack-auth.com/).
2. Select your project from the dashboard.
3. In the left sidebar, select **Auth Methods**.
4. Find the **Passkey** authentication method and toggle it to enable.
5. Save your changes.
</Step>
<Step>
### Implement Passkey Authentication in Your Application
1. Make sure you've installed the Stack Auth SDK in your application:
```bash
npm install @stackframe/stack
```
2. Add Passkey support to your sign-in component by using the built-in Stack Auth components or creating your own implementation with the SDK.
Using built-in components:
```jsx
import { SignIn } from "@stackframe/stack";
export default function SignInPage() {
return <SignIn />;
}
```
The built-in components will automatically show the passkey option when it's enabled in your project.
</Step>
</Steps>
## How Passkey Authentication Works
1. **Registration**: When a user creates a new passkey, their device generates a unique public-private key pair. The private key stays on the user's device, while the public key is sent to Stack Auth's servers.
2. **Authentication**: When a user wants to sign in, Stack Auth sends a challenge to the user's device. The device uses the private key to sign the challenge, and sends the signature back to Stack Auth for verification.
3. **Cross-device authentication**: Users can create passkeys on one device and use them to sign in on another device using QR codes or nearby device detection.
For the most up-to-date compatibility information, refer to the [WebAuthn browser compatibility chart](https://caniuse.com/webauthn).

View File

@ -0,0 +1,41 @@
---
title: "Spotify Authentication"
---
This guide explains how to set up Spotify as an authentication provider with Stack Auth. Spotify OAuth allows users to sign in to your application using their Spotify account.
<Info>
For Development purposes, Stack Auth uses shared keys for this provider. Shared keys are automatically created by Stack, but show Stack's logo on the OAuth sign-in page.
You should replace these before you go into production.
</Info>
## Integration Steps
<Steps>
<Step>
### Create a Spotify OAuth App
1. Navigate to the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard/).
2. Log in with your Spotify account.
3. Click **Create app** to create a new application.
4. Enter an **App name** and **App description**.
5. Under **Redirect URI**, add `https://api.stack-auth.com/api/v1/auth/oauth/callback/spotify`
6. Check the agreement checkbox and click **Create**.
7. You'll be redirected to your app's dashboard. Note your **Client ID** displayed on this page.
8. Click **Settings** to view more details about your app.
9. In the settings page, you can view your **Client Secret** by clicking **Show client secret**.
10. If needed, you can adjust the app settings, including adding additional redirect URIs.
</Step>
<Step>
### Enable Spotify OAuth in Stack Auth
1. On the Stack Auth dashboard, select **Auth Methods** in the left sidebar.
2. Click **Add SSO Providers** and select **Spotify** as the provider.
3. Set the **Client ID** and **Client Secret** you obtained from the Spotify Developer Dashboard earlier.
</Step>
</Steps>
### Need More Help?
- Check the [Spotify Web API Authorization Documentation](https://developer.spotify.com/documentation/general/guides/authorization/)
- Visit our [GitHub Support Channel](https://github.stack-auth.com)

View File

@ -0,0 +1,59 @@
---
title: "Two-Factor Authentication (2FA)"
---
This guide explains how Two-Factor Authentication (2FA) works with Stack Auth. 2FA adds an extra layer of security by requiring users to provide a verification code in addition to their password.
<Info>
Stack Auth implements TOTP (Time-based One-Time Password) for two-factor authentication, which is compatible with standard authenticator apps like Google Authenticator, Microsoft Authenticator, and Authy. 2FA is enabled by default at the platform level and can be configured by individual users.
</Info>
## Integration Steps
<Steps>
<Step>
### No Developer Configuration Required
2FA is enabled by default on the Stack Auth platform. Unlike other authentication methods, you don't need to enable it specifically for your project.
</Step>
<Step>
### Implement User Settings in Your Application
To allow your users to set up 2FA for their accounts:
1. Make sure you've installed the Stack Auth SDK in your application:
```bash
npm install @stackframe/stack
```
2. Use the Stack Auth components to give users access to their account settings, where they can enable 2FA:
```jsx
import { AccountSettings } from "@stackframe/stack";
export default function SettingsPage() {
return <AccountSettings />;
}
```
3. The built-in Stack Auth components will handle the entire 2FA setup process, including QR code generation, verification, and recovery codes.
</Step>
</Steps>
## How Stack Auth 2FA Works
Stack Auth uses the industry-standard TOTP (Time-based One-Time Password) algorithm for two-factor authentication:
1. **User Setup**: When a user enables 2FA in their account settings, Stack Auth generates a secret key that is shared with the user's authenticator app (usually via a QR code).
2. **Code Generation**: The authenticator app generates a 6-digit code that changes every 30 seconds, based on the shared secret and the current time.
## Recommended Authenticator Apps
The following authenticator apps are compatible with Stack Auth 2FA:
- Google Authenticator (Android, iOS)
- Microsoft Authenticator (Android, iOS)
- Authy (Android, iOS, desktop)
- 1Password (Android, iOS, desktop)
- LastPass Authenticator (Android, iOS)

View File

@ -0,0 +1,43 @@
---
title: "X (Twitter) Authentication"
---
This guide explains how to set up X (formerly Twitter) as an authentication provider with Stack Auth. X OAuth 2.0 allows users to sign in to your application using their X account.
## Integration Steps
<Steps>
<Step>
### Create an X Developer Account and Project
1. Log in to the [X Developer Portal](https://developer.twitter.com/).
2. Navigate to the [Developer Portal Dashboard](https://developer.twitter.com/en/portal/dashboard).
3. Click on **+ Create Project** to create a new project.
4. Enter a name for your project and select **Web App, Automated App or Bot** as the use case, then click **Next**.
5. Enter a description for your project and click **Next**.
6. Name your app and click **Next**.
7. In the **App settings** section, find your API Key and Secret. These will serve as your OAuth 2.0 Client ID and Client Secret.
8. In the left sidebar, click on your project, then select the app you just created.
9. Click on the **Settings** tab and scroll to the **User authentication settings**.
10. Click **Set up** or **Edit** if already configured.
11. Enable **OAuth 2.0** and set the following details:
- **Type of App**: Web App
- **Callback URL / Redirect URL**: `https://api.stack-auth.com/api/v1/auth/oauth/callback/twitter`
- **Website URL**: Your website's URL
12. Under **App permissions**, select your scopes.
13. Click **Save** to apply your changes.
</Step>
<Step>
### Enable X OAuth in Stack Auth
1. On the Stack Auth dashboard, select **Auth Methods** in the left sidebar.
2. Click **Add SSO Providers** and select **X (Twitter)** as the provider.
3. Set the **Client ID** (your API Key) and **Client Secret** you obtained from the X Developer Portal earlier.
</Step>
</Steps>
### Need More Help?
- Check the [X OAuth 2.0 documentation](https://developer.twitter.com/en/docs/authentication/oauth-2-0)
- Visit our [GitHub Support Channel](https://github.stack-auth.com)

View File

@ -9,6 +9,7 @@
"getting-started/setup",
"getting-started/components",
"getting-started/users",
"getting-started/auth-providers",
"getting-started/production",
"---Concepts---",
"concepts/api-keys",