Diese Anleitung behandelt die Integration beliebter Authentifizierungsdienste (Auth0, Clerk, Supabase) mit TanStack Router.
Wählen Sie einen Authentifizierungsanbieter, installieren Sie dessen SDK, umschließen Sie Ihren Router mit seinem Kontext und passen Sie seinen Authentifizierungsstatus an, um mit dem Kontextsystem von TanStack Router zu arbeiten.
npm install @auth0/auth0-react
npm install @auth0/auth0-react
Zu Ihrer .env-Datei hinzufügen
VITE_AUTH0_DOMAIN=your-auth0-domain.auth0.com
VITE_AUTH0_CLIENT_ID=your_auth0_client_id
VITE_AUTH0_DOMAIN=your-auth0-domain.auth0.com
VITE_AUTH0_CLIENT_ID=your_auth0_client_id
Erstellen Sie src/auth/auth0.tsx
import { Auth0Provider, useAuth0 } from '@auth0/auth0-react'
import { createContext, useContext } from 'react'
interface Auth0ContextType {
isAuthenticated: boolean
user: any
login: () => void
logout: () => void
isLoading: boolean
}
const Auth0Context = createContext<Auth0ContextType | undefined>(undefined)
export function Auth0Wrapper({ children }: { children: React.ReactNode }) {
return (
<Auth0Provider
domain={import.meta.env.VITE_AUTH0_DOMAIN}
clientId={import.meta.env.VITE_AUTH0_CLIENT_ID}
authorizationParams={{
redirect_uri: window.location.origin,
}}
>
<Auth0ContextProvider>{children}</Auth0ContextProvider>
</Auth0Provider>
)
}
function Auth0ContextProvider({ children }: { children: React.ReactNode }) {
const { isAuthenticated, user, loginWithRedirect, logout, isLoading } =
useAuth0()
const contextValue = {
isAuthenticated,
user,
login: loginWithRedirect,
logout: () =>
logout({ logoutParams: { returnTo: window.location.origin } }),
isLoading,
}
return (
<Auth0Context.Provider value={contextValue}>
{children}
</Auth0Context.Provider>
)
}
export function useAuth0Context() {
const context = useContext(Auth0Context)
if (context === undefined) {
throw new Error('useAuth0Context must be used within Auth0Wrapper')
}
return context
}
import { Auth0Provider, useAuth0 } from '@auth0/auth0-react'
import { createContext, useContext } from 'react'
interface Auth0ContextType {
isAuthenticated: boolean
user: any
login: () => void
logout: () => void
isLoading: boolean
}
const Auth0Context = createContext<Auth0ContextType | undefined>(undefined)
export function Auth0Wrapper({ children }: { children: React.ReactNode }) {
return (
<Auth0Provider
domain={import.meta.env.VITE_AUTH0_DOMAIN}
clientId={import.meta.env.VITE_AUTH0_CLIENT_ID}
authorizationParams={{
redirect_uri: window.location.origin,
}}
>
<Auth0ContextProvider>{children}</Auth0ContextProvider>
</Auth0Provider>
)
}
function Auth0ContextProvider({ children }: { children: React.ReactNode }) {
const { isAuthenticated, user, loginWithRedirect, logout, isLoading } =
useAuth0()
const contextValue = {
isAuthenticated,
user,
login: loginWithRedirect,
logout: () =>
logout({ logoutParams: { returnTo: window.location.origin } }),
isLoading,
}
return (
<Auth0Context.Provider value={contextValue}>
{children}
</Auth0Context.Provider>
)
}
export function useAuth0Context() {
const context = useContext(Auth0Context)
if (context === undefined) {
throw new Error('useAuth0Context must be used within Auth0Wrapper')
}
return context
}
Aktualisieren Sie src/App.tsx
import { RouterProvider } from '@tanstack/react-router'
import { Auth0Wrapper, useAuth0Context } from './auth/auth0'
import { router } from './router'
function InnerApp() {
const auth = useAuth0Context()
if (auth.isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
Loading...
</div>
)
}
return <RouterProvider router={router} context={{ auth }} />
}
function App() {
return (
<Auth0Wrapper>
<InnerApp />
</Auth0Wrapper>
)
}
export default App
import { RouterProvider } from '@tanstack/react-router'
import { Auth0Wrapper, useAuth0Context } from './auth/auth0'
import { router } from './router'
function InnerApp() {
const auth = useAuth0Context()
if (auth.isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
Loading...
</div>
)
}
return <RouterProvider router={router} context={{ auth }} />
}
function App() {
return (
<Auth0Wrapper>
<InnerApp />
</Auth0Wrapper>
)
}
export default App
Erstellen Sie src/routes/_authenticated.tsx
import { createFileRoute, redirect, Outlet } from '@tanstack/react-router'
export const Route = createFileRoute('/_authenticated')({
beforeLoad: ({ context, location }) => {
if (!context.auth.isAuthenticated) {
// Auth0 handles login redirects, so just trigger login
context.auth.login()
return
}
},
component: () => <Outlet />,
})
import { createFileRoute, redirect, Outlet } from '@tanstack/react-router'
export const Route = createFileRoute('/_authenticated')({
beforeLoad: ({ context, location }) => {
if (!context.auth.isAuthenticated) {
// Auth0 handles login redirects, so just trigger login
context.auth.login()
return
}
},
component: () => <Outlet />,
})
npm install @clerk/clerk-react
npm install @clerk/clerk-react
Zu Ihrer .env-Datei hinzufügen
VITE_CLERK_PUBLISHABLE_KEY=pk_test_your_clerk_key
VITE_CLERK_PUBLISHABLE_KEY=pk_test_your_clerk_key
Erstellen Sie src/auth/clerk.tsx
import { ClerkProvider, useUser, useAuth } from '@clerk/clerk-react'
export function ClerkWrapper({ children }: { children: React.ReactNode }) {
return (
<ClerkProvider publishableKey={import.meta.env.VITE_CLERK_PUBLISHABLE_KEY}>
{children}
</ClerkProvider>
)
}
export function useClerkAuth() {
const { isSignedIn, isLoaded } = useAuth()
const { user } = useUser()
return {
isAuthenticated: isSignedIn,
user: user
? {
id: user.id,
username:
user.username || user.primaryEmailAddress?.emailAddress || '',
email: user.primaryEmailAddress?.emailAddress || '',
}
: null,
isLoading: !isLoaded,
login: () => {
// Clerk handles login through components
window.location.href = '/sign-in'
},
logout: () => {
// Clerk handles logout through components
window.location.href = '/sign-out'
},
}
}
import { ClerkProvider, useUser, useAuth } from '@clerk/clerk-react'
export function ClerkWrapper({ children }: { children: React.ReactNode }) {
return (
<ClerkProvider publishableKey={import.meta.env.VITE_CLERK_PUBLISHABLE_KEY}>
{children}
</ClerkProvider>
)
}
export function useClerkAuth() {
const { isSignedIn, isLoaded } = useAuth()
const { user } = useUser()
return {
isAuthenticated: isSignedIn,
user: user
? {
id: user.id,
username:
user.username || user.primaryEmailAddress?.emailAddress || '',
email: user.primaryEmailAddress?.emailAddress || '',
}
: null,
isLoading: !isLoaded,
login: () => {
// Clerk handles login through components
window.location.href = '/sign-in'
},
logout: () => {
// Clerk handles logout through components
window.location.href = '/sign-out'
},
}
}
Erstellen Sie src/routes/sign-in.tsx
import { createFileRoute } from '@tanstack/react-router'
import { SignIn } from '@clerk/clerk-react'
export const Route = createFileRoute('/sign-in')({
component: () => (
<div className="flex items-center justify-center min-h-screen">
<SignIn redirectUrl="/dashboard" signUpUrl="/sign-up" />
</div>
),
})
import { createFileRoute } from '@tanstack/react-router'
import { SignIn } from '@clerk/clerk-react'
export const Route = createFileRoute('/sign-in')({
component: () => (
<div className="flex items-center justify-center min-h-screen">
<SignIn redirectUrl="/dashboard" signUpUrl="/sign-up" />
</div>
),
})
Erstellen Sie src/routes/sign-up.tsx
import { createFileRoute } from '@tanstack/react-router'
import { SignUp } from '@clerk/clerk-react'
export const Route = createFileRoute('/sign-up')({
component: () => (
<div className="flex items-center justify-center min-h-screen">
<SignUp redirectUrl="/dashboard" signInUrl="/sign-in" />
</div>
),
})
import { createFileRoute } from '@tanstack/react-router'
import { SignUp } from '@clerk/clerk-react'
export const Route = createFileRoute('/sign-up')({
component: () => (
<div className="flex items-center justify-center min-h-screen">
<SignUp redirectUrl="/dashboard" signInUrl="/sign-in" />
</div>
),
})
Aktualisieren Sie src/App.tsx
import { RouterProvider } from '@tanstack/react-router'
import { ClerkWrapper, useClerkAuth } from './auth/clerk'
import { router } from './router'
function InnerApp() {
const auth = useClerkAuth()
if (auth.isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
Loading...
</div>
)
}
return <RouterProvider router={router} context={{ auth }} />
}
function App() {
return (
<ClerkWrapper>
<InnerApp />
</ClerkWrapper>
)
}
export default App
import { RouterProvider } from '@tanstack/react-router'
import { ClerkWrapper, useClerkAuth } from './auth/clerk'
import { router } from './router'
function InnerApp() {
const auth = useClerkAuth()
if (auth.isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
Loading...
</div>
)
}
return <RouterProvider router={router} context={{ auth }} />
}
function App() {
return (
<ClerkWrapper>
<InnerApp />
</ClerkWrapper>
)
}
export default App
Erstellen Sie src/routes/_authenticated.tsx
import { createFileRoute, redirect, Outlet } from '@tanstack/react-router'
export const Route = createFileRoute('/_authenticated')({
beforeLoad: ({ context, location }) => {
if (!context.auth.isAuthenticated) {
throw redirect({
to: '/sign-in',
search: {
redirect: location.href,
},
})
}
},
component: () => <Outlet />,
})
import { createFileRoute, redirect, Outlet } from '@tanstack/react-router'
export const Route = createFileRoute('/_authenticated')({
beforeLoad: ({ context, location }) => {
if (!context.auth.isAuthenticated) {
throw redirect({
to: '/sign-in',
search: {
redirect: location.href,
},
})
}
},
component: () => <Outlet />,
})
npm install @supabase/supabase-js
npm install @supabase/supabase-js
Zu Ihrer .env-Datei hinzufügen
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your_supabase_anon_key
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your_supabase_anon_key
Erstellen Sie src/auth/supabase.tsx
import { createClient } from '@supabase/supabase-js'
import { createContext, useContext, useEffect, useState } from 'react'
const supabase = createClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY,
)
interface SupabaseAuthState {
isAuthenticated: boolean
user: any
login: (email: string, password: string) => Promise<void>
logout: () => Promise<void>
isLoading: boolean
}
const SupabaseAuthContext = createContext<SupabaseAuthState | undefined>(
undefined,
)
export function SupabaseAuthProvider({
children,
}: {
children: React.ReactNode
}) {
const [user, setUser] = useState(null)
const [isAuthenticated, setIsAuthenticated] = useState(false)
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
// Get initial session
supabase.auth.getSession().then(({ data: { session } }) => {
setUser(session?.user ?? null)
setIsAuthenticated(!!session?.user)
setIsLoading(false)
})
// Listen for auth changes
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setUser(session?.user ?? null)
setIsAuthenticated(!!session?.user)
setIsLoading(false)
})
return () => subscription.unsubscribe()
}, [])
const login = async (email: string, password: string) => {
const { error } = await supabase.auth.signInWithPassword({
email,
password,
})
if (error) throw error
}
const logout = async () => {
const { error } = await supabase.auth.signOut()
if (error) throw error
}
return (
<SupabaseAuthContext.Provider
value={{
isAuthenticated,
user,
login,
logout,
isLoading,
}}
>
{children}
</SupabaseAuthContext.Provider>
)
}
export function useSupabaseAuth() {
const context = useContext(SupabaseAuthContext)
if (context === undefined) {
throw new Error('useSupabaseAuth must be used within SupabaseAuthProvider')
}
return context
}
import { createClient } from '@supabase/supabase-js'
import { createContext, useContext, useEffect, useState } from 'react'
const supabase = createClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY,
)
interface SupabaseAuthState {
isAuthenticated: boolean
user: any
login: (email: string, password: string) => Promise<void>
logout: () => Promise<void>
isLoading: boolean
}
const SupabaseAuthContext = createContext<SupabaseAuthState | undefined>(
undefined,
)
export function SupabaseAuthProvider({
children,
}: {
children: React.ReactNode
}) {
const [user, setUser] = useState(null)
const [isAuthenticated, setIsAuthenticated] = useState(false)
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
// Get initial session
supabase.auth.getSession().then(({ data: { session } }) => {
setUser(session?.user ?? null)
setIsAuthenticated(!!session?.user)
setIsLoading(false)
})
// Listen for auth changes
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setUser(session?.user ?? null)
setIsAuthenticated(!!session?.user)
setIsLoading(false)
})
return () => subscription.unsubscribe()
}, [])
const login = async (email: string, password: string) => {
const { error } = await supabase.auth.signInWithPassword({
email,
password,
})
if (error) throw error
}
const logout = async () => {
const { error } = await supabase.auth.signOut()
if (error) throw error
}
return (
<SupabaseAuthContext.Provider
value={{
isAuthenticated,
user,
login,
logout,
isLoading,
}}
>
{children}
</SupabaseAuthContext.Provider>
)
}
export function useSupabaseAuth() {
const context = useContext(SupabaseAuthContext)
if (context === undefined) {
throw new Error('useSupabaseAuth must be used within SupabaseAuthProvider')
}
return context
}
Erstellen Sie src/routes/login.tsx
import { createFileRoute, redirect } from '@tanstack/react-router'
import { useState } from 'react'
export const Route = createFileRoute('/login')({
validateSearch: (search) => ({
redirect: (search.redirect as string) || '/dashboard',
}),
beforeLoad: ({ context, search }) => {
if (context.auth.isAuthenticated) {
throw redirect({ to: search.redirect })
}
},
component: LoginComponent,
})
function LoginComponent() {
const { auth } = Route.useRouteContext()
const { redirect } = Route.useSearch()
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState('')
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setIsLoading(true)
setError('')
try {
await auth.login(email, password)
// Supabase auth will automatically update context
window.location.href = redirect
} catch (err: any) {
setError(err.message || 'Login failed')
} finally {
setIsLoading(false)
}
}
return (
<div className="min-h-screen flex items-center justify-center">
<form
onSubmit={handleSubmit}
className="max-w-md w-full space-y-4 p-6 border rounded-lg"
>
<h1 className="text-2xl font-bold text-center">Sign In</h1>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
{error}
</div>
)}
<div>
<label htmlFor="email" className="block text-sm font-medium mb-1">
Email
</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium mb-1">
Password
</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
<button
type="submit"
disabled={isLoading}
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:opacity-50"
>
{isLoading ? 'Signing in...' : 'Sign In'}
</button>
</form>
</div>
)
}
import { createFileRoute, redirect } from '@tanstack/react-router'
import { useState } from 'react'
export const Route = createFileRoute('/login')({
validateSearch: (search) => ({
redirect: (search.redirect as string) || '/dashboard',
}),
beforeLoad: ({ context, search }) => {
if (context.auth.isAuthenticated) {
throw redirect({ to: search.redirect })
}
},
component: LoginComponent,
})
function LoginComponent() {
const { auth } = Route.useRouteContext()
const { redirect } = Route.useSearch()
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState('')
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setIsLoading(true)
setError('')
try {
await auth.login(email, password)
// Supabase auth will automatically update context
window.location.href = redirect
} catch (err: any) {
setError(err.message || 'Login failed')
} finally {
setIsLoading(false)
}
}
return (
<div className="min-h-screen flex items-center justify-center">
<form
onSubmit={handleSubmit}
className="max-w-md w-full space-y-4 p-6 border rounded-lg"
>
<h1 className="text-2xl font-bold text-center">Sign In</h1>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
{error}
</div>
)}
<div>
<label htmlFor="email" className="block text-sm font-medium mb-1">
Email
</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium mb-1">
Password
</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
<button
type="submit"
disabled={isLoading}
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:opacity-50"
>
{isLoading ? 'Signing in...' : 'Sign In'}
</button>
</form>
</div>
)
}
Aktualisieren Sie src/App.tsx
import { RouterProvider } from '@tanstack/react-router'
import { SupabaseAuthProvider, useSupabaseAuth } from './auth/supabase'
import { router } from './router'
function InnerApp() {
const auth = useSupabaseAuth()
if (auth.isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
Loading...
</div>
)
}
return <RouterProvider router={router} context={{ auth }} />
}
function App() {
return (
<SupabaseAuthProvider>
<InnerApp />
</SupabaseAuthProvider>
)
}
export default App
import { RouterProvider } from '@tanstack/react-router'
import { SupabaseAuthProvider, useSupabaseAuth } from './auth/supabase'
import { router } from './router'
function InnerApp() {
const auth = useSupabaseAuth()
if (auth.isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
Loading...
</div>
)
}
return <RouterProvider router={router} context={{ auth }} />
}
function App() {
return (
<SupabaseAuthProvider>
<InnerApp />
</SupabaseAuthProvider>
)
}
export default App
| Funktion | Auth0 | Clerk | Supabase |
|---|---|---|---|
| Einrichtungskomplexität | Mittel | Niedrig | Mittel |
| UI-Komponenten | Basis | Ausgezeichnet | Keine |
| Anpassung | Hoch | Mittel | Hoch |
| Preise | Freemium | Freemium | Freemium |
| Soziale Anmeldung | ✅ | ✅ | ✅ |
| Enterprise-Funktionen | ✅ | ✅ | ✅ |
| Datenbank enthalten | ❌ | ❌ | ✅ |
Problem: Auth-Kontext ist in Komponenten undefiniert.
Lösung: Stellen Sie sicher, dass sich der Provider-Wrapper über RouterProvider befindet.
// ✅ Correct order
<AuthProvider>
<InnerApp>
<RouterProvider />
</InnerApp>
</AuthProvider>
// ❌ Wrong order
<RouterProvider>
<AuthProvider />
</RouterProvider>
// ✅ Correct order
<AuthProvider>
<InnerApp>
<RouterProvider />
</InnerApp>
</AuthProvider>
// ❌ Wrong order
<RouterProvider>
<AuthProvider />
</RouterProvider>
Problem: App hängt im Ladebildschirm fest.
Lösung: Prüfen Sie, ob der Authentifizierungsanbieter isLoading korrekt auf false setzt.
// Add timeout fallback
useEffect(() => {
const timeout = setTimeout(() => {
if (isLoading) {
setIsLoading(false)
}
}, 5000)
return () => clearTimeout(timeout)
}, [isLoading])
// Add timeout fallback
useEffect(() => {
const timeout = setTimeout(() => {
if (isLoading) {
setIsLoading(false)
}
}, 5000)
return () => clearTimeout(timeout)
}, [isLoading])
Problem: Kontinuierliche Weiterleitungen zwischen Login und geschützten Routen.
Lösung: Behandeln Sie die automatischen Weiterleitungen von Auth0 korrekt.
export const Route = createFileRoute('/_authenticated')({
beforeLoad: ({ context }) => {
if (!context.auth.isAuthenticated && !context.auth.isLoading) {
context.auth.login()
// Don't throw redirect, let Auth0 handle it
return
}
},
component: () => <Outlet />,
})
export const Route = createFileRoute('/_authenticated')({
beforeLoad: ({ context }) => {
if (!context.auth.isAuthenticated && !context.auth.isLoading) {
context.auth.login()
// Don't throw redirect, let Auth0 handle it
return
}
},
component: () => <Outlet />,
})
Nach der Integration von Authentifizierungsanbietern möchten Sie vielleicht
Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.