guides
미들웨어 설정
SSO 미들웨어 동작 원리와 체이닝 패턴
미들웨어 설정
미들웨어 동작 원리
Connect SDK 미들웨어는 모든 요청에 대해 다음 순서로 동작합니다.
요청 수신
├── 1. 쿠키 파싱 (connect_access, connect_refresh)
├── 2. access_token JWT 서명 검증 (JWKS 공개키)
├── 3. 토큰 만료 확인
│ ├── 유효 → locals.user 설정
│ └── 만료 → refresh_token으로 자동 갱신
│ ├── 갱신 성공 → 새 쿠키 설정 + locals.user 설정
│ └── 갱신 실패 → locals.user = null
└── 4. 다음 미들웨어 또는 라우트 핸들러로 전달
인증에 실패하더라도 요청을 차단하지 않습니다.
locals.user가 null인지 확인하여 보호 라우트를 구현하세요.
SessionUser 타입
인증된 사용자 정보는 locals.user에 다음 타입으로 설정됩니다.
interface SessionUser {
/** 사용자 고유 ID (UUID) */
id: string;
/** 이메일 주소 */
email: string;
/** 사용자 표시 이름 */
name: string;
/** 역할 목록 */
roles: string[];
}
Astro에서는 Astro.locals.user, SvelteKit에서는 event.locals.user로 접근합니다.
미들웨어 체이닝 (Astro)
Astro의 sequence()를 사용하여 여러 미들웨어를 조합할 수 있습니다.
// src/middleware.ts
import { sequence } from "astro:middleware";
import { auth } from "./lib/connect";
/** 요청 로깅 미들웨어 */
async function logging(context: any, next: () => Promise<Response>) {
const start = Date.now();
const response = await next();
const duration = Date.now() - start;
console.log(`${context.request.method} ${context.url.pathname} ${duration}ms`);
return response;
}
/** i18n 로케일 감지 미들웨어 */
async function i18n(context: any, next: () => Promise<Response>) {
const lang = context.request.headers.get("accept-language")?.split(",")[0] ?? "ko";
context.locals.locale = lang.startsWith("en") ? "en" : "ko";
return next();
}
export const onRequest = sequence(
logging,
auth.middleware,
i18n,
);
실행 순서: logging -> auth.middleware -> i18n -> 라우트 핸들러.
auth 미들웨어 이후에 실행되는 미들웨어에서는 locals.user를 참조할 수 있습니다.
보호 라우트 패턴
방법 1: protectedPaths 옵션
SDK 초기화 시 보호할 경로를 선언적으로 지정합니다.
// src/lib/connect.ts
import { createAstroAuth } from "@modfolio/connect-sdk/astro";
export const auth = createAstroAuth({
clientId: "your-app-name",
protectedPaths: ["/dashboard", "/settings", "/api/**"],
loginRedirect: "/auth/login",
});
protectedPaths에 매칭되는 경로에 비인증 사용자가 접근하면 loginRedirect로 리다이렉트됩니다.
glob 패턴(**)을 지원합니다.
방법 2: 수동 가드
페이지나 API 핸들러에서 직접 인증 상태를 확인합니다.
// src/pages/dashboard.astro
---
const user = Astro.locals.user;
if (!user) {
return Astro.redirect("/auth/login?redirect=/dashboard");
}
---
<h1>{user.name}님의 대시보드</h1>
// src/pages/api/profile.ts
import type { APIRoute } from "astro";
export const GET: APIRoute = async ({ locals }) => {
if (!locals.user) {
return new Response(JSON.stringify({ error: "unauthorized" }), {
status: 401,
headers: { "Content-Type": "application/json" },
});
}
return new Response(JSON.stringify({ user: locals.user }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
};
에러 처리
토큰 만료 시 동작
| 상황 | SDK 동작 | locals.user |
|---|---|---|
| access_token 유효 | JWT 클레임 파싱 | SessionUser |
| access_token 만료, refresh 유효 | 자동 갱신 + 새 쿠키 설정 | SessionUser |
| access_token 만료, refresh 만료 | 쿠키 삭제 | null |
| 토큰 서명 불일치 | 쿠키 삭제 | null |
리프레시 실패 시 SDK는 에러를 throw하지 않습니다.
locals.user가 null로 설정될 뿐이므로, 앱에서 적절히 처리하세요.
프레임워크별 미들웨어 패턴
SvelteKit (hooks.server.ts)
// src/hooks.server.ts
import { auth } from "$lib/server/auth";
import type { Handle } from "@sveltejs/kit";
import { sequence } from "@sveltejs/kit/hooks";
const logging: Handle = async ({ event, resolve }) => {
const start = Date.now();
const response = await resolve(event);
console.log(`${event.request.method} ${event.url.pathname} ${Date.now() - start}ms`);
return response;
};
export const handle = sequence(
logging,
auth.handle,
);
event.locals.user로 인증 정보에 접근합니다.
SolidStart (middleware)
// src/middleware.ts
import { createMiddleware } from "@solidjs/start/middleware";
import { auth } from "./lib/connect";
export default createMiddleware({
onRequest: [auth.middleware],
});
SolidStart에서는 getRequestEvent()를 통해 locals.user에 접근합니다.
다음 단계
- 문제 해결 – SSO 연동 시 흔한 문제와 해결 방법