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 연동 시 흔한 문제와 해결 방법
ESC

검색어를 입력하세요