Files
external-app/app/callback/page.tsx
zhangheng f737603b0c chore: initial commit of external-app SSO consumer
Next.js sample application that integrates with the mock SSO service:
redirects to sso-mock for login, receives the one-time sso_code on its
/callback route, and exchanges it for user info to establish a session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 18:11:23 +08:00

75 lines
1.9 KiB
TypeScript

'use client'
import { Suspense } from 'react'
import { useEffect, useState } from 'react'
import { useSearchParams } from 'next/navigation'
import { Stack, Text, Loader } from '@mantine/core'
function CallbackContent() {
const searchParams = useSearchParams()
const [error, setError] = useState<string | null>(null)
useEffect(() => {
const code = searchParams.get('sso_code')
if (!code) {
setError('缺少 sso_code 参数')
return
}
// 用 code 调用本应用的 /api/callback 换取用户信息
fetch('/api/callback', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code }),
})
.then(async (res) => {
if (!res.ok) {
const data = await res.json()
throw new Error(data.error || '认证失败')
}
// 成功 -> 跳转到首页
window.location.href = '/'
})
.catch((err) => {
setError(err instanceof Error ? err.message : '认证失败')
})
}, [searchParams])
if (error) {
return (
<Stack align="center" justify="center" h="100vh" gap="md">
<Text size="lg" c="red"></Text>
<Text size="sm" c="dimmed">{error}</Text>
<Text
size="sm"
c="brand"
style={{ cursor: 'pointer' }}
onClick={() => (window.location.href = '/')}>
</Text>
</Stack>
)
}
return (
<Stack align="center" justify="center" h="100vh" gap="md">
<Loader size="lg" />
<Text size="sm" c="dimmed"> SSO ...</Text>
</Stack>
)
}
export default function CallbackPage() {
return (
<Suspense
fallback={
<Stack align="center" justify="center" h="100vh" gap="md">
<Loader size="lg" />
<Text size="sm" c="dimmed"> SSO ...</Text>
</Stack>
}>
<CallbackContent />
</Suspense>
)
}