Diese Anleitung behandelt die Implementierung von Basis-Authentifizierungsmustern und den Schutz von Routen in TanStack Router-Anwendungen.
Richten Sie die Authentifizierung ein, indem Sie einen kontextbezogenen Router erstellen, Zustandsverwaltung für die Authentifizierung implementieren und beforeLoad zum Schutz von Routen verwenden. Diese Anleitung konzentriert sich auf die grundlegende Einrichtung der Authentifizierung mit React Context.
src/auth.tsx erstellen
import React, { createContext, useContext, useState, useEffect } from 'react'
interface User {
id: string
username: string
email: string
}
interface AuthState {
isAuthenticated: boolean
user: User | null
login: (username: string, password: string) => Promise<void>
logout: () => void
}
const AuthContext = createContext<AuthState | undefined>(undefined)
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null)
const [isAuthenticated, setIsAuthenticated] = useState(false)
const [isLoading, setIsLoading] = useState(true)
// Restore auth state on app load
useEffect(() => {
const token = localStorage.getItem('auth-token')
if (token) {
// Validate token with your API
fetch('/api/validate-token', {
headers: { Authorization: `Bearer ${token}` },
})
.then((response) => response.json())
.then((userData) => {
if (userData.valid) {
setUser(userData.user)
setIsAuthenticated(true)
} else {
localStorage.removeItem('auth-token')
}
})
.catch(() => {
localStorage.removeItem('auth-token')
})
.finally(() => {
setIsLoading(false)
})
} else {
setIsLoading(false)
}
}, [])
// Show loading state while checking auth
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
Loading...
</div>
)
}
const login = async (username: string, password: string) => {
// Replace with your authentication logic
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
})
if (response.ok) {
const userData = await response.json()
setUser(userData)
setIsAuthenticated(true)
// Store token for persistence
localStorage.setItem('auth-token', userData.token)
} else {
throw new Error('Authentication failed')
}
}
const logout = () => {
setUser(null)
setIsAuthenticated(false)
localStorage.removeItem('auth-token')
}
return (
<AuthContext.Provider value={{ isAuthenticated, user, login, logout }}>
{children}
</AuthContext.Provider>
)
}
export function useAuth() {
const context = useContext(AuthContext)
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider')
}
return context
}
import React, { createContext, useContext, useState, useEffect } from 'react'
interface User {
id: string
username: string
email: string
}
interface AuthState {
isAuthenticated: boolean
user: User | null
login: (username: string, password: string) => Promise<void>
logout: () => void
}
const AuthContext = createContext<AuthState | undefined>(undefined)
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null)
const [isAuthenticated, setIsAuthenticated] = useState(false)
const [isLoading, setIsLoading] = useState(true)
// Restore auth state on app load
useEffect(() => {
const token = localStorage.getItem('auth-token')
if (token) {
// Validate token with your API
fetch('/api/validate-token', {
headers: { Authorization: `Bearer ${token}` },
})
.then((response) => response.json())
.then((userData) => {
if (userData.valid) {
setUser(userData.user)
setIsAuthenticated(true)
} else {
localStorage.removeItem('auth-token')
}
})
.catch(() => {
localStorage.removeItem('auth-token')
})
.finally(() => {
setIsLoading(false)
})
} else {
setIsLoading(false)
}
}, [])
// Show loading state while checking auth
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
Loading...
</div>
)
}
const login = async (username: string, password: string) => {
// Replace with your authentication logic
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
})
if (response.ok) {
const userData = await response.json()
setUser(userData)
setIsAuthenticated(true)
// Store token for persistence
localStorage.setItem('auth-token', userData.token)
} else {
throw new Error('Authentication failed')
}
}
const logout = () => {
setUser(null)
setIsAuthenticated(false)
localStorage.removeItem('auth-token')
}
return (
<AuthContext.Provider value={{ isAuthenticated, user, login, logout }}>
{children}
</AuthContext.Provider>
)
}
export function useAuth() {
const context = useContext(AuthContext)
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider')
}
return context
}
src/routes/__root.tsx aktualisieren
import { createRootRouteWithContext, Outlet } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
interface AuthState {
isAuthenticated: boolean
user: { id: string; username: string; email: string } | null
login: (username: string, password: string) => Promise<void>
logout: () => void
}
interface MyRouterContext {
auth: AuthState
}
export const Route = createRootRouteWithContext<MyRouterContext>()({
component: () => (
<div>
<Outlet />
<TanStackRouterDevtools />
</div>
),
})
import { createRootRouteWithContext, Outlet } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
interface AuthState {
isAuthenticated: boolean
user: { id: string; username: string; email: string } | null
login: (username: string, password: string) => Promise<void>
logout: () => void
}
interface MyRouterContext {
auth: AuthState
}
export const Route = createRootRouteWithContext<MyRouterContext>()({
component: () => (
<div>
<Outlet />
<TanStackRouterDevtools />
</div>
),
})
src/router.tsx aktualisieren
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
export const router = createRouter({
routeTree,
context: {
// auth will be passed down from App component
auth: undefined!,
},
})
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
export const router = createRouter({
routeTree,
context: {
// auth will be passed down from App component
auth: undefined!,
},
})
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
src/App.tsx aktualisieren
import { RouterProvider } from '@tanstack/react-router'
import { AuthProvider, useAuth } from './auth'
import { router } from './router'
function InnerApp() {
const auth = useAuth()
return <RouterProvider router={router} context={{ auth }} />
}
function App() {
return (
<AuthProvider>
<InnerApp />
</AuthProvider>
)
}
export default App
import { RouterProvider } from '@tanstack/react-router'
import { AuthProvider, useAuth } from './auth'
import { router } from './router'
function InnerApp() {
const auth = useAuth()
return <RouterProvider router={router} context={{ auth }} />
}
function App() {
return (
<AuthProvider>
<InnerApp />
</AuthProvider>
)
}
export default App
src/routes/_authenticated.tsx erstellen
import { createFileRoute, redirect, Outlet } from '@tanstack/react-router'
export const Route = createFileRoute('/_authenticated')({
beforeLoad: ({ context, location }) => {
if (!context.auth.isAuthenticated) {
throw redirect({
to: '/login',
search: {
// Save current location for redirect after login
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: '/login',
search: {
// Save current location for redirect after login
redirect: location.href,
},
})
}
},
component: () => <Outlet />,
})
src/routes/login.tsx erstellen
import { createFileRoute, redirect } from '@tanstack/react-router'
import { useState } from 'react'
export const Route = createFileRoute('/login')({
validateSearch: (search) => ({
redirect: (search.redirect as string) || '/',
}),
beforeLoad: ({ context, search }) => {
// Redirect if already authenticated
if (context.auth.isAuthenticated) {
throw redirect({ to: search.redirect })
}
},
component: LoginComponent,
})
function LoginComponent() {
const { auth } = Route.useRouteContext()
const { redirect } = Route.useSearch()
const navigate = Route.useNavigate()
const [username, setUsername] = 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(username, password)
// Navigate to the redirect URL using router navigation
navigate({ to: redirect })
} catch (err) {
setError('Invalid username or password')
} 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="username" className="block text-sm font-medium mb-1">
Username
</label>
<input
id="username"
type="text"
value={username}
onChange={(e) => setUsername(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 disabled:cursor-not-allowed"
>
{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) || '/',
}),
beforeLoad: ({ context, search }) => {
// Redirect if already authenticated
if (context.auth.isAuthenticated) {
throw redirect({ to: search.redirect })
}
},
component: LoginComponent,
})
function LoginComponent() {
const { auth } = Route.useRouteContext()
const { redirect } = Route.useSearch()
const navigate = Route.useNavigate()
const [username, setUsername] = 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(username, password)
// Navigate to the redirect URL using router navigation
navigate({ to: redirect })
} catch (err) {
setError('Invalid username or password')
} 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="username" className="block text-sm font-medium mb-1">
Username
</label>
<input
id="username"
type="text"
value={username}
onChange={(e) => setUsername(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 disabled:cursor-not-allowed"
>
{isLoading ? 'Signing in...' : 'Sign In'}
</button>
</form>
</div>
)
}
src/routes/_authenticated/dashboard.tsx erstellen
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/_authenticated/dashboard')({
component: DashboardComponent,
})
function DashboardComponent() {
const { auth } = Route.useRouteContext()
return (
<div className="p-6">
<div className="flex justify-between items-center mb-6">
<h1 className="text-3xl font-bold">Dashboard</h1>
<button
onClick={auth.logout}
className="bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700"
>
Sign Out
</button>
</div>
<div className="bg-white p-6 rounded-lg shadow">
<h2 className="text-xl font-semibold mb-2">Welcome back!</h2>
<p className="text-gray-600">
Hello, <strong>{auth.user?.username}</strong>! You are successfully
authenticated.
</p>
<p className="text-sm text-gray-500 mt-2">Email: {auth.user?.email}</p>
</div>
</div>
)
}
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/_authenticated/dashboard')({
component: DashboardComponent,
})
function DashboardComponent() {
const { auth } = Route.useRouteContext()
return (
<div className="p-6">
<div className="flex justify-between items-center mb-6">
<h1 className="text-3xl font-bold">Dashboard</h1>
<button
onClick={auth.logout}
className="bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700"
>
Sign Out
</button>
</div>
<div className="bg-white p-6 rounded-lg shadow">
<h2 className="text-xl font-semibold mb-2">Welcome back!</h2>
<p className="text-gray-600">
Hello, <strong>{auth.user?.username}</strong>! You are successfully
authenticated.
</p>
<p className="text-sm text-gray-500 mt-2">Email: {auth.user?.email}</p>
</div>
</div>
)
}
Aktualisieren Sie Ihren AuthProvider, um den Authentifizierungsstatus nach dem Aktualisieren der Seite wiederherzustellen
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null)
const [isAuthenticated, setIsAuthenticated] = useState(false)
const [isLoading, setIsLoading] = useState(true)
// Restore auth state on app load
useEffect(() => {
const token = localStorage.getItem('auth-token')
if (token) {
// Validate token with your API
fetch('/api/validate-token', {
headers: { Authorization: `Bearer ${token}` },
})
.then((response) => response.json())
.then((userData) => {
if (userData.valid) {
setUser(userData.user)
setIsAuthenticated(true)
} else {
localStorage.removeItem('auth-token')
}
})
.catch(() => {
localStorage.removeItem('auth-token')
})
.finally(() => {
setIsLoading(false)
})
} else {
setIsLoading(false)
}
}, [])
// Show loading state while checking auth
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
Loading...
</div>
)
}
// ... rest of the provider logic
}
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null)
const [isAuthenticated, setIsAuthenticated] = useState(false)
const [isLoading, setIsLoading] = useState(true)
// Restore auth state on app load
useEffect(() => {
const token = localStorage.getItem('auth-token')
if (token) {
// Validate token with your API
fetch('/api/validate-token', {
headers: { Authorization: `Bearer ${token}` },
})
.then((response) => response.json())
.then((userData) => {
if (userData.valid) {
setUser(userData.user)
setIsAuthenticated(true)
} else {
localStorage.removeItem('auth-token')
}
})
.catch(() => {
localStorage.removeItem('auth-token')
})
.finally(() => {
setIsLoading(false)
})
} else {
setIsLoading(false)
}
}, [])
// Show loading state while checking auth
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
Loading...
</div>
)
}
// ... rest of the provider logic
}
Bevor Sie die Authentifizierung in Produktion nehmen, stellen Sie sicher, dass Sie Folgendes getan haben:
Problem: Fehlermeldung useAuth muss innerhalb eines AuthProvider verwendet werden.
Lösung: Stellen Sie sicher, dass AuthProvider Ihre gesamte Anwendung umschließt und RouterProvider darin enthalten ist.
Problem: Der Authentifizierungsstatus wird beim Aktualisieren der Seite zurückgesetzt.
Lösung: Fügen Sie Token-Persistenz hinzu, wie im Abschnitt "Persistenz" oben gezeigt.
Problem: Geschützte Inhalte werden kurz angezeigt, bevor zur Anmeldung weitergeleitet wird.
Lösung: Verwenden Sie beforeLoad anstelle von Authentifizierungsprüfungen auf Komponentenebene
export const Route = createFileRoute('/_authenticated/dashboard')({
beforeLoad: ({ context }) => {
if (!context.auth.isAuthenticated) {
throw redirect({ to: '/login' })
}
},
component: DashboardComponent,
})
export const Route = createFileRoute('/_authenticated/dashboard')({
beforeLoad: ({ context }) => {
if (!context.auth.isAuthenticated) {
throw redirect({ to: '/login' })
}
},
component: DashboardComponent,
})
Nachdem Sie die Basis-Authentifizierung eingerichtet haben, möchten Sie möglicherweise Folgendes tun:
Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.