Replace hand-rolled auth with Ory Kratos browser flows
The honeyDue Go API no longer owns identity — Ory Kratos at
NEXT_PUBLIC_KRATOS_URL does. Rewrite the web app's auth layer to use Kratos
browser self-service flows and the ory_kratos_session cookie.
- Kratos client (src/lib/kratos/): flow init/fetch/submit, whoami, logout,
message helpers, and the useKratosFlow lifecycle hook.
- Generic flow renderer (src/components/auth/): KratosFlowForm renders
ui.nodes (inputs, oidc social buttons, hidden csrf), KratosMessages
surfaces flow-level messages, AuthGate guards /app via whoami.
- Auth pages (login/register/forgot-password/verify-email/reset-password)
rewritten as Kratos login/registration/recovery/verification/settings
flows. Password change in settings now uses the Kratos settings flow.
- Proxy + serverFetch forward the ory_kratos_session cookie to the Go API
instead of "Authorization: Token". Deleted /api/auth/{login,logout,me}.
- Middleware does a cheap ory_kratos_session cookie pre-filter; AuthGate's
whoami call is authoritative.
- auth store rewritten around whoami + GET /auth/me; removed dead auth API
functions, types/auth, validations/auth, code-input.
- Added NEXT_PUBLIC_KRATOS_URL to config (.env.example) and CLAUDE.md.
npm run build passes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+40
-10
@@ -1,26 +1,56 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Middleware — cheap cookie-presence route pre-filter
|
||||
// ---------------------------------------------------------------------------
|
||||
// Identity is owned by Ory Kratos. The authoritative session check is the
|
||||
// `whoami` call done client-side by <AuthGate> (and server-side by the Go API
|
||||
// for every API request). Middleware only does a cheap pre-filter on the
|
||||
// presence of the `ory_kratos_session` cookie so that obviously-logged-out
|
||||
// users are bounced to /login without a flash of the app shell.
|
||||
//
|
||||
// The cookie's mere presence does NOT guarantee a valid session (it may be
|
||||
// expired) — that's why <AuthGate> still re-verifies via `whoami`.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const SESSION_COOKIE = 'ory_kratos_session';
|
||||
|
||||
export function middleware(request: NextRequest) {
|
||||
const token = request.cookies.get('honeydue-token')?.value;
|
||||
const hasSession = Boolean(request.cookies.get(SESSION_COOKIE)?.value);
|
||||
const { pathname } = request.nextUrl;
|
||||
|
||||
// Public paths that don't require auth
|
||||
const publicPaths = ['/', '/login', '/register', '/forgot-password', '/reset-password', '/verify-email', '/demo', '/help'];
|
||||
const isPublicPath = publicPaths.some(p => pathname === p || pathname.startsWith(p + '/'));
|
||||
// Public paths that don't require auth.
|
||||
const publicPaths = [
|
||||
'/',
|
||||
'/login',
|
||||
'/register',
|
||||
'/forgot-password',
|
||||
'/reset-password',
|
||||
'/verify-email',
|
||||
'/demo',
|
||||
'/help',
|
||||
];
|
||||
const isPublicPath = publicPaths.some(
|
||||
(p) => pathname === p || pathname.startsWith(p + '/'),
|
||||
);
|
||||
const isApiPath = pathname.startsWith('/api/');
|
||||
const isStaticPath = pathname.startsWith('/_next/') || pathname.startsWith('/favicon') || pathname.match(/\.(png|jpg|jpeg|gif|svg|ico|webp|woff2?|ttf|css|js)$/);
|
||||
const isStaticPath =
|
||||
pathname.startsWith('/_next/') ||
|
||||
pathname.startsWith('/favicon') ||
|
||||
pathname.match(/\.(png|jpg|jpeg|gif|svg|ico|webp|woff2?|ttf|css|js)$/);
|
||||
|
||||
// Skip middleware for API routes and static files
|
||||
// Skip middleware for API routes and static files.
|
||||
if (isApiPath || isStaticPath) return NextResponse.next();
|
||||
|
||||
// No token + protected path → redirect to login
|
||||
if (!token && !isPublicPath) {
|
||||
// No session cookie + protected path -> redirect to login.
|
||||
if (!hasSession && !isPublicPath) {
|
||||
return NextResponse.redirect(new URL('/login', request.url));
|
||||
}
|
||||
|
||||
// Has token + auth page → redirect to app
|
||||
if (token && (pathname === '/login' || pathname === '/register')) {
|
||||
// Has a session cookie + on the login/register pages -> send to the app.
|
||||
// (AuthGate / whoami will catch the case where the cookie is stale.)
|
||||
if (hasSession && (pathname === '/login' || pathname === '/register')) {
|
||||
return NextResponse.redirect(new URL('/app', request.url));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user