9.5 KiB
9.5 KiB
description
| description |
|---|
| Framework-specific implementation guide for adding Auth0 OAuth to your application |
OAuth Implementation Guide
Detailed code walkthrough for implementing Auth0 authentication in your framework.
Quick Start
/oauth-implement [framework]
Supported frameworks:
react- React with Create React App or Vitenextjs- Next.js (13+ with App Router recommended)nodejs- Node.js/Express backendfastapi- Python FastAPIdjango- Python Djangovue- Vue.jssvelte- Svelteflutter- Flutter mobilereact-native- React Native mobile
Implementation Steps by Framework
React (SPA)
1. Install Auth0 React SDK
npm install @auth0/auth0-react
2. Wrap App with Auth0Provider
// src/main.tsx
import { Auth0Provider } from '@auth0/auth0-react'
ReactDOM.createRoot(document.getElementById('root')!).render(
<Auth0Provider
domain={import.meta.env.VITE_AUTH0_DOMAIN}
clientId={import.meta.env.VITE_AUTH0_CLIENT_ID}
authorizationParams={{
redirect_uri: window.location.origin,
scope: 'openid profile email read:items',
audience: `https://${import.meta.env.VITE_AUTH0_DOMAIN}/api/v2/`
}}
cacheLocation="memory"
>
<App />
</Auth0Provider>
)
3. Create Login/Logout Components
// src/components/Auth.tsx
import { useAuth0 } from '@auth0/auth0-react'
export function LoginButton() {
const { loginWithRedirect, isAuthenticated } = useAuth0()
if (isAuthenticated) return null
return <button onClick={() => loginWithRedirect()}>Login</button>
}
export function LogoutButton() {
const { logout, isAuthenticated } = useAuth0()
if (!isAuthenticated) return null
return <button onClick={() => logout({ logoutParams: { returnTo: window.location.origin } })}>Logout</button>
}
export function Profile() {
const { user, isAuthenticated } = useAuth0()
if (!isAuthenticated) return <p>Not logged in</p>
return (
<div>
<img src={user?.picture} alt={user?.name} />
<h2>{user?.name}</h2>
<p>{user?.email}</p>
</div>
)
}
4. Protected Routes
// src/components/ProtectedRoute.tsx
import { useAuth0 } from '@auth0/auth0-react'
export function ProtectedRoute({ children }: any) {
const { isAuthenticated, isLoading } = useAuth0()
if (isLoading) return <div>Loading...</div>
if (!isAuthenticated) return <div>Please log in</div>
return children
}
// In App.tsx
<Routes>
<Route path="/dashboard" element={<ProtectedRoute><Dashboard /></ProtectedRoute>} />
</Routes>
5. Call Protected API
// src/hooks/useApi.ts
import { useAuth0 } from '@auth0/auth0-react'
import { useEffect, useState } from 'react'
export function useApi<T>(url: string) {
const { getAccessTokenSilently } = useAuth0()
const [data, setData] = useState<T | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
let isMounted = true
const fetchData = async () => {
try {
const token = await getAccessTokenSilently()
const response = await fetch(url, {
headers: { Authorization: `Bearer ${token}` }
})
if (!response.ok) throw new Error('API error')
const result = await response.json()
if (isMounted) setData(result)
} catch (err) {
if (isMounted) setError(err instanceof Error ? err : new Error('Unknown error'))
} finally {
if (isMounted) setLoading(false)
}
}
fetchData()
return () => { isMounted = false }
}, [url, getAccessTokenSilently])
return { data, error, loading }
}
6. Environment Variables
VITE_AUTH0_DOMAIN=YOUR_DOMAIN.auth0.com
VITE_AUTH0_CLIENT_ID=YOUR_CLIENT_ID
Next.js (Full-Stack)
1. Install SDK
npm install @auth0/nextjs-auth0
2. Create Auth Routes
App Router (Recommended):
// app/api/auth/[auth0]/route.ts
import { handleAuth } from '@auth0/nextjs-auth0'
export const GET = handleAuth()
Pages Router:
// pages/api/auth/[auth0].ts
import { handleAuth } from '@auth0/nextjs-auth0'
export default handleAuth()
3. Access User in Components
App Router:
// app/profile/page.tsx
import { getSession } from '@auth0/nextjs-auth0'
import { redirect } from 'next/navigation'
export default async function ProfilePage() {
const session = await getSession()
if (!session) {
redirect('/api/auth/login')
}
return (
<div>
<h1>Welcome, {session.user.name}</h1>
<img src={session.user.picture} alt={session.user.name} />
</div>
)
}
4. Login/Logout Links
// app/components/Nav.tsx
import { getSession } from '@auth0/nextjs-auth0'
export async function Nav() {
const session = await getSession()
return (
<nav>
{session ? (
<>
<span>{session.user.name}</span>
<a href="/api/auth/logout">Logout</a>
</>
) : (
<a href="/api/auth/login">Login</a>
)}
</nav>
)
}
5. Call API with Token
// app/api/items/route.ts
import { getSession } from '@auth0/nextjs-auth0'
import { NextResponse } from 'next/server'
export async function GET() {
const session = await getSession()
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const response = await fetch('http://localhost:3001/items', {
headers: { Authorization: `Bearer ${session.accessToken}` }
})
return NextResponse.json(await response.json())
}
6. Environment Variables
AUTH0_SECRET='[generated secret]'
AUTH0_BASE_URL='http://localhost:3000'
AUTH0_ISSUER_BASE_URL='https://YOUR_DOMAIN.auth0.com'
AUTH0_CLIENT_ID='YOUR_CLIENT_ID'
AUTH0_CLIENT_SECRET='YOUR_CLIENT_SECRET'
Node.js/Express Backend
1. Install Dependencies
npm install express-jwt jwks-rsa
2. Create JWT Middleware
// middleware/auth.ts
import { expressjwt as jwt } from 'express-jwt'
import jwksRsa from 'jwks-rsa'
const checkJwt = jwt({
secret: jwksRsa.expressJwtSecret({
cache: true,
jwksUri: `https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`
}),
audience: `https://${process.env.AUTH0_DOMAIN}/api/v2/`,
issuer: `https://${process.env.AUTH0_DOMAIN}/`,
algorithms: ['RS256']
})
export default checkJwt
3. Protect Routes
// routes/items.ts
import express from 'express'
import checkJwt from '../middleware/auth'
const router = express.Router()
router.get('/public', (req, res) => {
res.json({ message: 'Public' })
})
router.get('/items', checkJwt, (req, res) => {
// req.auth contains decoded JWT
res.json({ userId: req.auth.sub, items: [] })
})
// Scope validation
router.delete('/items/:id', checkJwt, (req, res) => {
const scope = req.auth.scope?.split(' ') || []
if (!scope.includes('delete:items')) {
return res.status(403).json({ error: 'Insufficient permissions' })
}
res.json({ message: 'Item deleted' })
})
export default router
FastAPI (Python)
# main.py
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import HTTPBearer, HTTPAuthCredentials
import jwt
from jwt import algorithms
app = FastAPI()
security = HTTPBearer()
DOMAIN = "YOUR_DOMAIN.auth0.com"
async def verify_token(credentials: HTTPAuthCredentials = Depends(security)):
token = credentials.credentials
try:
# Get public key
jwks_client = jwt.PyJWKClient(f"https://{DOMAIN}/.well-known/jwks.json")
signing_key = jwks_client.get_signing_key_from_jwt(token)
# Verify token
payload = jwt.decode(
token,
signing_key.key,
algorithms=["RS256"],
audience=f"https://{DOMAIN}/api/v2/",
issuer=f"https://{DOMAIN}/"
)
return payload
except Exception as e:
raise HTTPException(status_code=401, detail="Invalid token")
@app.get("/items")
async def get_items(user: dict = Depends(verify_token)):
return { "user_id": user["sub"], "items": [] }
Testing Implementation
Test Login Flow
# 1. Visit login page
open http://localhost:3000/api/auth/login
# 2. Complete auth flow in Auth0
# 3. Check if callback works
# 4. Test API
curl -H "Authorization: Bearer <ACCESS_TOKEN>" \
http://localhost:3001/api/items
Inspect Token (React)
// In browser console
const token = await auth0.getAccessTokenSilently()
console.log(token)
// Decode at jwt.io
Common Issues & Solutions
| Issue | Solution |
|---|---|
| Token expired | SDK auto-refreshes, check refresh token rotation enabled |
| CORS error | Configure CORS in backend, check Auth0 allowed origins |
| Silent auth fails | Check cacheLocation: "memory", try explicit login |
| Scope not in token | Add to Auth0 rule or API scopes config |
| Token validation fails | Verify audience, issuer, algorithms match |
Next Steps
- Test implementation with
/oauth-security-audit - Run
/oauth-troubleshootfor any issues - Deploy and update callback URLs to production domain
Status: Implementation guide ready!